From afcb22b52db42c852074cee4a2bc80a0f2edaf65 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Mon, 17 Apr 2023 19:03:00 +0100 Subject: [PATCH 01/44] Add audit event logging. --- models/asymkey/ssh_key.go | 49 +++--- models/db/context.go | 5 + models/org_team.go | 25 +-- models/organization/org.go | 5 + models/system/notice.go | 1 + models/user/email_address.go | 6 +- modules/context/api.go | 3 + modules/context/auth.go | 3 + modules/context/org.go | 2 +- options/locale/locale_en-US.ini | 1 + routers/api/v1/admin/org.go | 3 + routers/api/v1/admin/user.go | 46 +++++- routers/api/v1/api.go | 4 + routers/api/v1/org/org.go | 12 +- routers/api/v1/org/team.go | 30 ++++ routers/api/v1/repo/collaborators.go | 11 +- routers/api/v1/repo/repo.go | 6 + routers/api/v1/repo/teams.go | 7 + routers/api/v1/repo/transfer.go | 9 +- routers/api/v1/user/app.go | 43 ++++- routers/init.go | 3 + routers/install/install.go | 3 + routers/private/hook_post_receive.go | 6 + routers/web/admin/applications.go | 16 +- routers/web/admin/auths.go | 15 +- routers/web/admin/emails.go | 24 ++- routers/web/admin/hooks.go | 17 +- routers/web/admin/users.go | 49 +++++- routers/web/auth/2fa.go | 15 +- routers/web/auth/auth.go | 16 +- routers/web/auth/oauth.go | 27 ++- routers/web/auth/openid.go | 5 + routers/web/auth/password.go | 9 + routers/web/org/org.go | 4 + routers/web/org/setting.go | 33 +++- routers/web/org/setting_oauth2.go | 21 ++- routers/web/org/setting_secrets.go | 8 +- routers/web/org/teams.go | 85 ++++++++-- routers/web/repo/repo.go | 4 + routers/web/repo/setting.go | 69 +++++++- routers/web/repo/setting_protected_branch.go | 16 ++ routers/web/repo/setting_secrets.go | 8 +- routers/web/repo/tag.go | 7 + routers/web/repo/webhook.go | 76 +++++++-- routers/web/shared/secrets/secrets.go | 65 +++++++- routers/web/user/setting/account.go | 19 ++- routers/web/user/setting/applications.go | 23 ++- routers/web/user/setting/keys.go | 31 +++- routers/web/user/setting/oauth2.go | 17 +- routers/web/user/setting/oauth2_common.go | 92 +++++++++-- routers/web/user/setting/profile.go | 16 +- routers/web/user/setting/secrets.go | 8 +- routers/web/user/setting/security/2fa.go | 7 + routers/web/user/setting/security/openid.go | 15 ++ routers/web/user/setting/security/security.go | 30 ++-- routers/web/user/setting/security/webauthn.go | 17 +- routers/web/user/setting/webhooks.go | 17 +- services/asymkey/deploy_key.go | 16 ++ services/asymkey/ssh_key.go | 10 ++ services/audit/action.go | 122 ++++++++++++++ services/audit/appender.go | 40 +++++ services/audit/audit.go | 155 ++++++++++++++++++ services/audit/helper.go | 52 ++++++ services/auth/reverseproxy.go | 3 + .../auth/source/ldap/source_authenticate.go | 43 ++++- services/auth/source/ldap/source_sync.go | 57 +++++-- .../auth/source/pam/source_authenticate.go | 3 + .../auth/source/smtp/source_authenticate.go | 3 + services/auth/source/source_group_sync.go | 5 + services/auth/sspi_windows.go | 3 + services/externalaccount/user.go | 3 + services/org/org.go | 5 +- services/org/org_test.go | 8 +- services/repository/fork.go | 10 +- services/repository/repository.go | 11 +- services/repository/transfer.go | 8 + services/user/user.go | 20 ++- services/user/user_test.go | 14 +- services/wiki/wiki.go | 6 +- 79 files changed, 1509 insertions(+), 252 deletions(-) create mode 100644 services/audit/action.go create mode 100644 services/audit/appender.go create mode 100644 services/audit/audit.go create mode 100644 services/audit/helper.go diff --git a/models/asymkey/ssh_key.go b/models/asymkey/ssh_key.go index 8d84c2f7dcdf3..c7618405029f5 100644 --- a/models/asymkey/ssh_key.go +++ b/models/asymkey/ssh_key.go @@ -317,17 +317,17 @@ func PublicKeyIsExternallyManaged(id int64) (bool, error) { return false, nil } -// deleteKeysMarkedForDeletion returns true if ssh keys needs update -func deleteKeysMarkedForDeletion(keys []string) (bool, error) { +// deleteKeysMarkedForDeletion returns the deleted keys +func deleteKeysMarkedForDeletion(keys []string) ([]*PublicKey, error) { // Start session ctx, committer, err := db.TxContext(db.DefaultContext) if err != nil { - return false, err + return nil, err } defer committer.Close() - // Delete keys marked for deletion - var sshKeysNeedUpdate bool + deletedKeys := make([]*PublicKey, 0, len(keys)) + for _, KeyToDelete := range keys { key, err := SearchPublicKeyByContent(ctx, KeyToDelete) if err != nil { @@ -338,19 +338,21 @@ func deleteKeysMarkedForDeletion(keys []string) (bool, error) { log.Error("deletePublicKeys: %v", err) continue } - sshKeysNeedUpdate = true + + deletedKeys = append(deletedKeys, key) } if err := committer.Commit(); err != nil { - return false, err + return nil, err } - return sshKeysNeedUpdate, nil + return deletedKeys, nil } -// AddPublicKeysBySource add a users public keys. Returns true if there are changes. -func AddPublicKeysBySource(usr *user_model.User, s *auth.Source, sshPublicKeys []string) bool { - var sshKeysNeedUpdate bool +// AddPublicKeysBySource add a users public keys. Returns the added keys. +func AddPublicKeysBySource(usr *user_model.User, s *auth.Source, sshPublicKeys []string) []*PublicKey { + addedKeys := make([]*PublicKey, 0, len(sshPublicKeys)) + for _, sshKey := range sshPublicKeys { var err error found := false @@ -368,28 +370,27 @@ func AddPublicKeysBySource(usr *user_model.User, s *auth.Source, sshPublicKeys [ marshalled = marshalled[:len(marshalled)-1] sshKeyName := fmt.Sprintf("%s-%s", s.Name, ssh.FingerprintSHA256(out)) - if _, err := AddPublicKey(usr.ID, sshKeyName, marshalled, s.ID); err != nil { + if pubKey, err := AddPublicKey(usr.ID, sshKeyName, marshalled, s.ID); err != nil { if IsErrKeyAlreadyExist(err) { log.Trace("AddPublicKeysBySource[%s]: Public SSH Key %s already exists for user", sshKeyName, usr.Name) } else { log.Error("AddPublicKeysBySource[%s]: Error adding Public SSH Key for user %s: %v", sshKeyName, usr.Name, err) } } else { + addedKeys = append(addedKeys, pubKey) + log.Trace("AddPublicKeysBySource[%s]: Added Public SSH Key for user %s", sshKeyName, usr.Name) - sshKeysNeedUpdate = true } } if !found && err != nil { log.Warn("AddPublicKeysBySource[%s]: Skipping invalid Public SSH Key for user %s: %v", s.Name, usr.Name, sshKey) } } - return sshKeysNeedUpdate + return addedKeys } // SynchronizePublicKeys updates a users public keys. Returns true if there are changes. -func SynchronizePublicKeys(usr *user_model.User, s *auth.Source, sshPublicKeys []string) bool { - var sshKeysNeedUpdate bool - +func SynchronizePublicKeys(usr *user_model.User, s *auth.Source, sshPublicKeys []string) ([]*PublicKey, []*PublicKey) { log.Trace("synchronizePublicKeys[%s]: Handling Public SSH Key synchronization for user %s", s.Name, usr.Name) // Get Public Keys from DB with current LDAP source @@ -418,7 +419,7 @@ func SynchronizePublicKeys(usr *user_model.User, s *auth.Source, sshPublicKeys [ // Check if Public Key sync is needed if util.SliceSortedEqual(giteaKeys, providedKeys) { log.Trace("synchronizePublicKeys[%s]: Public Keys are already in sync for %s (Source:%v/DB:%v)", s.Name, usr.Name, len(providedKeys), len(giteaKeys)) - return false + return nil, nil } log.Trace("synchronizePublicKeys[%s]: Public Key needs update for user %s (Source:%v/DB:%v)", s.Name, usr.Name, len(providedKeys), len(giteaKeys)) @@ -429,9 +430,8 @@ func SynchronizePublicKeys(usr *user_model.User, s *auth.Source, sshPublicKeys [ newKeys = append(newKeys, key) } } - if AddPublicKeysBySource(usr, s, newKeys) { - sshKeysNeedUpdate = true - } + + addedKeys := AddPublicKeysBySource(usr, s, newKeys) // ToDo Audit // Mark keys from DB that no longer exist in the source for deletion var giteaKeysToDelete []string @@ -443,13 +443,10 @@ func SynchronizePublicKeys(usr *user_model.User, s *auth.Source, sshPublicKeys [ } // Delete keys from DB that no longer exist in the source - needUpd, err := deleteKeysMarkedForDeletion(giteaKeysToDelete) + deletedKeys, err := deleteKeysMarkedForDeletion(giteaKeysToDelete) if err != nil { log.Error("synchronizePublicKeys[%s]: Error deleting Public Keys marked for deletion for user %s: %v", s.Name, usr.Name, err) } - if needUpd { - sshKeysNeedUpdate = true - } - return sshKeysNeedUpdate + return addedKeys, deletedKeys } diff --git a/models/db/context.go b/models/db/context.go index 670f6272aa9a9..c1fefa8e1d926 100644 --- a/models/db/context.go +++ b/models/db/context.go @@ -179,6 +179,11 @@ func GetByBean(ctx context.Context, bean interface{}) (bool, error) { return GetEngine(ctx).Get(bean) } +// GetBeanByID +func GetBeanByID(ctx context.Context, id, bean interface{}) (bool, error) { + return GetEngine(ctx).ID(id).Get(bean) +} + // DeleteByBean deletes all records according non-empty fields of the bean as conditions. func DeleteByBean(ctx context.Context, bean interface{}) (int64, error) { return GetEngine(ctx).Delete(bean) diff --git a/models/org_team.go b/models/org_team.go index be3b63b52ec49..288440379018c 100644 --- a/models/org_team.go +++ b/models/org_team.go @@ -55,36 +55,39 @@ func AddRepository(ctx context.Context, t *organization.Team, repo *repo_model.R // addAllRepositories adds all repositories to the team. // If the team already has some repositories they will be left unchanged. -func addAllRepositories(ctx context.Context, t *organization.Team) error { +func addAllRepositories(ctx context.Context, t *organization.Team) ([]*repo_model.Repository, error) { orgRepos, err := organization.GetOrgRepositories(ctx, t.OrgID) if err != nil { - return fmt.Errorf("get org repos: %w", err) + return nil, fmt.Errorf("get org repos: %w", err) } + added := make([]*repo_model.Repository, 0, len(orgRepos)) for _, repo := range orgRepos { if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repo.ID) { if err := AddRepository(ctx, t, repo); err != nil { - return fmt.Errorf("AddRepository: %w", err) + return nil, fmt.Errorf("AddRepository: %w", err) } + added = append(added, repo) } } - return nil + return added, nil } // AddAllRepositories adds all repositories to the team -func AddAllRepositories(t *organization.Team) (err error) { +func AddAllRepositories(t *organization.Team) ([]*repo_model.Repository, error) { ctx, committer, err := db.TxContext(db.DefaultContext) if err != nil { - return err + return nil, err } defer committer.Close() - if err = addAllRepositories(ctx, t); err != nil { - return err + added, err := addAllRepositories(ctx, t) + if err != nil { + return nil, err } - return committer.Commit() + return added, committer.Commit() } // RemoveAllRepositories removes all repositories from team and recalculates access @@ -283,7 +286,7 @@ func NewTeam(t *organization.Team) (err error) { // Add all repositories to the team if it has access to all of them. if t.IncludesAllRepositories { - err = addAllRepositories(ctx, t) + _, err = addAllRepositories(ctx, t) if err != nil { return fmt.Errorf("addAllRepositories: %w", err) } @@ -361,7 +364,7 @@ func UpdateTeam(t *organization.Team, authChanged, includeAllChanged bool) (err // Add all repositories to the team if it has access to all of them. if includeAllChanged && t.IncludesAllRepositories { - err = addAllRepositories(ctx, t) + _, err = addAllRepositories(ctx, t) if err != nil { return fmt.Errorf("addAllRepositories: %w", err) } diff --git a/models/organization/org.go b/models/organization/org.go index 53b020d7048d1..e4513c257a11b 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -90,6 +90,11 @@ func OrgFromUser(user *user_model.User) *Organization { return (*Organization)(user) } +// UserFromOrg converts organization to user +func UserFromOrg(org *Organization) *user_model.User { + return (*user_model.User)(org) +} + // TableName represents the real table name of Organization func (Organization) TableName() string { return "user" diff --git a/models/system/notice.go b/models/system/notice.go index e598abe22251a..787d47a2903dc 100644 --- a/models/system/notice.go +++ b/models/system/notice.go @@ -23,6 +23,7 @@ const ( NoticeRepository NoticeType = iota + 1 // NoticeTask type NoticeTask + NoticeAudit ) // Notice represents a system notice for admin. diff --git a/models/user/email_address.go b/models/user/email_address.go index e310858f92ee9..65697a0dc5e80 100644 --- a/models/user/email_address.go +++ b/models/user/email_address.go @@ -400,7 +400,7 @@ func MakeEmailPrimary(email *EmailAddress) error { } // VerifyActiveEmailCode verifies active email code when active account -func VerifyActiveEmailCode(code, email string) *EmailAddress { +func VerifyActiveEmailCode(code, email string) (*User, *EmailAddress) { minutes := setting.Service.ActiveCodeLives if user := GetVerifyUser(code); user != nil { @@ -411,11 +411,11 @@ func VerifyActiveEmailCode(code, email string) *EmailAddress { if base.VerifyTimeLimitCode(data, minutes, prefix) { emailAddress := &EmailAddress{UID: user.ID, Email: email} if has, _ := db.GetEngine(db.DefaultContext).Get(emailAddress); has { - return emailAddress + return user, emailAddress } } } - return nil + return nil, nil } // SearchEmailOrderBy is used to sort the results from SearchEmails() diff --git a/modules/context/api.go b/modules/context/api.go index f7a3384691246..f868ea329ca69 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -19,6 +19,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web/middleware" + "code.gitea.io/gitea/services/audit" ) // APIContext is a specific context for API service @@ -209,6 +210,8 @@ func (ctx *APIContext) CheckForOTP() { return } if !ok { + audit.Record(audit.UserAuthenticationFailTwoFactor, ctx.Context.Doer, ctx.Context.Doer, twofa, "Failed two-factor authentication for user %s.", ctx.Context.Doer.Name) + ctx.Context.Error(http.StatusUnauthorized) return } diff --git a/modules/context/auth.go b/modules/context/auth.go index 7cc29debbd03f..ac5a6530e0a30 100644 --- a/modules/context/auth.go +++ b/modules/context/auth.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web/middleware" + "code.gitea.io/gitea/services/audit" ) // ToggleOptions contains required or check options @@ -173,6 +174,8 @@ func ToggleAPI(options *ToggleOptions) func(ctx *APIContext) { return } if !ok { + audit.Record(audit.UserAuthenticationFailTwoFactor, ctx.Doer, ctx.Doer, twofa, "Failed two-factor authentication for user %s.", ctx.Doer.Name) + ctx.JSON(http.StatusForbidden, map[string]string{ "message": "Only signed in user is allowed to call APIs.", }) diff --git a/modules/context/org.go b/modules/context/org.go index 39a3038f910cb..1ad04bf7bbb6f 100644 --- a/modules/context/org.go +++ b/modules/context/org.go @@ -95,7 +95,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { if ctx.Org == nil { ctx.Org = &Organization{} } - ctx.Org.Organization = (*organization.Organization)(ctx.ContextUser) + ctx.Org.Organization = organization.OrgFromUser(ctx.ContextUser) } else { // ContextUser is an individual User return diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index d8693ee9dfa29..fafd991d55f22 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3065,6 +3065,7 @@ notices.delete_all = Delete All Notices notices.type = Type notices.type_1 = Repository notices.type_2 = Task +notices.type_3 = Audit notices.desc = Description notices.op = Op. notices.delete_success = The system notices have been deleted. diff --git a/routers/api/v1/admin/org.go b/routers/api/v1/admin/org.go index 6d50a12674a33..ebfa51b2e3a36 100644 --- a/routers/api/v1/admin/org.go +++ b/routers/api/v1/admin/org.go @@ -14,6 +14,7 @@ import ( api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/utils" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/convert" ) @@ -74,6 +75,8 @@ func CreateOrg(ctx *context.APIContext) { return } + audit.Record(audit.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) + ctx.JSON(http.StatusCreated, convert.ToOrganization(ctx, org)) } diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 8649a982b08ee..9c436a8323be1 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -26,6 +26,7 @@ import ( "code.gitea.io/gitea/routers/api/v1/user" "code.gitea.io/gitea/routers/api/v1/utils" asymkey_service "code.gitea.io/gitea/services/asymkey" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/mailer" user_service "code.gitea.io/gitea/services/user" @@ -143,6 +144,9 @@ func CreateUser(ctx *context.APIContext) { } return } + + audit.Record(audit.UserCreate, ctx.Doer, u, u, "Created user %s.", u.Name) + log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name) // Send email notification. @@ -179,6 +183,23 @@ func EditUser(ctx *context.APIContext) { // "422": // "$ref": "#/responses/validationError" + auditFields := struct { + LoginSource int64 + // Password + Email string + IsActive bool + IsAdmin bool + IsRestricted bool + Visibility api.VisibleType + }{ + LoginSource: ctx.ContextUser.LoginSource, + Email: ctx.ContextUser.Email, + IsActive: ctx.ContextUser.IsActive, + IsAdmin: ctx.ContextUser.IsAdmin, + IsRestricted: ctx.ContextUser.IsRestricted, + Visibility: ctx.ContextUser.Visibility, + } + form := web.GetForm(ctx).(*api.EditUserOption) parseAuthSource(ctx, ctx.ContextUser, form.SourceID, form.LoginName) @@ -186,6 +207,7 @@ func EditUser(ctx *context.APIContext) { return } + passwordChanged := false if len(form.Password) != 0 { if len(form.Password) < setting.MinPasswordLength { ctx.Error(http.StatusBadRequest, "PasswordTooShort", fmt.Errorf("password must be at least %d characters", setting.MinPasswordLength)) @@ -287,6 +309,26 @@ func EditUser(ctx *context.APIContext) { } return } + + if passwordChanged { + audit.Record(audit.UserPassword, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Password of user %s changed.", ctx.ContextUser.Name) + } + if auditFields.LoginSource != ctx.ContextUser.LoginSource { + audit.Record(audit.UserAuthenticationSource, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Authentication source of user %s changed.", ctx.ContextUser.Name) + } + if auditFields.Visibility != ctx.ContextUser.Visibility { + audit.Record(audit.UserVisibility, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Visibility of user %s changed from %s to %s.", ctx.ContextUser.Name, auditFields.Visibility.String(), ctx.ContextUser.Visibility.String()) + } + if auditFields.IsActive != ctx.ContextUser.IsActive { + audit.Record(audit.UserActive, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Activation status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserActiveString(ctx.ContextUser.IsActive)) + } + if auditFields.IsAdmin != ctx.ContextUser.IsAdmin { + audit.Record(audit.UserAdmin, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Admin status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserAdminString(ctx.ContextUser.IsAdmin)) + } + if auditFields.IsRestricted != ctx.ContextUser.IsRestricted { + audit.Record(audit.UserRestricted, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Restricted status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserRestrictedString(ctx.ContextUser.IsRestricted)) + } + log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, ctx.ContextUser.Name) ctx.JSON(http.StatusOK, convert.ToUser(ctx, ctx.ContextUser, ctx.Doer)) @@ -328,7 +370,7 @@ func DeleteUser(ctx *context.APIContext) { return } - if err := user_service.DeleteUser(ctx, ctx.ContextUser, ctx.FormBool("purge")); err != nil { + if err := user_service.DeleteUser(ctx, ctx.Doer, ctx.ContextUser, ctx.FormBool("purge")); err != nil { if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) || models.IsErrUserOwnPackages(err) { @@ -513,7 +555,7 @@ func RenameUser(ctx *context.APIContext) { } // Check if user name has been changed - if err := user_service.RenameUser(ctx, ctx.ContextUser, newName); err != nil { + if err := user_service.RenameUser(ctx, ctx.Doer, ctx.ContextUser, newName); err != nil { switch { case user_model.IsErrUserAlreadyExist(err): ctx.Error(http.StatusUnprocessableEntity, "", ctx.Tr("form.username_been_taken")) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 9510b17e2df0d..4cce5615e30a4 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -91,6 +91,7 @@ import ( "code.gitea.io/gitea/routers/api/v1/repo" "code.gitea.io/gitea/routers/api/v1/settings" "code.gitea.io/gitea/routers/api/v1/user" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/auth" context_service "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" @@ -119,6 +120,9 @@ func sudo() func(ctx *context.APIContext) { } return } + + audit.Record(audit.UserImpersonation, ctx.Doer, ctx.Doer, ctx.Doer, "User %s impersonating user %s.", ctx.Doer.Name, user.Name) + log.Trace("Sudo from (%s) to: %s", ctx.Doer.Name, user.Name) ctx.Doer = user } else { diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go index 4e30ad17620f3..4663b3e5d0d80 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/user" "code.gitea.io/gitea/routers/api/v1/utils" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/org" ) @@ -275,6 +276,8 @@ func Create(ctx *context.APIContext) { return } + audit.Record(audit.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) + ctx.JSON(http.StatusCreated, convert.ToOrganization(ctx, org)) } @@ -331,6 +334,8 @@ func Edit(ctx *context.APIContext) { org.Description = form.Description org.Website = form.Website org.Location = form.Location + + oldVisibility := org.Visibility if form.Visibility != "" { org.Visibility = api.VisibilityModes[form.Visibility] } @@ -345,6 +350,11 @@ func Edit(ctx *context.APIContext) { return } + audit.Record(audit.OrganizationUpdate, ctx.Doer, org, org, "Updated settings of organization %s.", org.Name) + if org.Visibility != oldVisibility { + audit.Record(audit.OrganizationVisibility, ctx.Doer, org, org, "Visibility of organization %s changed from %s to %s.", org.Name, oldVisibility.String(), org.Visibility.String()) + } + ctx.JSON(http.StatusOK, convert.ToOrganization(ctx, org)) } @@ -365,7 +375,7 @@ func Delete(ctx *context.APIContext) { // "204": // "$ref": "#/responses/empty" - if err := org.DeleteOrganization(ctx.Org.Organization); err != nil { + if err := org.DeleteOrganization(ctx.Doer, ctx.Org.Organization); err != nil { ctx.Error(http.StatusInternalServerError, "DeleteOrganization", err) return } diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index 50439251cc05f..3ed152812e2cf 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -21,6 +21,7 @@ import ( "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/user" "code.gitea.io/gitea/routers/api/v1/utils" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/convert" org_service "code.gitea.io/gitea/services/org" ) @@ -224,6 +225,8 @@ func CreateTeam(ctx *context.APIContext) { return } + audit.Record(audit.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, team, "Team %s was added to organziation %s.", team.Name, ctx.Org.Organization.Name) + apiTeam, err := convert.ToTeam(ctx, team) if err != nil { ctx.InternalServerError(err) @@ -275,6 +278,7 @@ func EditTeam(ctx *context.APIContext) { } isAuthChanged := false + oldAccessMode := team.AccessMode isIncludeAllChanged := false if !team.IsOwnerTeam() && len(form.Permission) != 0 { // Validate permission level. @@ -307,6 +311,11 @@ func EditTeam(ctx *context.APIContext) { return } + audit.Record(audit.OrganizationTeamUpdate, ctx.Doer, ctx.Org.Organization, team, "Updated settings of team %s/%s.", ctx.Org.Organization.Name, team.Name) + if isAuthChanged { + audit.Record(audit.OrganizationTeamPermission, ctx.Doer, ctx.Org.Organization, team, "Permission of team %s/%s changed from %s to %s.", ctx.Org.Organization.Name, team.Name, oldAccessMode.String(), team.AccessMode.String()) + } + apiTeam, err := convert.ToTeam(ctx, team) if err != nil { ctx.InternalServerError(err) @@ -335,6 +344,9 @@ func DeleteTeam(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "DeleteTeam", err) return } + + audit.Record(audit.OrganizationTeamRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Team %s was removed from organziation %s.", ctx.Org.Team.Name, ctx.Org.Organization.Name) + ctx.Status(http.StatusNoContent) } @@ -465,6 +477,15 @@ func AddTeamMember(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "AddMember", err) return } + + org, err := organization.GetOrgByID(ctx, ctx.Org.Team.OrgID) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetOrgByID", err) + return + } + + audit.Record(audit.OrganizationTeamMemberAdd, ctx.Doer, org, ctx.Org.Team, "User %s was added to team %s/%s.", u.Name, org.Name, ctx.Org.Team.Name) + ctx.Status(http.StatusNoContent) } @@ -502,6 +523,15 @@ func RemoveTeamMember(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "RemoveTeamMember", err) return } + + org, err := organization.GetOrgByID(ctx, ctx.Org.Team.OrgID) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetOrgByID", err) + return + } + + audit.Record(audit.OrganizationTeamMemberRemove, ctx.Doer, org, ctx.Org.Team, "User %s was removed from team %s/%s.", u.Name, org.Name, ctx.Org.Team.Name) + ctx.Status(http.StatusNoContent) } diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go index 942d4c799fbd3..095fad7330405 100644 --- a/routers/api/v1/repo/collaborators.go +++ b/routers/api/v1/repo/collaborators.go @@ -18,6 +18,7 @@ import ( api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/utils" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/convert" ) @@ -179,11 +180,16 @@ func AddCollaborator(ctx *context.APIContext) { return } + audit.Record(audit.RepositoryCollaboratorAdd, ctx.Doer, ctx.Repo.Repository, collaborator, "Added user %s as collaborator.", collaborator.Name) + if form.Permission != nil { - if err := repo_model.ChangeCollaborationAccessMode(ctx, ctx.Repo.Repository, collaborator.ID, perm.ParseAccessMode(*form.Permission)); err != nil { + accessMode := perm.ParseAccessMode(*form.Permission) + if err := repo_model.ChangeCollaborationAccessMode(ctx, ctx.Repo.Repository, collaborator.ID, accessMode); err != nil { ctx.Error(http.StatusInternalServerError, "ChangeCollaborationAccessMode", err) return } + + audit.Record(audit.RepositoryCollaboratorAccess, ctx.Doer, ctx.Repo.Repository, collaborator, "Changed access mode of collaborator %s to %s.", collaborator.Name, accessMode.String()) } ctx.Status(http.StatusNoContent) @@ -232,6 +238,9 @@ func DeleteCollaborator(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "DeleteCollaboration", err) return } + + audit.Record(audit.RepositoryCollaboratorRemove, ctx.Doer, ctx.Repo.Repository, collaborator, "Removed collaborator %s.", collaborator.Name) + ctx.Status(http.StatusNoContent) } diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 48ace3a8e02ce..fb512f0b66b89 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -29,6 +29,7 @@ import ( "code.gitea.io/gitea/modules/validation" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/utils" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/convert" repo_service "code.gitea.io/gitea/services/repository" ) @@ -732,6 +733,11 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err return err } + audit.Record(audit.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) + if visibilityChanged { + audit.Record(audit.RepositoryVisibility, ctx.Doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) + } + log.Trace("Repository basic settings updated: %s/%s", owner.Name, repo.Name) return nil } diff --git a/routers/api/v1/repo/teams.go b/routers/api/v1/repo/teams.go index 01292f18d8aed..d5a357d62c78a 100644 --- a/routers/api/v1/repo/teams.go +++ b/routers/api/v1/repo/teams.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/convert" org_service "code.gitea.io/gitea/services/org" ) @@ -212,6 +213,12 @@ func changeRepoTeam(ctx *context.APIContext, add bool) { return } + if add { + audit.Record(audit.RepositoryCollaboratorTeamAdd, ctx.Doer, ctx.Repo.Repository, team, "Added team %s as collaborator for %s.", team.Name, ctx.Repo.Repository.FullName()) + } else { + audit.Record(audit.RepositoryCollaboratorTeamRemove, ctx.Doer, ctx.Repo.Repository, team, "Removed team %s as collaborator from %s.", team.Name, ctx.Repo.Repository.FullName()) + } + ctx.Status(http.StatusNoContent) } diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go index ded8edd41c067..42f98a4a610a8 100644 --- a/routers/api/v1/repo/transfer.go +++ b/routers/api/v1/repo/transfer.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/convert" repo_service "code.gitea.io/gitea/services/repository" ) @@ -229,5 +230,11 @@ func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error { return repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams) } - return models.CancelRepositoryTransfer(ctx.Repo.Repository) + if err := models.CancelRepositoryTransfer(ctx.Repo.Repository); err != nil { + return err + } + + audit.Record(audit.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected repository transfer.") + + return nil } diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index f89d53945fa0b..f9f94cd1636c2 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -12,10 +12,13 @@ import ( "strings" auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/utils" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/convert" ) @@ -124,6 +127,9 @@ func CreateAccessToken(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "NewAccessToken", err) return } + + audit.Record(audit.UserAccessTokenAdd, ctx.Doer, ctx.Doer, t, "Added access token %s for user %s with scope %s.", t.Name, ctx.Doer.Name, t.Scope) + ctx.JSON(http.StatusCreated, &api.AccessToken{ Name: t.Name, Token: t.Token, @@ -161,6 +167,7 @@ func DeleteAccessToken(ctx *context.APIContext) { token := ctx.Params(":id") tokenID, _ := strconv.ParseInt(token, 0, 64) + var t *auth_model.AccessToken if tokenID == 0 { tokens, err := auth_model.ListAccessTokens(auth_model.ListAccessTokensOptions{ Name: token, @@ -176,18 +183,24 @@ func DeleteAccessToken(ctx *context.APIContext) { ctx.NotFound() return case 1: - tokenID = tokens[0].ID + t = tokens[0] default: ctx.Error(http.StatusUnprocessableEntity, "DeleteAccessTokenByID", fmt.Errorf("multiple matches for token name '%s'", token)) return } + } else { + _, err := db.GetBeanByID(ctx, tokenID, &t) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetBeanByID", err) + return + } } - if tokenID == 0 { - ctx.Error(http.StatusInternalServerError, "Invalid TokenID", nil) + if t == nil { + ctx.NotFound() return } - if err := auth_model.DeleteAccessTokenByID(tokenID, ctx.Doer.ID); err != nil { + if err := auth_model.DeleteAccessTokenByID(t.ID, ctx.Doer.ID); err != nil { if auth_model.IsErrAccessTokenNotExist(err) { ctx.NotFound() } else { @@ -196,6 +209,8 @@ func DeleteAccessToken(ctx *context.APIContext) { return } + audit.Record(audit.UserAccessTokenRemove, ctx.Doer, ctx.Doer, t, "Removed access token %s from user %s.", t.Name, ctx.Doer.Name) + ctx.Status(http.StatusNoContent) } @@ -237,6 +252,8 @@ func CreateOauth2Application(ctx *context.APIContext) { } app.ClientSecret = secret + audit.Record(audit.UserOAuth2ApplicationAdd, ctx.Doer, ctx.Doer, app, "Created OAuth2 application %s.", app.Name) + ctx.JSON(http.StatusCreated, convert.ToOAuth2Application(app)) } @@ -295,8 +312,18 @@ func DeleteOauth2Application(ctx *context.APIContext) { // "$ref": "#/responses/empty" // "404": // "$ref": "#/responses/notFound" - appID := ctx.ParamsInt64(":id") - if err := auth_model.DeleteOAuth2Application(appID, ctx.Doer.ID); err != nil { + + app, err := auth_model.GetOAuth2ApplicationByID(ctx, ctx.ParamsInt64("id")) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.NotFound() + } else { + ctx.Error(http.StatusInternalServerError, "GetOAuth2ApplicationByID", err) + } + return + } + + if err := auth_model.DeleteOAuth2Application(app.ID, ctx.Doer.ID); err != nil { if auth_model.IsErrOAuthApplicationNotFound(err) { ctx.NotFound() } else { @@ -305,6 +332,8 @@ func DeleteOauth2Application(ctx *context.APIContext) { return } + audit.Record(audit.UserOAuth2ApplicationRemove, ctx.Doer, ctx.Doer, app, "Removed OAuth2 application %s.", app.Name) + ctx.Status(http.StatusNoContent) } @@ -392,5 +421,7 @@ func UpdateOauth2Application(ctx *context.APIContext) { return } + audit.Record(audit.UserOAuth2ApplicationUpdate, ctx.Doer, ctx.Doer, app, "Updated OAuth2 application %s.", app.Name) + ctx.JSON(http.StatusOK, convert.ToOAuth2Application(app)) } diff --git a/routers/init.go b/routers/init.go index 8cf53fc108de0..50dd01116db90 100644 --- a/routers/init.go +++ b/routers/init.go @@ -37,6 +37,7 @@ import ( "code.gitea.io/gitea/routers/private" web_routers "code.gitea.io/gitea/routers/web" actions_service "code.gitea.io/gitea/services/actions" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/automerge" @@ -176,6 +177,8 @@ func GlobalInitInstalled(ctx context.Context) { actions_service.Init() + audit.Init() + // Finally start up the cron cron.NewContext(ctx) } diff --git a/routers/install/install.go b/routers/install/install.go index 8e2d19c73271e..20e4a27148caa 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -33,6 +33,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/forms" "gitea.com/go-chi/session" @@ -558,6 +559,8 @@ func SubmitInstall(ctx *context.Context) { u, _ = user_model.GetUserByName(ctx, u.Name) } + audit.Record(audit.UserCreate, u, u, u, "Created user %s.", u.Name) + days := 86400 * setting.LogInRememberDays ctx.SetCookie(setting.CookieUserName, u.Name, days) diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index cfe20be106cf7..0303bb288a164 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -19,6 +19,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/audit" repo_service "code.gitea.io/gitea/services/repository" ) @@ -102,6 +103,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { wasEmpty = repo.IsEmpty } + oldIsPrivate := repo.IsPrivate repo.IsPrivate = opts.GitPushOptions.Bool(private.GitPushOptionRepoPrivate, repo.IsPrivate) repo.IsTemplate = opts.GitPushOptions.Bool(private.GitPushOptionRepoTemplate, repo.IsTemplate) if err := repo_model.UpdateRepositoryCols(ctx, repo, "is_private", "is_template"); err != nil { @@ -110,6 +112,10 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err), }) } + + if oldIsPrivate != repo.IsPrivate { + audit.Record(audit.RepositoryVisibility, ctx.Doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) + } } results := make([]private.HookPostReceiveBranchResult, 0, len(opts.OldCommitIDs)) diff --git a/routers/web/admin/applications.go b/routers/web/admin/applications.go index 745d17ff2a442..a83755cd6ca11 100644 --- a/routers/web/admin/applications.go +++ b/routers/web/admin/applications.go @@ -8,6 +8,7 @@ import ( "net/http" "code.gitea.io/gitea/models/auth" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" @@ -19,9 +20,10 @@ var ( tplSettingsOauth2ApplicationEdit base.TplName = "admin/applications/oauth2_edit" ) -func newOAuth2CommonHandlers() *user_setting.OAuth2CommonHandlers { +func newOAuth2CommonHandlers(doer *user_model.User) *user_setting.OAuth2CommonHandlers { return &user_setting.OAuth2CommonHandlers{ - OwnerID: 0, + Doer: doer, + Owner: nil, BasePathList: fmt.Sprintf("%s/admin/applications", setting.AppSubURL), BasePathEditPrefix: fmt.Sprintf("%s/admin/applications/oauth2", setting.AppSubURL), TplAppEdit: tplSettingsOauth2ApplicationEdit, @@ -50,7 +52,7 @@ func ApplicationsPost(ctx *context.Context) { ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminApplications"] = true - oa := newOAuth2CommonHandlers() + oa := newOAuth2CommonHandlers(ctx.Doer) oa.AddApp(ctx) } @@ -59,7 +61,7 @@ func EditApplication(ctx *context.Context) { ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminApplications"] = true - oa := newOAuth2CommonHandlers() + oa := newOAuth2CommonHandlers(ctx.Doer) oa.EditShow(ctx) } @@ -69,7 +71,7 @@ func EditApplicationPost(ctx *context.Context) { ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminApplications"] = true - oa := newOAuth2CommonHandlers() + oa := newOAuth2CommonHandlers(ctx.Doer) oa.EditSave(ctx) } @@ -79,13 +81,13 @@ func ApplicationsRegenerateSecret(ctx *context.Context) { ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdminApplications"] = true - oa := newOAuth2CommonHandlers() + oa := newOAuth2CommonHandlers(ctx.Doer) oa.RegenerateSecret(ctx) } // DeleteApplication deletes the given oauth2 application func DeleteApplication(ctx *context.Context) { - oa := newOAuth2CommonHandlers() + oa := newOAuth2CommonHandlers(ctx.Doer) oa.DeleteApp(ctx) } diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 6ebd23b7bbd59..5cde0a01d57f2 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/audit" auth_service "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/ldap" "code.gitea.io/gitea/services/auth/source/oauth2" @@ -304,13 +305,15 @@ func NewAuthSourcePost(ctx *context.Context) { return } - if err := auth.CreateSource(&auth.Source{ + source := &auth.Source{ Type: auth.Type(form.Type), Name: form.Name, IsActive: form.IsActive, IsSyncEnabled: form.IsSyncEnabled, Cfg: config, - }); err != nil { + } + + if err := auth.CreateSource(source); err != nil { if auth.IsErrSourceAlreadyExist(err) { ctx.Data["Err_Name"] = true ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_exist", err.(auth.ErrSourceAlreadyExist).Name), tplAuthNew, form) @@ -324,6 +327,8 @@ func NewAuthSourcePost(ctx *context.Context) { return } + audit.Record(audit.SystemAuthenticationSourceAdd, ctx.Doer, nil, source, "Created authentication source %s [%s].", source.Name, source.Type.String()) + log.Trace("Authentication created by admin(%s): %s", ctx.Doer.Name, form.Name) ctx.Flash.Success(ctx.Tr("admin.auths.new_success", form.Name)) @@ -439,6 +444,9 @@ func EditAuthSourcePost(ctx *context.Context) { } return } + + audit.Record(audit.SystemAuthenticationSourceUpdate, nil, ctx.Doer, source, "Updated authentication source %s.", source.Name) + log.Trace("Authentication changed by admin(%s): %d", ctx.Doer.Name, source.ID) ctx.Flash.Success(ctx.Tr("admin.auths.update_success")) @@ -464,6 +472,9 @@ func DeleteAuthSource(ctx *context.Context) { }) return } + + audit.Record(audit.SystemAuthenticationSourceRemove, nil, ctx.Doer, source, "Removed authentication source %s.", source.Name) + log.Trace("Authentication deleted by admin(%s): %d", ctx.Doer.Name, source.ID) ctx.Flash.Success(ctx.Tr("admin.auths.deletion_success")) diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go index c16158c6ae47f..dd92447ca584b 100644 --- a/routers/web/admin/emails.go +++ b/routers/web/admin/emails.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/audit" ) const ( @@ -122,16 +123,23 @@ func ActivateEmail(ctx *context.Context) { log.Info("Changing activation for User ID: %d, email: %s, primary: %v to %v", uid, email, primary, activate) - if err := user_model.ActivateUserEmail(uid, email, activate); err != nil { - log.Error("ActivateUserEmail(%v,%v,%v): %v", uid, email, activate, err) - if user_model.IsErrEmailAlreadyUsed(err) { - ctx.Flash.Error(ctx.Tr("admin.emails.duplicate_active")) + u, err := user_model.GetUserByID(ctx, uid) + if err != nil { + ctx.Flash.Error(ctx.Tr("admin.emails.not_updated", err)) + } else { + if err := user_model.ActivateUserEmail(uid, email, activate); err != nil { + log.Error("ActivateUserEmail(%v,%v,%v): %v", uid, email, activate, err) + if user_model.IsErrEmailAlreadyUsed(err) { + ctx.Flash.Error(ctx.Tr("admin.emails.duplicate_active")) + } else { + ctx.Flash.Error(ctx.Tr("admin.emails.not_updated", err)) + } } else { - ctx.Flash.Error(ctx.Tr("admin.emails.not_updated", err)) + audit.Record(audit.UserEmailActivate, ctx.Doer, u, u, "Email %s of user %s activated.", email, u.Name) + + log.Info("Activation for User ID: %d, email: %s, primary: %v changed to %v", uid, email, primary, activate) + ctx.Flash.Info(ctx.Tr("admin.emails.updated")) } - } else { - log.Info("Activation for User ID: %d, email: %s, primary: %v changed to %v", uid, email, primary, activate) - ctx.Flash.Info(ctx.Tr("admin.emails.updated")) } redirect, _ := url.Parse(setting.AppSubURL + "/admin/emails") diff --git a/routers/web/admin/hooks.go b/routers/web/admin/hooks.go index 46dc734d252fa..5ac33c312da51 100644 --- a/routers/web/admin/hooks.go +++ b/routers/web/admin/hooks.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/audit" ) const ( @@ -61,13 +62,21 @@ func DefaultOrSystemWebhooks(ctx *context.Context) { // DeleteDefaultOrSystemWebhook handler to delete an admin-defined system or default webhook func DeleteDefaultOrSystemWebhook(ctx *context.Context) { + defer ctx.JSON(http.StatusOK, map[string]interface{}{ + "redirect": setting.AppSubURL + "/admin/hooks", + }) + + hook, err := webhook.GetSystemOrDefaultWebhook(ctx, ctx.FormInt64("id")) + if err != nil { + ctx.Flash.Error("GetWebhookByOwnerID: " + err.Error()) + return + } + if err := webhook.DeleteDefaultSystemWebhook(ctx, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteDefaultWebhook: " + err.Error()) } else { + audit.Record(audit.SystemWebhookRemove, ctx.Doer, nil, hook, "Removed webhook %s.", hook.URL) + ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } - - ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": setting.AppSubURL + "/admin/hooks", - }) } diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 531f14d08627e..3ddbe31cd3428 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -19,10 +19,12 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/web/explore" user_setting "code.gitea.io/gitea/routers/web/user/setting" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/mailer" user_service "code.gitea.io/gitea/services/user" @@ -195,6 +197,9 @@ func NewUserPost(ctx *context.Context) { } return } + + audit.Record(audit.UserCreate, ctx.Doer, u, u, "Created user %s.", u.Name) + log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name) // Send email notification. @@ -286,6 +291,23 @@ func EditUserPost(ctx *context.Context) { return } + auditFields := struct { + LoginSource int64 + // Password + Email string + IsActive bool + IsAdmin bool + IsRestricted bool + Visibility structs.VisibleType + }{ + LoginSource: u.LoginSource, + Email: u.Email, + IsActive: u.IsActive, + IsAdmin: u.IsAdmin, + IsRestricted: u.IsRestricted, + Visibility: u.Visibility, + } + fields := strings.Split(form.LoginType, "-") if len(fields) == 2 { loginType, _ := strconv.ParseInt(fields[0], 10, 0) @@ -297,6 +319,7 @@ func EditUserPost(ctx *context.Context) { } } + passwordChanged := false if len(form.Password) > 0 && (u.IsLocal() || u.IsOAuth2()) { var err error if len(form.Password) < setting.MinPasswordLength { @@ -334,10 +357,12 @@ func EditUserPost(ctx *context.Context) { ctx.ServerError("SetPassword", err) return } + + passwordChanged = true } if len(form.UserName) != 0 && u.Name != form.UserName { - if err := user_setting.HandleUsernameChange(ctx, u, form.UserName); err != nil { + if err := user_setting.HandleUsernameChange(ctx, ctx.Doer, u, form.UserName); err != nil { if ctx.Written() { return } @@ -410,6 +435,26 @@ func EditUserPost(ctx *context.Context) { } return } + + if passwordChanged { + audit.Record(audit.UserPassword, ctx.Doer, u, u, "Password of user %s changed.", u.Name) + } + if auditFields.LoginSource != u.LoginSource { + audit.Record(audit.UserAuthenticationSource, ctx.Doer, u, u, "Authentication source of user %s changed.", u.Name) + } + if auditFields.Visibility != u.Visibility { + audit.Record(audit.UserVisibility, ctx.Doer, u, u, "Visibility of user %s changed from %s to %s.", u.Name, auditFields.Visibility.String(), u.Visibility.String()) + } + if auditFields.IsActive != u.IsActive { + audit.Record(audit.UserActive, ctx.Doer, u, u, "Activation status of user %s changed to %s.", u.Name, audit.UserActiveString(u.IsActive)) + } + if auditFields.IsAdmin != u.IsAdmin { + audit.Record(audit.UserAdmin, ctx.Doer, u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) + } + if auditFields.IsRestricted != u.IsRestricted { + audit.Record(audit.UserRestricted, ctx.Doer, u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) + } + log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, u.Name) ctx.Flash.Success(ctx.Tr("admin.users.update_profile_success")) @@ -431,7 +476,7 @@ func DeleteUser(ctx *context.Context) { return } - if err = user_service.DeleteUser(ctx, u, ctx.FormBool("purge")); err != nil { + if err = user_service.DeleteUser(ctx, ctx.Doer, u, ctx.FormBool("purge")); err != nil { switch { case models.IsErrUserOwnRepos(err): ctx.Flash.Error(ctx.Tr("admin.users.still_own_repo")) diff --git a/routers/web/auth/2fa.go b/routers/web/auth/2fa.go index 4791b043131db..f12dadae75601 100644 --- a/routers/web/auth/2fa.go +++ b/routers/web/auth/2fa.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/externalaccount" "code.gitea.io/gitea/services/forms" ) @@ -53,6 +54,13 @@ func TwoFactorPost(ctx *context.Context) { } id := idSess.(int64) + + u, err := user_model.GetUserByID(ctx, id) + if err != nil { + ctx.ServerError("UserSignIn", err) + return + } + twofa, err := auth.GetTwoFactorByUID(id) if err != nil { ctx.ServerError("UserSignIn", err) @@ -68,11 +76,6 @@ func TwoFactorPost(ctx *context.Context) { if ok && twofa.LastUsedPasscode != form.Passcode { remember := ctx.Session.Get("twofaRemember").(bool) - u, err := user_model.GetUserByID(ctx, id) - if err != nil { - ctx.ServerError("UserSignIn", err) - return - } if ctx.Session.Get("linkAccount") != nil { err = externalaccount.LinkAccountFromStore(ctx.Session, u) @@ -92,6 +95,8 @@ func TwoFactorPost(ctx *context.Context) { return } + audit.Record(audit.UserAuthenticationFailTwoFactor, user_model.NewGhostUser(), u, twofa, "Failed two-factor authentication for user %s.", u.Name) + ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplTwofa, forms.TwoFactorAuthForm{}) } diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 5fba632817e62..03224bdada8ed 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -25,6 +25,7 @@ import ( "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/routers/utils" + "code.gitea.io/gitea/services/audit" auth_service "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/externalaccount" @@ -545,6 +546,9 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{ } return } + + audit.Record(audit.UserCreate, audit.NewAuthenticationSourceUser(), u, u, "Created user %s.", u.Name) + log.Trace("Account created: %s", u.Name) return true } @@ -706,6 +710,8 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) { return } + audit.Record(audit.UserActive, user, user, user, "Activation status of user %s changed to %s.", user.Name, audit.UserActiveString(user.IsActive)) + log.Trace("User activated: %s", user.Name) if err := updateSession(ctx, nil, map[string]interface{}{ @@ -739,7 +745,7 @@ func ActivateEmail(ctx *context.Context) { emailStr := ctx.FormString("email") // Verify code. - if email := user_model.VerifyActiveEmailCode(code, emailStr); email != nil { + if user, email := user_model.VerifyActiveEmailCode(code, emailStr); user != nil && email != nil { if err := user_model.ActivateEmail(email); err != nil { ctx.ServerError("ActivateEmail", err) } @@ -747,12 +753,12 @@ func ActivateEmail(ctx *context.Context) { log.Trace("Email activated: %s", email.Email) ctx.Flash.Success(ctx.Tr("settings.add_email_success")) - if u, err := user_model.GetUserByID(ctx, email.UID); err != nil { - log.Warn("GetUserByID: %d", email.UID) - } else if setting.CacheService.Enabled { + if setting.CacheService.Enabled { // Allow user to validate more emails - _ = ctx.Cache.Delete("MailResendLimit_" + u.LowerName) + _ = ctx.Cache.Delete("MailResendLimit_" + user.LowerName) } + + audit.Record(audit.UserEmailActivate, user, user, email, "Email %s of user %s activated.", email.Email, user.Name) } // FIXME: e-mail verification does not require the user to be logged in, diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 7de63dbe94ffe..1dff92c2ffd50 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -28,6 +28,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" + "code.gitea.io/gitea/services/audit" auth_service "code.gitea.io/gitea/services/auth" source_service "code.gitea.io/gitea/services/auth/source" "code.gitea.io/gitea/services/auth/source/oauth2" @@ -1032,7 +1033,7 @@ func getClaimedGroups(source *oauth2.Source, gothUser *goth.User) container.Set[ return claimValueToStringSet(groupClaims) } -func setUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, u *user_model.User, gothUser *goth.User) bool { +func setUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, u *user_model.User, gothUser *goth.User) (bool, bool) { groups := getClaimedGroups(source, gothUser) wasAdmin, wasRestricted := u.IsAdmin, u.IsRestricted @@ -1044,7 +1045,7 @@ func setUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, u *user_mod u.IsRestricted = groups.Contains(source.RestrictedGroup) } - return wasAdmin != u.IsAdmin || wasRestricted != u.IsRestricted + return wasAdmin != u.IsAdmin, wasRestricted != u.IsRestricted } func showLinkingLogin(ctx *context.Context, gothUser goth.User) { @@ -1115,9 +1116,9 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model u.SetLastLogin() // Update GroupClaims - changed := setUserAdminAndRestrictedFromGroupClaims(oauth2Source, u, &gothUser) + changedIsAdmin, changedIsRestricted := setUserAdminAndRestrictedFromGroupClaims(oauth2Source, u, &gothUser) cols := []string{"last_login_unix"} - if changed { + if changedIsAdmin || changedIsRestricted { cols = append(cols, "is_admin", "is_restricted") } @@ -1126,6 +1127,13 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model return } + if changedIsAdmin { + audit.Record(audit.UserAdmin, audit.NewAuthenticationSourceUser(), u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) + } + if changedIsRestricted { + audit.Record(audit.UserRestricted, audit.NewAuthenticationSourceUser(), u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) + } + if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval { if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil { ctx.ServerError("SyncGroupsToTeams", err) @@ -1155,14 +1163,21 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model return } - changed := setUserAdminAndRestrictedFromGroupClaims(oauth2Source, u, &gothUser) - if changed { + changedIsAdmin, changedIsRestricted := setUserAdminAndRestrictedFromGroupClaims(oauth2Source, u, &gothUser) + if changedIsAdmin || changedIsRestricted { if err := user_model.UpdateUserCols(ctx, u, "is_admin", "is_restricted"); err != nil { ctx.ServerError("UpdateUserCols", err) return } } + if changedIsAdmin { + audit.Record(audit.UserAdmin, audit.NewAuthenticationSourceUser(), u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) + } + if changedIsRestricted { + audit.Record(audit.UserRestricted, audit.NewAuthenticationSourceUser(), u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) + } + if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval { if err := source_service.SyncGroupsToTeams(ctx, u, groups, groupTeamMapping, oauth2Source.GroupTeamMapRemoval); err != nil { ctx.ServerError("SyncGroupsToTeams", err) diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go index aff2e5f780857..835c1f7aa5f05 100644 --- a/routers/web/auth/openid.go +++ b/routers/web/auth/openid.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/forms" ) @@ -301,6 +302,8 @@ func ConnectOpenIDPost(ctx *context.Context) { return } + audit.Record(audit.UserOpenIDAdd, u, u, userOID, "Associated OpenID %s to user %s.", userOID.URI, u.Name) + ctx.Flash.Success(ctx.Tr("settings.add_openid_success")) remember, _ := ctx.Session.Get("openid_signin_remember").(bool) @@ -405,6 +408,8 @@ func RegisterOpenIDPost(ctx *context.Context) { return } + audit.Record(audit.UserOpenIDAdd, u, u, userOID, "Associated OpenID %s to user %s.", userOID.URI, u.Name) + remember, _ := ctx.Session.Get("openid_signin_remember").(bool) log.Trace("Session stored openid-remember: %t", remember) handleSignIn(ctx, u, remember) diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go index a5aa9c5344ddb..344432c49878d 100644 --- a/routers/web/auth/password.go +++ b/routers/web/auth/password.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/routers/utils" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/mailer" ) @@ -86,6 +87,8 @@ func ForgotPasswdPost(ctx *context.Context) { mailer.SendResetPasswordMail(u) + audit.Record(audit.UserPasswordReset, u, u, u, "User %s requested a password reset.", u.Name) + if setting.CacheService.Enabled { if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { log.Error("Set cache(MailResendLimit) fail: %v", err) @@ -209,6 +212,8 @@ func ResetPasswdPost(ctx *context.Context) { return } if !ok || twofa.LastUsedPasscode == passcode { + audit.Record(audit.UserAuthenticationFailTwoFactor, u, u, twofa, "Failed two-factor authentication for user %s.", u.Name) + ctx.Data["IsResetForm"] = true ctx.Data["Err_Passcode"] = true ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplResetPassword, nil) @@ -237,6 +242,8 @@ func ResetPasswdPost(ctx *context.Context) { return } + audit.Record(audit.UserPassword, u, u, u, "Password of user %s changed.", u.Name) + log.Trace("User password reset: %s", u.Name) ctx.Data["IsResetFailed"] = true remember := len(ctx.FormString("remember")) != 0 @@ -336,6 +343,8 @@ func MustChangePasswordPost(ctx *context.Context) { log.Trace("User updated password: %s", u.Name) + audit.Record(audit.UserPassword, u, u, u, "Password of user %s changed.", u.Name) + if redirectTo := ctx.GetCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) { middleware.DeleteRedirectToCookie(ctx.Resp) ctx.RedirectToFirst(redirectTo) diff --git a/routers/web/org/org.go b/routers/web/org/org.go index f67e7edb4c7cc..fe613df12e85d 100644 --- a/routers/web/org/org.go +++ b/routers/web/org/org.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/forms" ) @@ -74,6 +75,9 @@ func CreatePost(ctx *context.Context) { } return } + + audit.Record(audit.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) + log.Trace("Organization created: %s", org.Name) ctx.Redirect(org.AsUser().DashboardLink()) diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 7d84c101d8a60..638e897fb5bc8 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -21,6 +21,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" user_setting "code.gitea.io/gitea/routers/web/user/setting" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/org" container_service "code.gitea.io/gitea/services/packages/container" @@ -67,6 +68,8 @@ func SettingsPost(ctx *context.Context) { } org := ctx.Org.Organization + + oldName := org.Name nameChanged := org.Name != form.Name // Check if organization name has been changed. @@ -118,7 +121,7 @@ func SettingsPost(ctx *context.Context) { org.Location = form.Location org.RepoAdminChangeTeamAccess = form.RepoAdminChangeTeamAccess - visibilityChanged := form.Visibility != org.Visibility + oldVisibility := org.Visibility org.Visibility = form.Visibility if err := user_model.UpdateUser(ctx, org.AsUser(), false); err != nil { @@ -127,7 +130,7 @@ func SettingsPost(ctx *context.Context) { } // update forks visibility - if visibilityChanged { + if org.Visibility != oldVisibility { repos, _, err := repo_model.GetUserRepositories(&repo_model.SearchRepoOptions{ Actor: org.AsUser(), Private: true, ListOptions: db.ListOptions{Page: 1, PageSize: org.NumRepos}, }) @@ -149,6 +152,14 @@ func SettingsPost(ctx *context.Context) { } } + audit.Record(audit.OrganizationUpdate, ctx.Doer, org, org, "Updated settings of organization %s.", org.Name) + if nameChanged { + audit.Record(audit.OrganizationName, ctx.Doer, org, org, "Organization name changed from %s to %s.", oldName, org.Name) + } + if org.Visibility != oldVisibility { + audit.Record(audit.OrganizationVisibility, ctx.Doer, org, org, "Visibility of organization %s changed from %s to %s.", org.Name, oldVisibility.String(), org.Visibility.String()) + } + log.Trace("Organization setting updated: %s", org.Name) ctx.Flash.Success(ctx.Tr("org.settings.update_setting_success")) ctx.Redirect(ctx.Org.OrgLink + "/settings") @@ -189,7 +200,7 @@ func SettingsDelete(ctx *context.Context) { return } - if err := org.DeleteOrganization(ctx.Org.Organization); err != nil { + if err := org.DeleteOrganization(ctx.Doer, ctx.Org.Organization); err != nil { if models.IsErrUserOwnRepos(err) { ctx.Flash.Error(ctx.Tr("form.org_still_own_repo")) ctx.Redirect(ctx.Org.OrgLink + "/settings/delete") @@ -230,15 +241,23 @@ func Webhooks(ctx *context.Context) { // DeleteWebhook response for delete webhook func DeleteWebhook(ctx *context.Context) { + defer ctx.JSON(http.StatusOK, map[string]interface{}{ + "redirect": ctx.Org.OrgLink + "/settings/hooks", + }) + + hook, err := webhook.GetWebhookByOwnerID(ctx.Org.Organization.ID, ctx.FormInt64("id")) + if err != nil { + ctx.Flash.Error("GetWebhookByOwnerID: " + err.Error()) + return + } + if err := webhook.DeleteWebhookByOwnerID(ctx.Org.Organization.ID, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error()) } else { + audit.Record(audit.OrganizationWebhookRemove, ctx.Doer, ctx.Org.Organization, hook, "Removed webhook %s.", hook.URL) + ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } - - ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": ctx.Org.OrgLink + "/settings/hooks", - }) } // Labels render organization labels page diff --git a/routers/web/org/setting_oauth2.go b/routers/web/org/setting_oauth2.go index 9bf4280b07e53..04ca5670ae12a 100644 --- a/routers/web/org/setting_oauth2.go +++ b/routers/web/org/setting_oauth2.go @@ -8,6 +8,8 @@ import ( "net/http" "code.gitea.io/gitea/models/auth" + organization_model "code.gitea.io/gitea/models/organization" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" @@ -19,11 +21,12 @@ const ( tplSettingsOAuthApplicationEdit base.TplName = "org/settings/applications_oauth2_edit" ) -func newOAuth2CommonHandlers(org *context.Organization) *user_setting.OAuth2CommonHandlers { +func newOAuth2CommonHandlers(doer *user_model.User, org *organization_model.Organization) *user_setting.OAuth2CommonHandlers { return &user_setting.OAuth2CommonHandlers{ - OwnerID: org.Organization.ID, - BasePathList: fmt.Sprintf("%s/org/%s/settings/applications", setting.AppSubURL, org.Organization.Name), - BasePathEditPrefix: fmt.Sprintf("%s/org/%s/settings/applications/oauth2", setting.AppSubURL, org.Organization.Name), + Doer: doer, + Owner: organization_model.UserFromOrg(org), + BasePathList: fmt.Sprintf("%s/org/%s/settings/applications", setting.AppSubURL, org.Name), + BasePathEditPrefix: fmt.Sprintf("%s/org/%s/settings/applications/oauth2", setting.AppSubURL, org.Name), TplAppEdit: tplSettingsOAuthApplicationEdit, } } @@ -50,7 +53,7 @@ func OAuthApplicationsPost(ctx *context.Context) { ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsSettingsApplications"] = true - oa := newOAuth2CommonHandlers(ctx.Org) + oa := newOAuth2CommonHandlers(ctx.Doer, ctx.Org.Organization) oa.AddApp(ctx) } @@ -59,7 +62,7 @@ func OAuth2ApplicationShow(ctx *context.Context) { ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsSettingsApplications"] = true - oa := newOAuth2CommonHandlers(ctx.Org) + oa := newOAuth2CommonHandlers(ctx.Doer, ctx.Org.Organization) oa.EditShow(ctx) } @@ -69,7 +72,7 @@ func OAuth2ApplicationEdit(ctx *context.Context) { ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsSettingsApplications"] = true - oa := newOAuth2CommonHandlers(ctx.Org) + oa := newOAuth2CommonHandlers(ctx.Doer, ctx.Org.Organization) oa.EditSave(ctx) } @@ -79,13 +82,13 @@ func OAuthApplicationsRegenerateSecret(ctx *context.Context) { ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsSettingsApplications"] = true - oa := newOAuth2CommonHandlers(ctx.Org) + oa := newOAuth2CommonHandlers(ctx.Doer, ctx.Org.Organization) oa.RegenerateSecret(ctx) } // DeleteOAuth2Application deletes the given oauth2 application func DeleteOAuth2Application(ctx *context.Context) { - oa := newOAuth2CommonHandlers(ctx.Org) + oa := newOAuth2CommonHandlers(ctx.Doer, ctx.Org.Organization) oa.DeleteApp(ctx) } diff --git a/routers/web/org/setting_secrets.go b/routers/web/org/setting_secrets.go index 1cdbe35f32ade..aa1b3bfaafe40 100644 --- a/routers/web/org/setting_secrets.go +++ b/routers/web/org/setting_secrets.go @@ -33,8 +33,9 @@ func Secrets(ctx *context.Context) { func SecretsPost(ctx *context.Context) { shared.PerformSecretsPost( ctx, - ctx.ContextUser.ID, - 0, + ctx.Doer, + ctx.ContextUser, + nil, ctx.Org.OrgLink+"/settings/secrets", ) } @@ -43,6 +44,9 @@ func SecretsPost(ctx *context.Context) { func SecretsDelete(ctx *context.Context) { shared.PerformSecretsDelete( ctx, + ctx.Doer, + ctx.ContextUser, + nil, ctx.Org.OrgLink+"/settings/secrets", ) } diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index 1ed7980145491..6ef1a75d99fd8 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -24,6 +24,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/utils" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/forms" org_service "code.gitea.io/gitea/services/org" @@ -70,6 +71,9 @@ func TeamsAction(ctx *context.Context) { return } err = models.AddTeamMember(ctx.Org.Team, ctx.Doer.ID) + if err == nil { + audit.Record(audit.OrganizationTeamMemberAdd, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was added to team %s/%s.", ctx.Doer.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + } case "leave": err = models.RemoveTeamMember(ctx.Org.Team, ctx.Doer.ID) if err != nil { @@ -83,6 +87,8 @@ func TeamsAction(ctx *context.Context) { }) return } + } else { + audit.Record(audit.OrganizationTeamMemberRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was removed from team %s/%s.", ctx.Doer.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) } ctx.JSON(http.StatusOK, map[string]interface{}{ @@ -95,13 +101,13 @@ func TeamsAction(ctx *context.Context) { return } - uid := ctx.FormInt64("uid") - if uid == 0 { + u, err := user_model.GetUserByID(ctx, ctx.FormInt64("uid")) + if err != nil { ctx.Redirect(ctx.Org.OrgLink + "/teams") return } - err = models.RemoveTeamMember(ctx.Org.Team, uid) + err = models.RemoveTeamMember(ctx.Org.Team, u.ID) if err != nil { if org_model.IsErrLastOrgOwner(err) { ctx.Flash.Error(ctx.Tr("form.last_org_owner")) @@ -113,7 +119,10 @@ func TeamsAction(ctx *context.Context) { }) return } + } else { + audit.Record(audit.OrganizationTeamMemberRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was removed from team %s/%s.", u.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) } + ctx.JSON(http.StatusOK, map[string]interface{}{ "redirect": ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName), @@ -160,6 +169,9 @@ func TeamsAction(ctx *context.Context) { ctx.Flash.Error(ctx.Tr("org.teams.add_duplicate_users")) } else { err = models.AddTeamMember(ctx.Org.Team, u.ID) + if err == nil { + audit.Record(audit.OrganizationTeamMemberAdd, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was added to team %s/%s.", u.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + } } page = "team" @@ -214,13 +226,10 @@ func TeamsRepoAction(ctx *context.Context) { return } - var err error action := ctx.Params(":action") switch action { case "add": - repoName := path.Base(ctx.FormString("repo_name")) - var repo *repo_model.Repository - repo, err = repo_model.GetRepositoryByName(ctx.Org.Organization.ID, repoName) + repo, err := repo_model.GetRepositoryByName(ctx.Org.Organization.ID, path.Base(ctx.FormString("repo_name"))) if err != nil { if repo_model.IsErrRepoNotExist(err) { ctx.Flash.Error(ctx.Tr("org.teams.add_nonexistent_repo")) @@ -230,19 +239,49 @@ func TeamsRepoAction(ctx *context.Context) { ctx.ServerError("GetRepositoryByName", err) return } - err = org_service.TeamAddRepository(ctx.Org.Team, repo) + if err := org_service.TeamAddRepository(ctx.Org.Team, repo); err != nil { + ctx.ServerError("TeamAddRepository "+ctx.Org.Team.Name, err) + return + } + + audit.Record(audit.RepositoryCollaboratorTeamAdd, ctx.Doer, repo, ctx.Org.Team, "Added team %s as collaborator for %s.", ctx.Org.Team.Name, repo.FullName()) case "remove": - err = models.RemoveRepository(ctx.Org.Team, ctx.FormInt64("repoid")) + repo, err := repo_model.GetRepositoryByID(db.DefaultContext, ctx.FormInt64("repoid")) + if err != nil { + ctx.ServerError("GetRepositoryByID", err) + return + } + + if err := models.RemoveRepository(ctx.Org.Team, repo.ID); err != nil { + ctx.ServerError("RemoveRepository "+ctx.Org.Team.Name, err) + return + } + + audit.Record(audit.RepositoryCollaboratorTeamRemove, ctx.Doer, repo, ctx.Org.Team, "Removed team %s as collaborator from %s.", ctx.Org.Team.Name, repo.FullName()) case "addall": - err = models.AddAllRepositories(ctx.Org.Team) + added, err := models.AddAllRepositories(ctx.Org.Team) + if err != nil { + ctx.ServerError("AddAllRepositories "+ctx.Org.Team.Name, err) + return + } + + for _, repo := range added { + audit.Record(audit.RepositoryCollaboratorTeamAdd, ctx.Doer, repo, ctx.Org.Team, "Added team %s as collaborator for %s.", ctx.Org.Team.Name, repo.FullName()) + } case "removeall": - err = models.RemoveAllRepositories(ctx.Org.Team) - } + if err := ctx.Org.Team.LoadRepositories(ctx); err != nil { + ctx.ServerError("LoadRepositories "+ctx.Org.Team.Name, err) + return + } - if err != nil { - log.Error("Action(%s): '%s' %v", ctx.Params(":action"), ctx.Org.Team.Name, err) - ctx.ServerError("TeamsRepoAction", err) - return + if err := models.RemoveAllRepositories(ctx.Org.Team); err != nil { + ctx.ServerError("RemoveAllRepositories "+ctx.Org.Team.Name, err) + return + } + + for _, repo := range ctx.Org.Team.Repos { + audit.Record(audit.RepositoryCollaboratorTeamRemove, ctx.Doer, repo, ctx.Org.Team, "Removed team %s as collaborator from %s.", ctx.Org.Team.Name, repo.FullName()) + } } if action == "addall" || action == "removeall" { @@ -337,6 +376,9 @@ func NewTeamPost(ctx *context.Context) { } return } + + audit.Record(audit.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, t, "Team %s was added to organziation %s.", t.Name, ctx.Org.Organization.Name) + log.Trace("Team created: %s/%s", ctx.Org.Organization.Name, t.Name) ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(t.LowerName)) } @@ -442,6 +484,7 @@ func EditTeamPost(ctx *context.Context) { ctx.Data["Team"] = t ctx.Data["Units"] = unit_model.Units + oldAccessMode := t.AccessMode if !t.IsOwnerTeam() { // Validate permission level. newAccessMode := perm.ParseAccessMode(form.Permission) @@ -503,6 +546,12 @@ func EditTeamPost(ctx *context.Context) { } return } + + audit.Record(audit.OrganizationTeamUpdate, ctx.Doer, ctx.Org.Organization, t, "Updated settings of team %s/%s.", ctx.Org.Organization.Name, t.Name) + if isAuthChanged { + audit.Record(audit.OrganizationTeamPermission, ctx.Doer, ctx.Org.Organization, t, "Permission of team %s/%s changed from %s to %s.", ctx.Org.Organization.Name, t.Name, oldAccessMode.String(), t.AccessMode.String()) + } + ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(t.LowerName)) } @@ -511,6 +560,8 @@ func DeleteTeam(ctx *context.Context) { if err := models.DeleteTeam(ctx.Org.Team); err != nil { ctx.Flash.Error("DeleteTeam: " + err.Error()) } else { + audit.Record(audit.OrganizationTeamRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Team %s was removed from organziation %s.", ctx.Org.Team.Name, ctx.Org.Organization.Name) + ctx.Flash.Success(ctx.Tr("org.teams.delete_team_success")) } @@ -557,6 +608,8 @@ func TeamInvitePost(ctx *context.Context) { return } + audit.Record(audit.OrganizationTeamMemberAdd, ctx.Doer, org, team, "User %s was added to team %s/%s.", ctx.Doer.Name, org.Name, team.Name) + if err := org_model.RemoveInviteByID(ctx, invite.ID, team.ID); err != nil { log.Error("RemoveInviteByID: %v", err) } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index b4e7b5a46e2a7..e124fb9a58585 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -26,6 +26,7 @@ import ( api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/forms" repo_service "code.gitea.io/gitea/services/repository" @@ -341,6 +342,9 @@ func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) error { if err := models.CancelRepositoryTransfer(ctx.Repo.Repository); err != nil { return err } + + audit.Record(audit.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected repository transfer.") + ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected")) } diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go index 0c36503b3c4fc..358276a397076 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -38,6 +38,7 @@ import ( "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/utils" asymkey_service "code.gitea.io/gitea/services/asymkey" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/mailer" "code.gitea.io/gitea/services/migrations" @@ -187,6 +188,12 @@ func SettingsPost(ctx *context.Context) { ctx.ServerError("UpdateRepository", err) return } + + audit.Record(audit.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) + if visibilityChanged { + audit.Record(audit.RepositoryVisibility, ctx.Doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) + } + log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) @@ -323,6 +330,8 @@ func SettingsPost(ctx *context.Context) { return } + audit.Record(audit.RepositoryMirrorPushRemove, ctx.Doer, repo, m, "Removed push mirror for repository %s.", repo.FullName()) + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(repo.Link() + "/settings") @@ -379,6 +388,8 @@ func SettingsPost(ctx *context.Context) { return } + audit.Record(audit.RepositoryMirrorPushAdd, ctx.Doer, repo, m, "Added push mirror for repository %s.", repo.FullName()) + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(repo.Link() + "/settings") @@ -546,6 +557,9 @@ func SettingsPost(ctx *context.Context) { return } } + + audit.Record(audit.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) + log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) @@ -564,6 +578,8 @@ func SettingsPost(ctx *context.Context) { ctx.ServerError("UpdateRepository", err) return } + + audit.Record(audit.RepositorySigningVerification, ctx.Doer, repo, repo, "Changed signing verification of repository %s to %s.", repo.FullName(), repo.TrustModel.String()) } log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) @@ -641,6 +657,9 @@ func SettingsPost(ctx *context.Context) { ctx.ServerError("DeleteMirrorByRepoID", err) return } + + audit.Record(audit.RepositoryConvertMirror, ctx.Doer, repo, repo, "Converted repository %s from mirror to regular repository.", repo.FullName()) + log.Trace("Repository converted from mirror to regular: %s", repo.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.convert_succeed")) ctx.Redirect(repo.Link()) @@ -672,7 +691,7 @@ func SettingsPost(ctx *context.Context) { return } - if err := repo_service.ConvertForkToNormalRepository(ctx, repo); err != nil { + if err := repo_service.ConvertForkToNormalRepository(ctx, ctx.Doer, repo); err != nil { log.Error("Unable to convert repository %-v from fork. Error: %v", repo, err) ctx.ServerError("Convert Fork", err) return @@ -722,7 +741,7 @@ func SettingsPost(ctx *context.Context) { } else if models.IsErrRepoTransferInProgress(err) { ctx.RenderWithErr(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil) } else { - ctx.ServerError("TransferOwnership", err) + ctx.ServerError("StartRepositoryTransfer", err) } return @@ -760,6 +779,8 @@ func SettingsPost(ctx *context.Context) { return } + audit.Record(audit.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected transfer of repository %s.", ctx.Repo.Repository.FullName()) + log.Trace("Repository transfer process was cancelled: %s/%s ", ctx.Repo.Owner.Name, repo.Name) ctx.Flash.Success(ctx.Tr("repo.settings.transfer_abort_success", repoTransfer.Recipient.Name)) ctx.Redirect(repo.Link() + "/settings") @@ -798,10 +819,11 @@ func SettingsPost(ctx *context.Context) { return } - err := wiki_service.DeleteWiki(ctx, repo) - if err != nil { - log.Error("Delete Wiki: %v", err.Error()) + if err := wiki_service.DeleteWiki(ctx, ctx.Doer, repo); err != nil { + ctx.ServerError("DeleteWiki", err) + return } + log.Trace("Repository wiki deleted: %s/%s", ctx.Repo.Owner.Name, repo.Name) ctx.Flash.Success(ctx.Tr("repo.settings.wiki_deletion_success")) @@ -826,6 +848,8 @@ func SettingsPost(ctx *context.Context) { return } + audit.Record(audit.RepositoryArchive, ctx.Doer, repo, repo, "Archived repository %s.", repo.FullName()) + ctx.Flash.Success(ctx.Tr("repo.settings.archive.success")) log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) @@ -844,6 +868,8 @@ func SettingsPost(ctx *context.Context) { return } + audit.Record(audit.RepositoryUnarchive, ctx.Doer, repo, repo, "Unarchived repository %s.", repo.FullName()) + ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success")) log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name) @@ -965,32 +991,51 @@ func CollaborationPost(ctx *context.Context) { mailer.SendCollaboratorMail(u, ctx.Doer, ctx.Repo.Repository) } + audit.Record(audit.RepositoryCollaboratorAdd, ctx.Doer, ctx.Repo.Repository, u, "Added user %s as collaborator.", u.Name) + ctx.Flash.Success(ctx.Tr("repo.settings.add_collaborator_success")) ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) } // ChangeCollaborationAccessMode response for changing access of a collaboration func ChangeCollaborationAccessMode(ctx *context.Context) { + u, err := user_model.GetUserByID(ctx, ctx.FormInt64("uid")) + if err != nil { + log.Error("GetUserByID: %v", err) + return + } + if err := repo_model.ChangeCollaborationAccessMode( ctx, ctx.Repo.Repository, ctx.FormInt64("uid"), perm.AccessMode(ctx.FormInt("mode"))); err != nil { log.Error("ChangeCollaborationAccessMode: %v", err) + return } + + audit.Record(audit.RepositoryCollaboratorAccess, ctx.Doer, ctx.Repo.Repository, u, "Changed access mode of collaborator %s to %s.", u.Name, perm.AccessMode(ctx.FormInt("mode")).String()) } // DeleteCollaboration delete a collaboration for a repository func DeleteCollaboration(ctx *context.Context) { + defer ctx.JSON(http.StatusOK, map[string]interface{}{ + "redirect": ctx.Repo.RepoLink + "/settings/collaboration", + }) + + u, err := user_model.GetUserByID(ctx, ctx.FormInt64("id")) + if err != nil { + ctx.Flash.Error("GetUserByID: " + err.Error()) + return + } + if err := models.DeleteCollaboration(ctx.Repo.Repository, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteCollaboration: " + err.Error()) } else { + audit.Record(audit.RepositoryCollaboratorRemove, ctx.Doer, ctx.Repo.Repository, u, "Removed user %s as collaborator.", u.Name) + ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success")) } - - ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": ctx.Repo.RepoLink + "/settings/collaboration", - }) } // AddTeamPost response for adding a team to a repository @@ -1035,6 +1080,8 @@ func AddTeamPost(ctx *context.Context) { return } + audit.Record(audit.RepositoryCollaboratorTeamAdd, ctx.Doer, ctx.Repo.Repository, team, "Added team %s as collaborator for %s.", team.Name, ctx.Repo.Repository.FullName()) + ctx.Flash.Success(ctx.Tr("repo.settings.add_team_success")) ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") } @@ -1058,6 +1105,8 @@ func DeleteTeam(ctx *context.Context) { return } + audit.Record(audit.RepositoryCollaboratorTeamRemove, ctx.Doer, ctx.Repo.Repository, team, "Removed team %s as collaborator from %s.", team.Name, ctx.Repo.Repository.FullName()) + ctx.Flash.Success(ctx.Tr("repo.settings.remove_team_success")) ctx.JSON(http.StatusOK, map[string]interface{}{ "redirect": ctx.Repo.RepoLink + "/settings/collaboration", @@ -1194,6 +1243,8 @@ func DeployKeysPost(ctx *context.Context) { return } + audit.Record(audit.RepositoryDeployKeyAdd, ctx.Doer, ctx.Repo.Repository, key, "Added deploy key %s.", key.Name) + log.Trace("Deploy key added: %d", ctx.Repo.Repository.ID) ctx.Flash.Success(ctx.Tr("repo.settings.add_key_success", key.Name)) ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys") diff --git a/routers/web/repo/setting_protected_branch.go b/routers/web/repo/setting_protected_branch.go index 34e84c4656827..cdc71936726ab 100644 --- a/routers/web/repo/setting_protected_branch.go +++ b/routers/web/repo/setting_protected_branch.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/forms" pull_service "code.gitea.io/gitea/services/pull" "code.gitea.io/gitea/services/repository" @@ -63,6 +64,7 @@ func SetDefaultBranchPost(ctx *context.Context) { ctx.Status(http.StatusNotFound) return } else if repo.DefaultBranch != branch { + oldBranch := repo.DefaultBranch repo.DefaultBranch = branch if err := ctx.Repo.GitRepo.SetDefaultBranch(branch); err != nil { if !git.IsErrUnsupportedVersion(err) { @@ -74,6 +76,8 @@ func SetDefaultBranchPost(ctx *context.Context) { ctx.ServerError("SetDefaultBranch", err) return } + + audit.Record(audit.RepositoryBranchDefault, ctx.Doer, repo, repo, "Changed default branch from %s to %s.", oldBranch, branch) } log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) @@ -269,6 +273,8 @@ func SettingsProtectedBranchPost(ctx *context.Context) { protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns protectBranch.BlockOnOutdatedBranch = f.BlockOnOutdatedBranch + isNewProtectedBranch := protectBranch.ID == 0 + err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{ UserIDs: whitelistUsers, TeamIDs: whitelistTeams, @@ -282,6 +288,12 @@ func SettingsProtectedBranchPost(ctx *context.Context) { return } + if isNewProtectedBranch { + audit.Record(audit.RepositoryBranchProtectionAdd, ctx.Doer, ctx.Repo.Repository, protectBranch, "Added branch protection %s.", protectBranch.RuleName) + } else { + audit.Record(audit.RepositoryBranchProtectionUpdate, ctx.Doer, ctx.Repo.Repository, protectBranch, "Updated branch protection %s.", protectBranch.RuleName) + } + // FIXME: since we only need to recheck files protected rules, we could improve this matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.GitRepo, protectBranch.RuleName) if err != nil { @@ -335,6 +347,8 @@ func DeleteProtectedBranchRulePost(ctx *context.Context) { return } + audit.Record(audit.RepositoryBranchProtectionRemove, ctx.Doer, ctx.Repo.Repository, rule, "Removed branch protection %s.", rule.RuleName) + ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", rule.RuleName)) ctx.JSON(http.StatusOK, map[string]interface{}{ "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink), @@ -374,6 +388,8 @@ func RenameBranchPost(ctx *context.Context) { return } + audit.Record(audit.RepositoryBranchRename, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Renamed branch from %s to %s.", form.From, form.To) + ctx.Flash.Success(ctx.Tr("repo.settings.rename_branch_success", form.From, form.To)) ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) } diff --git a/routers/web/repo/setting_secrets.go b/routers/web/repo/setting_secrets.go index c42dee583b923..8f2d3270718ab 100644 --- a/routers/web/repo/setting_secrets.go +++ b/routers/web/repo/setting_secrets.go @@ -32,8 +32,9 @@ func Secrets(ctx *context.Context) { func SecretsPost(ctx *context.Context) { shared.PerformSecretsPost( ctx, - 0, - ctx.Repo.Repository.ID, + ctx.Doer, + nil, + ctx.Repo.Repository, ctx.Repo.RepoLink+"/settings/secrets", ) } @@ -41,6 +42,9 @@ func SecretsPost(ctx *context.Context) { func DeleteSecret(ctx *context.Context) { shared.PerformSecretsDelete( ctx, + ctx.Doer, + nil, + ctx.Repo.Repository, ctx.Repo.RepoLink+"/settings/secrets", ) } diff --git a/routers/web/repo/tag.go b/routers/web/repo/tag.go index 95bc6dfce7f80..e1b0acfe8f5b4 100644 --- a/routers/web/repo/tag.go +++ b/routers/web/repo/tag.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/forms" ) @@ -59,6 +60,8 @@ func NewProtectedTagPost(ctx *context.Context) { return } + audit.Record(audit.RepositoryTagProtectionAdd, ctx.Doer, repo, pt, "Added tag protection for %s.", pt.NamePattern) + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) } @@ -112,6 +115,8 @@ func EditProtectedTagPost(ctx *context.Context) { return } + audit.Record(audit.RepositoryTagProtectionUpdate, ctx.Doer, ctx.Repo.Repository, pt, "Updated tag protection for %s.", pt.NamePattern) + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/tags") } @@ -128,6 +133,8 @@ func DeleteProtectedTagPost(ctx *context.Context) { return } + audit.Record(audit.RepositoryTagProtectionRemove, ctx.Doer, ctx.Repo.Repository, pt, "Removed tag protection for %s.", pt.NamePattern) + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/tags") } diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go index f30588967e836..47258f44fed84 100644 --- a/routers/web/repo/webhook.go +++ b/routers/web/repo/webhook.go @@ -13,6 +13,7 @@ import ( "strings" "code.gitea.io/gitea/models/perm" + repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/base" @@ -24,6 +25,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" webhook_module "code.gitea.io/gitea/modules/webhook" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/forms" webhook_service "code.gitea.io/gitea/services/webhook" @@ -56,8 +58,8 @@ func Webhooks(ctx *context.Context) { } type ownerRepoCtx struct { - OwnerID int64 - RepoID int64 + Owner *user_model.User + Repo *repo_model.Repository IsAdmin bool IsSystemWebhook bool Link string @@ -65,11 +67,34 @@ type ownerRepoCtx struct { NewTemplate base.TplName } +func (ctx *ownerRepoCtx) auditActionSwitch(user, org, repo, system audit.Action) audit.Action { + if ctx.IsAdmin { + return system + } + if ctx.Repo != nil { + return repo + } + if ctx.Owner.IsOrganization() { + return org + } + return user +} + +func (ctx *ownerRepoCtx) auditScopeSwitch() any { + if ctx.IsAdmin { + return nil + } + if ctx.Repo != nil { + return ctx.Repo + } + return ctx.Owner +} + // getOwnerRepoCtx determines whether this is a repo, owner, or admin (both default and system) context. func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) { if is, ok := ctx.Data["IsRepositoryWebhook"]; ok && is.(bool) { return &ownerRepoCtx{ - RepoID: ctx.Repo.Repository.ID, + Repo: ctx.Repo.Repository, Link: path.Join(ctx.Repo.RepoLink, "settings/hooks"), LinkNew: path.Join(ctx.Repo.RepoLink, "settings/hooks"), NewTemplate: tplHookNew, @@ -78,7 +103,7 @@ func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) { if is, ok := ctx.Data["IsOrganizationWebhook"]; ok && is.(bool) { return &ownerRepoCtx{ - OwnerID: ctx.ContextUser.ID, + Owner: ctx.ContextUser, Link: path.Join(ctx.Org.OrgLink, "settings/hooks"), LinkNew: path.Join(ctx.Org.OrgLink, "settings/hooks"), NewTemplate: tplOrgHookNew, @@ -87,7 +112,7 @@ func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) { if is, ok := ctx.Data["IsUserWebhook"]; ok && is.(bool) { return &ownerRepoCtx{ - OwnerID: ctx.Doer.ID, + Owner: ctx.Doer, Link: path.Join(setting.AppSubURL, "/user/settings/hooks"), LinkNew: path.Join(setting.AppSubURL, "/user/settings/hooks"), NewTemplate: tplUserHookNew, @@ -225,8 +250,17 @@ func createWebhook(ctx *context.Context, params webhookParams) { } } + repoID := int64(0) + if orCtx.Repo != nil { + repoID = orCtx.Repo.ID + } + ownerID := int64(0) + if orCtx.Owner != nil { + ownerID = orCtx.Owner.ID + } + w := &webhook.Webhook{ - RepoID: orCtx.RepoID, + RepoID: repoID, URL: params.URL, HTTPMethod: params.HTTPMethod, ContentType: params.ContentType, @@ -235,7 +269,7 @@ func createWebhook(ctx *context.Context, params webhookParams) { IsActive: params.WebhookForm.Active, Type: params.Type, Meta: string(meta), - OwnerID: orCtx.OwnerID, + OwnerID: ownerID, IsSystemWebhook: orCtx.IsSystemWebhook, } err = w.SetHeaderAuthorization(params.WebhookForm.AuthorizationHeader) @@ -251,6 +285,8 @@ func createWebhook(ctx *context.Context, params webhookParams) { return } + audit.Record(orCtx.auditActionSwitch(audit.UserWebhookAdd, audit.OrganizationWebhookAdd, audit.RepositoryWebhookAdd, audit.SystemWebhookAdd), ctx.Doer, orCtx.auditScopeSwitch(), w, "Added webhook %s.", w.URL) + ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) ctx.Redirect(orCtx.Link) } @@ -303,6 +339,8 @@ func editWebhook(ctx *context.Context, params webhookParams) { return } + audit.Record(orCtx.auditActionSwitch(audit.UserWebhookUpdate, audit.OrganizationWebhookUpdate, audit.RepositoryWebhookUpdate, audit.SystemWebhookUpdate), ctx.Doer, orCtx.auditScopeSwitch(), w, "Updated webhook %s.", w.URL) + ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) } @@ -585,10 +623,10 @@ func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) { ctx.Data["BaseLink"] = orCtx.Link var w *webhook.Webhook - if orCtx.RepoID > 0 { - w, err = webhook.GetWebhookByRepoID(orCtx.RepoID, ctx.ParamsInt64(":id")) - } else if orCtx.OwnerID > 0 { - w, err = webhook.GetWebhookByOwnerID(orCtx.OwnerID, ctx.ParamsInt64(":id")) + if orCtx.Repo != nil { + w, err = webhook.GetWebhookByRepoID(orCtx.Repo.ID, ctx.ParamsInt64(":id")) + } else if orCtx.Owner != nil { + w, err = webhook.GetWebhookByOwnerID(orCtx.Owner.ID, ctx.ParamsInt64(":id")) } else if orCtx.IsAdmin { w, err = webhook.GetSystemOrDefaultWebhook(ctx, ctx.ParamsInt64(":id")) } @@ -721,13 +759,21 @@ func ReplayWebhook(ctx *context.Context) { // DeleteWebhook delete a webhook func DeleteWebhook(ctx *context.Context) { + defer ctx.JSON(http.StatusOK, map[string]interface{}{ + "redirect": ctx.Repo.RepoLink + "/settings/hooks", + }) + + hook, err := webhook.GetWebhookByRepoID(ctx.Repo.Repository.ID, ctx.FormInt64("id")) + if err != nil { + ctx.Flash.Error("GetWebhookByRepoID: " + err.Error()) + return + } + if err := webhook.DeleteWebhookByRepoID(ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteWebhookByRepoID: " + err.Error()) } else { + audit.Record(audit.RepositoryWebhookRemove, ctx.Doer, ctx.Repo.Repository, hook, "Removed webhook %s.", hook.URL) + ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } - - ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": ctx.Repo.RepoLink + "/settings/hooks", - }) } diff --git a/routers/web/shared/secrets/secrets.go b/routers/web/shared/secrets/secrets.go index e242c5e81611d..aa68b229b5cf8 100644 --- a/routers/web/shared/secrets/secrets.go +++ b/routers/web/shared/secrets/secrets.go @@ -7,10 +7,13 @@ import ( "net/http" "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" secret_model "code.gitea.io/gitea/models/secret" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/forms" ) @@ -24,31 +27,77 @@ func SetSecretsContext(ctx *context.Context, ownerID, repoID int64) { ctx.Data["Secrets"] = secrets } -func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL string) { +func PerformSecretsPost(ctx *context.Context, doer, owner *user_model.User, repo *repo_model.Repository, redirectURL string) { form := web.GetForm(ctx).(*forms.AddSecretForm) - s, err := secret_model.InsertEncryptedSecret(ctx, ownerID, repoID, form.Title, form.Content) + s, err := secret_model.InsertEncryptedSecret(ctx, tryGetOwnerID(owner), tryGetRepositoryID(repo), form.Title, form.Content) if err != nil { log.Error("InsertEncryptedSecret: %v", err) ctx.Flash.Error(ctx.Tr("secrets.creation.failed")) } else { + audit.Record(auditActionSwitch(owner, repo, audit.UserSecretAdd, audit.OrganizationSecretAdd, audit.RepositorySecretAdd), doer, auditScopeSwitch(owner, repo), s, "Added secret %s.", s.Name) + ctx.Flash.Success(ctx.Tr("secrets.creation.success", s.Name)) } ctx.Redirect(redirectURL) } -func PerformSecretsDelete(ctx *context.Context, redirectURL string) { +func PerformSecretsDelete(ctx *context.Context, doer, owner *user_model.User, repo *repo_model.Repository, redirectURL string) { + defer ctx.JSON(http.StatusOK, map[string]interface{}{ + "redirect": redirectURL, + }) + id := ctx.FormInt64("id") + s := &secret_model.Secret{} + if has, err := db.GetBeanByID(ctx, id, s); err != nil { + log.Error("GetBeanByID failed: %v", err) + ctx.Flash.Error(ctx.Tr("secrets.deletion.failed")) + return + } else if !has || s.OwnerID != tryGetOwnerID(owner) || s.RepoID != tryGetRepositoryID(repo) { + ctx.Flash.Error(ctx.Tr("secrets.deletion.failed")) + return + } + if _, err := db.DeleteByBean(ctx, &secret_model.Secret{ID: id}); err != nil { log.Error("Delete secret %d failed: %v", id, err) ctx.Flash.Error(ctx.Tr("secrets.deletion.failed")) - } else { - ctx.Flash.Success(ctx.Tr("secrets.deletion.success")) + return } - ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": redirectURL, - }) + audit.Record(auditActionSwitch(owner, repo, audit.UserSecretRemove, audit.OrganizationSecretRemove, audit.RepositorySecretRemove), doer, auditScopeSwitch(owner, repo), s, "Removed secret %s.", s.Name) + + ctx.Flash.Success(ctx.Tr("secrets.deletion.success")) +} + +func tryGetOwnerID(owner *user_model.User) int64 { + if owner == nil { + return 0 + } + return owner.ID +} + +func tryGetRepositoryID(repo *repo_model.Repository) int64 { + if repo == nil { + return 0 + } + return repo.ID +} + +func auditActionSwitch(owner *user_model.User, repo *repo_model.Repository, userAction, orgAction, repoAction audit.Action) audit.Action { + if owner == nil { + return repoAction + } + if owner.IsOrganization() { + return orgAction + } + return userAction +} + +func auditScopeSwitch(owner *user_model.User, repo *repo_model.Repository) any { + if owner != nil { + return owner + } + return repo } diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index 0e48013b04da1..1ee351e65fb20 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/mailer" @@ -78,6 +79,9 @@ func AccountPost(ctx *context.Context) { ctx.ServerError("UpdateUser", err) return } + + audit.Record(audit.UserPassword, ctx.Doer, ctx.Doer, ctx.Doer, "Password of user %s changed.", ctx.Doer.Name) + log.Trace("User password updated: %s", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.change_password_success")) } @@ -214,16 +218,27 @@ func EmailPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.add_email_success")) } + audit.Record(audit.UserEmailAdd, ctx.Doer, ctx.Doer, email, "Email %s added to user %s.", email.Email, ctx.Doer.Name) + log.Trace("Email address added: %s", email.Email) ctx.Redirect(setting.AppSubURL + "/user/settings/account") } // DeleteEmail response for delete user's email func DeleteEmail(ctx *context.Context) { - if err := user_model.DeleteEmailAddress(&user_model.EmailAddress{ID: ctx.FormInt64("id"), UID: ctx.Doer.ID}); err != nil { + email, err := user_model.GetEmailAddressByID(ctx.Doer.ID, ctx.FormInt64("id")) + if err != nil { + ctx.ServerError("GetEmailAddressByID", err) + return + } + + if err := user_model.DeleteEmailAddress(email); err != nil { ctx.ServerError("DeleteEmail", err) return } + + audit.Record(audit.UserEmailRemove, ctx.Doer, ctx.Doer, email, "Email %s removed from user %s.", email.Email, ctx.Doer.Name) + log.Trace("Email address deleted: %s", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.email_deletion_success")) @@ -248,7 +263,7 @@ func DeleteAccount(ctx *context.Context) { return } - if err := user.DeleteUser(ctx, ctx.Doer, false); err != nil { + if err := user.DeleteUser(ctx, ctx.Doer, ctx.Doer, false); err != nil { switch { case models.IsErrUserOwnRepos(err): ctx.Flash.Error(ctx.Tr("form.still_own_repo")) diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index ac935e51bbb38..8db1922b8ac4d 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -8,10 +8,12 @@ import ( "net/http" auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/forms" ) @@ -69,6 +71,8 @@ func ApplicationsPost(ctx *context.Context) { return } + audit.Record(audit.UserAccessTokenAdd, ctx.Doer, ctx.Doer, t, "Added access token %s for user %s with scope %s.", t.Name, ctx.Doer.Name, t.Scope) + ctx.Flash.Success(ctx.Tr("settings.generate_token_success")) ctx.Flash.Info(t.Token) @@ -77,15 +81,26 @@ func ApplicationsPost(ctx *context.Context) { // DeleteApplication response for delete user access token func DeleteApplication(ctx *context.Context) { + defer ctx.JSON(http.StatusOK, map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/applications", + }) + + t := &auth_model.AccessToken{UID: ctx.Doer.ID} + has, err := db.GetBeanByID(ctx, ctx.FormInt64("id"), t) + if err != nil { + ctx.Flash.Error("GetBeanByID: " + err.Error()) + return + } else if !has { + return + } + if err := auth_model.DeleteAccessTokenByID(ctx.FormInt64("id"), ctx.Doer.ID); err != nil { ctx.Flash.Error("DeleteAccessTokenByID: " + err.Error()) } else { + audit.Record(audit.UserAccessTokenRemove, ctx.Doer, ctx.Doer, t, "Removed access token %s from user %s.", t.Name, ctx.Doer.Name) + ctx.Flash.Success(ctx.Tr("settings.delete_token_success")) } - - ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": setting.AppSubURL + "/user/settings/applications", - }) } func loadApplicationsData(ctx *context.Context) { diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index 6debf95bbce06..ccfc5b54b0ddb 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" asymkey_service "code.gitea.io/gitea/services/asymkey" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/forms" ) @@ -61,7 +62,8 @@ func KeysPost(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/user/settings/keys") return } - if _, err = asymkey_model.AddPrincipalKey(ctx.Doer.ID, content, 0); err != nil { + key, err := asymkey_model.AddPrincipalKey(ctx.Doer.ID, content, 0) + if err != nil { ctx.Data["HasPrincipalError"] = true switch { case asymkey_model.IsErrKeyAlreadyExist(err), asymkey_model.IsErrKeyNameAlreadyUsed(err): @@ -74,6 +76,9 @@ func KeysPost(ctx *context.Context) { } return } + + audit.Record(audit.UserKeyPrincipalAdd, ctx.Doer, ctx.Doer, key, "Added principal key %s.", key.Name) + ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "gpg": @@ -117,6 +122,11 @@ func KeysPost(ctx *context.Context) { } return } + + for _, key := range keys { + audit.Record(audit.UserKeyGPGAdd, ctx.Doer, ctx.Doer, key, "Added GPG key %s.", key.KeyID) + } + keyIDs := "" for _, key := range keys { keyIDs += key.KeyID @@ -168,7 +178,8 @@ func KeysPost(ctx *context.Context) { return } - if _, err = asymkey_model.AddPublicKey(ctx.Doer.ID, form.Title, content, 0); err != nil { + key, err := asymkey_model.AddPublicKey(ctx.Doer.ID, form.Title, content, 0) + if err != nil { ctx.Data["HasSSHError"] = true switch { case asymkey_model.IsErrKeyAlreadyExist(err): @@ -189,6 +200,9 @@ func KeysPost(ctx *context.Context) { } return } + + audit.Record(audit.UserKeySSHAdd, ctx.Doer, ctx.Doer, key, "Added SSH key %s.", key.Fingerprint) + ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "verify_ssh": @@ -224,10 +238,17 @@ func KeysPost(ctx *context.Context) { func DeleteKey(ctx *context.Context) { switch ctx.FormString("type") { case "gpg": - if err := asymkey_model.DeleteGPGKey(ctx.Doer, ctx.FormInt64("id")); err != nil { - ctx.Flash.Error("DeleteGPGKey: " + err.Error()) + key, err := asymkey_model.GetGPGKeyByID(ctx.FormInt64("id")) + if err != nil && !asymkey_model.IsErrGPGKeyNotExist(err) { + ctx.Flash.Error("GetGPGKeyByID: " + err.Error()) } else { - ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success")) + if err := asymkey_model.DeleteGPGKey(ctx.Doer, key.ID); err != nil { + ctx.Flash.Error("DeleteGPGKey: " + err.Error()) + } else { + audit.Record(audit.UserKeyGPGRemove, ctx.Doer, ctx.Doer, key, "Removed GPG key %s.", key.KeyID) + + ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success")) + } } case "ssh": keyID := ctx.FormInt64("id") diff --git a/routers/web/user/setting/oauth2.go b/routers/web/user/setting/oauth2.go index 93142c21fcfc1..2cc3ae9dcd306 100644 --- a/routers/web/user/setting/oauth2.go +++ b/routers/web/user/setting/oauth2.go @@ -4,6 +4,7 @@ package setting import ( + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" @@ -13,9 +14,9 @@ const ( tplSettingsOAuthApplicationEdit base.TplName = "user/settings/applications_oauth2_edit" ) -func newOAuth2CommonHandlers(userID int64) *OAuth2CommonHandlers { +func newOAuth2CommonHandlers(u *user_model.User) *OAuth2CommonHandlers { return &OAuth2CommonHandlers{ - OwnerID: userID, + Owner: u, BasePathList: setting.AppSubURL + "/user/settings/applications", BasePathEditPrefix: setting.AppSubURL + "/user/settings/applications/oauth2", TplAppEdit: tplSettingsOAuthApplicationEdit, @@ -27,7 +28,7 @@ func OAuthApplicationsPost(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsApplications"] = true - oa := newOAuth2CommonHandlers(ctx.Doer.ID) + oa := newOAuth2CommonHandlers(ctx.Doer) oa.AddApp(ctx) } @@ -36,7 +37,7 @@ func OAuthApplicationsEdit(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsApplications"] = true - oa := newOAuth2CommonHandlers(ctx.Doer.ID) + oa := newOAuth2CommonHandlers(ctx.Doer) oa.EditSave(ctx) } @@ -45,24 +46,24 @@ func OAuthApplicationsRegenerateSecret(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsApplications"] = true - oa := newOAuth2CommonHandlers(ctx.Doer.ID) + oa := newOAuth2CommonHandlers(ctx.Doer) oa.RegenerateSecret(ctx) } // OAuth2ApplicationShow displays the given application func OAuth2ApplicationShow(ctx *context.Context) { - oa := newOAuth2CommonHandlers(ctx.Doer.ID) + oa := newOAuth2CommonHandlers(ctx.Doer) oa.EditShow(ctx) } // DeleteOAuth2Application deletes the given oauth2 application func DeleteOAuth2Application(ctx *context.Context) { - oa := newOAuth2CommonHandlers(ctx.Doer.ID) + oa := newOAuth2CommonHandlers(ctx.Doer) oa.DeleteApp(ctx) } // RevokeOAuth2Grant revokes the grant with the given id func RevokeOAuth2Grant(ctx *context.Context) { - oa := newOAuth2CommonHandlers(ctx.Doer.ID) + oa := newOAuth2CommonHandlers(ctx.Doer) oa.RevokeGrant(ctx) } diff --git a/routers/web/user/setting/oauth2_common.go b/routers/web/user/setting/oauth2_common.go index f6ad1b2b381ca..02e7072d2de2f 100644 --- a/routers/web/user/setting/oauth2_common.go +++ b/routers/web/user/setting/oauth2_common.go @@ -4,21 +4,43 @@ package setting import ( + "errors" "fmt" "net/http" "code.gitea.io/gitea/models/auth" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/forms" ) type OAuth2CommonHandlers struct { - OwnerID int64 // 0 for instance-wide, otherwise OrgID or UserID - BasePathList string // the base URL for the application list page, eg: "/user/setting/applications" - BasePathEditPrefix string // the base URL for the application edit page, will be appended with app id, eg: "/user/setting/applications/oauth2" - TplAppEdit base.TplName // the template for the application edit page + Doer *user_model.User + Owner *user_model.User // nil for instance-wide, otherwise Org or User + BasePathList string // the base URL for the application list page, eg: "/user/setting/applications" + BasePathEditPrefix string // the base URL for the application edit page, will be appended with app id, eg: "/user/setting/applications/oauth2" + TplAppEdit base.TplName // the template for the application edit page +} + +func (oa *OAuth2CommonHandlers) ownerID() int64 { + if oa.Owner != nil { + return oa.Owner.ID + } + return 0 +} + +func (oa *OAuth2CommonHandlers) auditActionSwitch(user, org, system audit.Action) audit.Action { + if oa.Owner == nil { + return system + } + if oa.Owner.IsOrganization() { + return org + } + return user } func (oa *OAuth2CommonHandlers) renderEditPage(ctx *context.Context) { @@ -40,7 +62,7 @@ func (oa *OAuth2CommonHandlers) AddApp(ctx *context.Context) { app, err := auth.CreateOAuth2Application(ctx, auth.CreateOAuth2ApplicationOptions{ Name: form.Name, RedirectURIs: []string{form.RedirectURI}, - UserID: oa.OwnerID, + UserID: oa.ownerID(), ConfidentialClient: form.ConfidentialClient, }) if err != nil { @@ -48,6 +70,8 @@ func (oa *OAuth2CommonHandlers) AddApp(ctx *context.Context) { return } + audit.Record(oa.auditActionSwitch(audit.UserOAuth2ApplicationAdd, audit.OrganizationOAuth2ApplicationAdd, audit.SystemOAuth2ApplicationAdd), oa.Doer, oa.Owner, app, "Created OAuth2 application %s.", app.Name) + // render the edit page with secret ctx.Flash.Success(ctx.Tr("settings.create_oauth2_application_success"), true) ctx.Data["App"] = app @@ -70,7 +94,7 @@ func (oa *OAuth2CommonHandlers) EditShow(ctx *context.Context) { ctx.ServerError("GetOAuth2ApplicationByID", err) return } - if app.UID != oa.OwnerID { + if app.UID != oa.ownerID() { ctx.NotFound("Application not found", nil) return } @@ -88,17 +112,22 @@ func (oa *OAuth2CommonHandlers) EditSave(ctx *context.Context) { } // TODO validate redirect URI - var err error - if ctx.Data["App"], err = auth.UpdateOAuth2Application(auth.UpdateOAuth2ApplicationOptions{ + app, err := auth.UpdateOAuth2Application(auth.UpdateOAuth2ApplicationOptions{ ID: ctx.ParamsInt64("id"), Name: form.Name, RedirectURIs: []string{form.RedirectURI}, - UserID: oa.OwnerID, + UserID: oa.ownerID(), ConfidentialClient: form.ConfidentialClient, - }); err != nil { + }) + if err != nil { ctx.ServerError("UpdateOAuth2Application", err) return } + + ctx.Data["App"] = app + + audit.Record(oa.auditActionSwitch(audit.UserOAuth2ApplicationUpdate, audit.OrganizationOAuth2ApplicationUpdate, audit.SystemOAuth2ApplicationUpdate), oa.Doer, oa.Owner, app, "Updated OAuth2 application %s.", app.Name) + ctx.Flash.Success(ctx.Tr("settings.update_oauth2_application_success")) ctx.Redirect(oa.BasePathList) } @@ -114,7 +143,7 @@ func (oa *OAuth2CommonHandlers) RegenerateSecret(ctx *context.Context) { ctx.ServerError("GetOAuth2ApplicationByID", err) return } - if app.UID != oa.OwnerID { + if app.UID != oa.ownerID() { ctx.NotFound("Application not found", nil) return } @@ -124,28 +153,65 @@ func (oa *OAuth2CommonHandlers) RegenerateSecret(ctx *context.Context) { ctx.ServerError("GenerateClientSecret", err) return } + + audit.Record(oa.auditActionSwitch(audit.UserOAuth2ApplicationSecret, audit.OrganizationOAuth2ApplicationSecret, audit.SystemOAuth2ApplicationSecret), oa.Doer, oa.Owner, app, "Regenerated secret for OAuth2 application %s.", app.Name) + ctx.Flash.Success(ctx.Tr("settings.update_oauth2_application_success"), true) oa.renderEditPage(ctx) } // DeleteApp deletes the given oauth2 application func (oa *OAuth2CommonHandlers) DeleteApp(ctx *context.Context) { - if err := auth.DeleteOAuth2Application(ctx.ParamsInt64("id"), oa.OwnerID); err != nil { + app, err := auth.GetOAuth2ApplicationByID(ctx, ctx.ParamsInt64("id")) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.NotFound("Application not found", err) + } else { + ctx.ServerError("GetOAuth2ApplicationByID", err) + } + return + } + + if err := auth.DeleteOAuth2Application(app.ID, oa.ownerID()); err != nil { ctx.ServerError("DeleteOAuth2Application", err) return } + audit.Record(oa.auditActionSwitch(audit.UserOAuth2ApplicationRemove, audit.OrganizationOAuth2ApplicationRemove, audit.SystemOAuth2ApplicationRemove), oa.Doer, oa.Owner, app, "Removed OAuth2 application %s.", app.Name) + ctx.Flash.Success(ctx.Tr("settings.remove_oauth2_application_success")) ctx.JSON(http.StatusOK, map[string]interface{}{"redirect": oa.BasePathList}) } // RevokeGrant revokes the grant func (oa *OAuth2CommonHandlers) RevokeGrant(ctx *context.Context) { - if err := auth.RevokeOAuth2Grant(ctx, ctx.ParamsInt64("grantId"), oa.OwnerID); err != nil { + grant, err := auth.GetOAuth2GrantByID(ctx, ctx.ParamsInt64("grantId")) + if err != nil { + ctx.ServerError("GetOAuth2GrantByID", err) + return + } + if grant == nil { + ctx.NotFound("Grant not found", nil) + return + } + + app, err := auth.GetOAuth2ApplicationByID(ctx, grant.ApplicationID) + if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.NotFound("Application not found", err) + } else { + ctx.ServerError("GetOAuth2ApplicationByID", err) + } + return + } + + if err := auth.RevokeOAuth2Grant(ctx, grant.ID, oa.ownerID()); err != nil { ctx.ServerError("RevokeOAuth2Grant", err) return } + audit.Record(audit.UserOAuth2ApplicationRevoke, oa.Doer, oa.Owner, grant, "Revoked OAuth2 grant for application %s.", app.Name) + ctx.Flash.Success(ctx.Tr("settings.revoke_oauth2_grant_success")) ctx.JSON(http.StatusOK, map[string]interface{}{"redirect": oa.BasePathList}) } diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index f500be7632336..4032b8765791a 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -27,6 +27,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/forms" user_service "code.gitea.io/gitea/services/user" ) @@ -48,7 +49,7 @@ func Profile(ctx *context.Context) { } // HandleUsernameChange handle username changes from user settings and admin interface -func HandleUsernameChange(ctx *context.Context, user *user_model.User, newName string) error { +func HandleUsernameChange(ctx *context.Context, doer, user *user_model.User, newName string) error { // Non-local users are not allowed to change their username. if !user.IsLocal() { ctx.Flash.Error(ctx.Tr("form.username_change_not_local_user")) @@ -56,7 +57,7 @@ func HandleUsernameChange(ctx *context.Context, user *user_model.User, newName s } // rename user - if err := user_service.RenameUser(ctx, user, newName); err != nil { + if err := user_service.RenameUser(ctx, doer, user, newName); err != nil { switch { case user_model.IsErrUserAlreadyExist(err): ctx.Flash.Error(ctx.Tr("form.username_been_taken")) @@ -90,7 +91,7 @@ func ProfilePost(ctx *context.Context) { if len(form.Name) != 0 && ctx.Doer.Name != form.Name { log.Debug("Changing name for %s to %s", ctx.Doer.Name, form.Name) - if err := HandleUsernameChange(ctx, ctx.Doer, form.Name); err != nil { + if err := HandleUsernameChange(ctx, ctx.Doer, ctx.Doer, form.Name); err != nil { ctx.Redirect(setting.AppSubURL + "/user/settings") return } @@ -104,6 +105,8 @@ func ProfilePost(ctx *context.Context) { ctx.Doer.Location = form.Location ctx.Doer.Description = form.Description ctx.Doer.KeepActivityPrivate = form.KeepActivityPrivate + + oldVisibility := ctx.Doer.Visibility ctx.Doer.Visibility = form.Visibility if err := user_model.UpdateUserSetting(ctx.Doer); err != nil { if _, ok := err.(user_model.ErrEmailAlreadyUsed); ok { @@ -116,6 +119,13 @@ func ProfilePost(ctx *context.Context) { } log.Trace("User settings updated: %s", ctx.Doer.Name) + + audit.Record(audit.UserUpdate, ctx.Doer, ctx.Doer, ctx.Doer, "Updated settings of user %s.", ctx.Doer.Name) + + if oldVisibility != ctx.Doer.Visibility { + audit.Record(audit.UserVisibility, ctx.Doer, ctx.Doer, ctx.Doer, "Visibility of user %s changed from %s to %s.", ctx.Doer.Name, oldVisibility.String(), ctx.Doer.Visibility.String()) + } + ctx.Flash.Success(ctx.Tr("settings.update_profile_success")) ctx.Redirect(setting.AppSubURL + "/user/settings") } diff --git a/routers/web/user/setting/secrets.go b/routers/web/user/setting/secrets.go index 3a57897d8f607..18a8eb2217fae 100644 --- a/routers/web/user/setting/secrets.go +++ b/routers/web/user/setting/secrets.go @@ -31,8 +31,9 @@ func Secrets(ctx *context.Context) { func SecretsPost(ctx *context.Context) { shared.PerformSecretsPost( ctx, - ctx.Doer.ID, - 0, + ctx.Doer, + ctx.Doer, + nil, setting.AppSubURL+"/user/settings/secrets", ) } @@ -40,6 +41,9 @@ func SecretsPost(ctx *context.Context) { func SecretsDelete(ctx *context.Context) { shared.PerformSecretsDelete( ctx, + ctx.Doer, + ctx.Doer, + nil, setting.AppSubURL+"/user/settings/secrets", ) } diff --git a/routers/web/user/setting/security/2fa.go b/routers/web/user/setting/security/2fa.go index 0cecb1aa37fef..eb58e5749954e 100644 --- a/routers/web/user/setting/security/2fa.go +++ b/routers/web/user/setting/security/2fa.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/forms" "github.com/pquerna/otp" @@ -49,6 +50,8 @@ func RegenerateScratchTwoFactor(ctx *context.Context) { return } + audit.Record(audit.UserTwoFactorRegenerate, ctx.Doer, ctx.Doer, t, "User %s regenerated two-factor authentication secret.", ctx.Doer.Name) + ctx.Flash.Success(ctx.Tr("settings.twofa_scratch_token_regenerated", token)) ctx.Redirect(setting.AppSubURL + "/user/settings/security") } @@ -78,6 +81,8 @@ func DisableTwoFactor(ctx *context.Context) { return } + audit.Record(audit.UserTwoFactorDisable, ctx.Doer, ctx.Doer, t, "User %s disabled two-factor authentication.", ctx.Doer.Name) + ctx.Flash.Success(ctx.Tr("settings.twofa_disabled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") } @@ -244,6 +249,8 @@ func EnrollTwoFactorPost(ctx *context.Context) { return } + audit.Record(audit.UserTwoFactorEnable, ctx.Doer, ctx.Doer, t, "User %s enabled two-factor authentication.", ctx.Doer.Name) + ctx.Flash.Success(ctx.Tr("settings.twofa_enrolled", token)) ctx.Redirect(setting.AppSubURL + "/user/settings/security") } diff --git a/routers/web/user/setting/security/openid.go b/routers/web/user/setting/security/openid.go index 08fcb6b62343c..d23d04b8f1649 100644 --- a/routers/web/user/setting/security/openid.go +++ b/routers/web/user/setting/security/openid.go @@ -6,12 +6,14 @@ package security import ( "net/http" + "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/openid" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/forms" ) @@ -97,6 +99,9 @@ func settingsOpenIDVerify(ctx *context.Context) { ctx.ServerError("AddUserOpenID", err) return } + + audit.Record(audit.UserOpenIDAdd, ctx.Doer, ctx.Doer, oid, "Associated OpenID %s to user %s.", oid.URI, ctx.Doer.Name) + log.Trace("Associated OpenID %s to user %s", id, ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.add_openid_success")) @@ -105,10 +110,20 @@ func settingsOpenIDVerify(ctx *context.Context) { // DeleteOpenID response for delete user's openid func DeleteOpenID(ctx *context.Context) { + oid := &user_model.UserOpenID{UID: ctx.Doer.ID} + _, err := db.GetBeanByID(ctx, ctx.FormInt64("id"), oid) + if err != nil { + ctx.ServerError("GetBeanByID", err) + return + } + if err := user_model.DeleteUserOpenID(&user_model.UserOpenID{ID: ctx.FormInt64("id"), UID: ctx.Doer.ID}); err != nil { ctx.ServerError("DeleteUserOpenID", err) return } + + audit.Record(audit.UserOpenIDAdd, ctx.Doer, ctx.Doer, oid, "Removed OpenID %s from user %s.", oid.URI, ctx.Doer.Name) + log.Trace("OpenID address deleted: %s", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.openid_deletion_success")) diff --git a/routers/web/user/setting/security/security.go b/routers/web/user/setting/security/security.go index 6e6e7efb0b2c1..488c9cb9dc6c5 100644 --- a/routers/web/user/setting/security/security.go +++ b/routers/web/user/setting/security/security.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/auth/source/oauth2" ) @@ -37,20 +38,27 @@ func Security(ctx *context.Context) { // DeleteAccountLink delete a single account link func DeleteAccountLink(ctx *context.Context) { - id := ctx.FormInt64("id") - if id <= 0 { - ctx.Flash.Error("Account link id is not given") - } else { - if _, err := user_model.RemoveAccountLink(ctx.Doer, id); err != nil { - ctx.Flash.Error("RemoveAccountLink: " + err.Error()) - } else { - ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success")) + defer ctx.JSON(http.StatusOK, map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/security", + }) + + elu := &user_model.ExternalLoginUser{UserID: ctx.Doer.ID, LoginSourceID: ctx.FormInt64("id")} + if has, err := user_model.GetExternalLogin(elu); err != nil || !has { + if !has { + err = user_model.ErrExternalLoginUserNotExist{UserID: elu.UserID, LoginSourceID: elu.LoginSourceID} } + ctx.Flash.Error("RemoveAccountLink: " + err.Error()) + return } - ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": setting.AppSubURL + "/user/settings/security", - }) + if _, err := user_model.RemoveAccountLink(ctx.Doer, elu.LoginSourceID); err != nil { + ctx.Flash.Error("RemoveAccountLink: " + err.Error()) + return + } + + audit.Record(audit.UserExternalLoginRemove, ctx.Doer, ctx.Doer, elu, "Removed external login %s for user %s.", elu.ExternalID, ctx.Doer.Name) + + ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success")) } func loadSecurityData(ctx *context.Context) { diff --git a/routers/web/user/setting/security/webauthn.go b/routers/web/user/setting/security/webauthn.go index 0054318867461..2f7f8d0fa6050 100644 --- a/routers/web/user/setting/security/webauthn.go +++ b/routers/web/user/setting/security/webauthn.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/forms" "github.com/go-webauthn/webauthn/protocol" @@ -97,23 +98,35 @@ func WebauthnRegisterPost(ctx *context.Context) { } // Create the credential - _, err = auth.CreateCredential(ctx.Doer.ID, name, cred) + dbCred, err = auth.CreateCredential(ctx.Doer.ID, name, cred) if err != nil { ctx.ServerError("CreateCredential", err) return } _ = ctx.Session.Delete("webauthnName") + audit.Record(audit.UserWebAuthAdd, ctx.Doer, ctx.Doer, dbCred, "User %s added WebAuthn key %s.", ctx.Doer.Name, dbCred.Name) + ctx.JSON(http.StatusCreated, cred) } // WebauthnDelete deletes an security key by id func WebauthnDelete(ctx *context.Context) { form := web.GetForm(ctx).(*forms.WebauthnDeleteForm) - if _, err := auth.DeleteCredential(form.ID, ctx.Doer.ID); err != nil { + + cred, err := auth.GetWebAuthnCredentialByID(form.ID) + if err != nil { ctx.ServerError("GetWebAuthnCredentialByID", err) return } + + if ok, err := auth.DeleteCredential(form.ID, ctx.Doer.ID); err != nil { + ctx.ServerError("DeleteCredential", err) + return + } else if ok { + audit.Record(audit.UserWebAuthRemove, ctx.Doer, ctx.Doer, cred, "User %s removed WebAuthn key %s.", ctx.Doer.Name, cred.Name) + } + ctx.JSON(http.StatusOK, map[string]interface{}{ "redirect": setting.AppSubURL + "/user/settings/security", }) diff --git a/routers/web/user/setting/webhooks.go b/routers/web/user/setting/webhooks.go index 9b0b0c9611c1a..4ad62ef40cfe2 100644 --- a/routers/web/user/setting/webhooks.go +++ b/routers/web/user/setting/webhooks.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/audit" ) const ( @@ -36,13 +37,21 @@ func Webhooks(ctx *context.Context) { // DeleteWebhook response for delete webhook func DeleteWebhook(ctx *context.Context) { + defer ctx.JSON(http.StatusOK, map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/hooks", + }) + + hook, err := webhook.GetWebhookByOwnerID(ctx.Doer.ID, ctx.FormInt64("id")) + if err != nil { + ctx.Flash.Error("GetWebhookByOwnerID: " + err.Error()) + return + } + if err := webhook.DeleteWebhookByOwnerID(ctx.Doer.ID, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error()) } else { + audit.Record(audit.UserWebhookRemove, ctx.Doer, ctx.Doer, hook, "Removed webhook %s.", hook.URL) + ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } - - ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": setting.AppSubURL + "/user/settings/hooks", - }) } diff --git a/services/asymkey/deploy_key.go b/services/asymkey/deploy_key.go index f5ca54b72387f..e8cf58d10433d 100644 --- a/services/asymkey/deploy_key.go +++ b/services/asymkey/deploy_key.go @@ -4,10 +4,14 @@ package asymkey import ( + "fmt" + "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/services/audit" ) // DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed. @@ -18,6 +22,16 @@ func DeleteDeployKey(doer *user_model.User, id int64) error { } defer committer.Close() + key, err := asymkey_model.GetDeployKeyByID(ctx, id) + if err != nil && !asymkey_model.IsErrDeployKeyNotExist(err) { + return fmt.Errorf("GetDeployKeyByID: %w", err) + } + + repo, err := repo_model.GetRepositoryByID(ctx, key.RepoID) + if err != nil { + return fmt.Errorf("GetRepositoryByID: %w", err) + } + if err := models.DeleteDeployKey(ctx, doer, id); err != nil { return err } @@ -25,5 +39,7 @@ func DeleteDeployKey(doer *user_model.User, id int64) error { return err } + audit.Record(audit.RepositoryDeployKeyRemove, doer, repo, key, "Removed deploy key %s.", key.Name) + return asymkey_model.RewriteAllPublicKeys() } diff --git a/services/asymkey/ssh_key.go b/services/asymkey/ssh_key.go index 0809458107b75..d4dea08b09366 100644 --- a/services/asymkey/ssh_key.go +++ b/services/asymkey/ssh_key.go @@ -7,6 +7,7 @@ import ( asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/services/audit" ) // DeletePublicKey deletes SSH key information both in database and authorized_keys file. @@ -16,6 +17,11 @@ func DeletePublicKey(doer *user_model.User, id int64) (err error) { return err } + owner, err := user_model.GetUserByID(db.DefaultContext, key.OwnerID) + if err != nil { + return err + } + // Check if user has access to delete this key. if !doer.IsAdmin && doer.ID != key.OwnerID { return asymkey_model.ErrKeyAccessDenied{ @@ -41,8 +47,12 @@ func DeletePublicKey(doer *user_model.User, id int64) (err error) { committer.Close() if key.Type == asymkey_model.KeyTypePrincipal { + audit.Record(audit.UserKeyPrincipalRemove, doer, owner, key, "Removed principal key %s.", key.Name) + return asymkey_model.RewriteAllPrincipalKeys(db.DefaultContext) } + audit.Record(audit.UserKeySSHRemove, doer, owner, key, "Removed SSH key %s.", key.Fingerprint) + return asymkey_model.RewriteAllPublicKeys() } diff --git a/services/audit/action.go b/services/audit/action.go new file mode 100644 index 0000000000000..ab5bdbfbbcb86 --- /dev/null +++ b/services/audit/action.go @@ -0,0 +1,122 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package audit + +type Action string + +const ( + UserImpersonation Action = "user:impersonation" + UserCreate Action = "user:create" + UserUpdate Action = "user:update" + UserDelete Action = "user:delete" + UserAuthenticationFailTwoFactor Action = "user:authentication:fail:twofactor" + UserAuthenticationSource Action = "user:authentication:source" + UserActive Action = "user:active" + UserRestricted Action = "user:restricted" + UserAdmin Action = "user:admin" + UserName Action = "user:name" + UserPassword Action = "user:password" + UserPasswordReset Action = "user:password:reset" + UserVisibility Action = "user:visibility" + UserEmailAdd Action = "user:email:add" + UserEmailActivate Action = "user:email:activate" + UserEmailRemove Action = "user:email:remove" + UserTwoFactorEnable Action = "user:twofactor:enable" + UserTwoFactorRegenerate Action = "user:twofactor:regenerate" + UserTwoFactorDisable Action = "user:twofactor:disable" + UserWebAuthAdd Action = "user:webauth:add" + UserWebAuthRemove Action = "user:webauth:remove" + UserExternalLoginAdd Action = "user:externallogin:add" + UserExternalLoginRemove Action = "user:externallogin:remove" + UserOpenIDAdd Action = "user:openid:add" + UserOpenIDRemove Action = "user:openid:remove" + UserAccessTokenAdd Action = "user:accesstoken:add" + UserAccessTokenRemove Action = "user:accesstoken:remove" + UserOAuth2ApplicationAdd Action = "user:oauth2application:add" + UserOAuth2ApplicationUpdate Action = "user:oauth2application:update" + UserOAuth2ApplicationSecret Action = "user:oauth2application:secret" + UserOAuth2ApplicationGrant Action = "user:oauth2application:grant" + UserOAuth2ApplicationRevoke Action = "user:oauth2application:revoke" + UserOAuth2ApplicationRemove Action = "user:oauth2application:remove" + UserKeySSHAdd Action = "user:key:ssh:add" + UserKeySSHRemove Action = "user:key:ssh:remove" + UserKeyPrincipalAdd Action = "user:key:principal:add" + UserKeyPrincipalRemove Action = "user:key:principal:remove" + UserKeyGPGAdd Action = "user:key:gpg:add" + UserKeyGPGRemove Action = "user:key:gpg:remove" + UserSecretAdd Action = "user:secret:add" + UserSecretRemove Action = "user:secret:remove" + UserWebhookAdd Action = "user:webhook:add" + UserWebhookUpdate Action = "user:webhook:update" + UserWebhookRemove Action = "user:webhook:remove" + + OrganizationCreate Action = "organization:create" + OrganizationUpdate Action = "organization:update" + OrganizationDelete Action = "organization:delete" + OrganizationName Action = "organization:name" + OrganizationVisibility Action = "organization:visibility" + OrganizationTeamAdd Action = "organization:team:add" + OrganizationTeamUpdate Action = "organization:team:update" + OrganizationTeamRemove Action = "organization:team:remove" + OrganizationTeamPermission Action = "organization:team:permission" + OrganizationTeamMemberAdd Action = "organization:team:member:add" + OrganizationTeamMemberRemove Action = "organization:team:member:remove" + OrganizationOAuth2ApplicationAdd Action = "organization:oauth2application:add" + OrganizationOAuth2ApplicationUpdate Action = "organization:oauth2application:update" + OrganizationOAuth2ApplicationSecret Action = "organization:oauth2application:secret" + OrganizationOAuth2ApplicationRemove Action = "organization:oauth2application:remove" + OrganizationSecretAdd Action = "organization:secret:add" + OrganizationSecretRemove Action = "organization:secret:remove" + OrganizationWebhookAdd Action = "organization:webhook:add" + OrganizationWebhookUpdate Action = "organization:webhook:update" + OrganizationWebhookRemove Action = "organization:webhook:remove" + + RepositoryCreate Action = "repository:create" + RepositoryUpdate Action = "repository:update" + RepositoryArchive Action = "repository:archive" + RepositoryUnarchive Action = "repository:unarchive" + RepositoryDelete Action = "repository:delete" + RepositoryName Action = "repository:name" + RepositoryVisibility Action = "repository:visibility" + RepositoryConvertFork Action = "repository:convert:fork" + RepositoryConvertMirror Action = "repository:convert:mirror" + RepositoryMirrorPushAdd Action = "repository:mirror:push:add" + RepositoryMirrorPushRemove Action = "repository:mirror:push:remove" + RepositorySigningVerification Action = "repository:signingverification" + RepositoryTransferStart Action = "repository:transfer:start" + RepositoryTransferAccept Action = "repository:transfer:accept" + RepositoryTransferReject Action = "repository:transfer:reject" + RepositoryWikiDelete Action = "repository:wiki:delete" + RepositoryCollaboratorAdd Action = "repository:collaborator:add" + RepositoryCollaboratorAccess Action = "repository:collaborator:access" + RepositoryCollaboratorRemove Action = "repository:collaborator:remove" + RepositoryCollaboratorTeamAdd Action = "repository:collaborator:team:add" + RepositoryCollaboratorTeamRemove Action = "repository:collaborator:team:remove" + RepositoryBranchDefault Action = "repository:branch:default" + RepositoryBranchRename Action = "repository:branch:rename" + RepositoryBranchProtectionAdd Action = "repository:branch:protection:add" + RepositoryBranchProtectionUpdate Action = "repository:branch:protection:update" + RepositoryBranchProtectionRemove Action = "repository:branch:protection:remove" + RepositoryTagProtectionAdd Action = "repository:tag:protection:add" + RepositoryTagProtectionUpdate Action = "repository:tag:protection:update" + RepositoryTagProtectionRemove Action = "repository:tag:protection:remove" + RepositoryWebhookAdd Action = "repository:webhook:add" + RepositoryWebhookUpdate Action = "repository:webhook:update" + RepositoryWebhookRemove Action = "repository:webhook:remove" + RepositoryDeployKeyAdd Action = "repository:deploykey:add" + RepositoryDeployKeyRemove Action = "repository:deploykey:remove" + RepositorySecretAdd Action = "repository:secret:add" + RepositorySecretRemove Action = "repository:secret:remove" + + SystemWebhookAdd Action = "system:webhook:add" + SystemWebhookUpdate Action = "system:webhook:update" + SystemWebhookRemove Action = "system:webhook:remove" + SystemAuthenticationSourceAdd Action = "system:authenticationsource:add" + SystemAuthenticationSourceUpdate Action = "system:authenticationsource:update" + SystemAuthenticationSourceRemove Action = "system:authenticationsource:remove" + SystemOAuth2ApplicationAdd Action = "system:oauth2application:add" + SystemOAuth2ApplicationUpdate Action = "system:oauth2application:update" + SystemOAuth2ApplicationSecret Action = "system:oauth2application:secret" + SystemOAuth2ApplicationRemove Action = "system:oauth2application:remove" +) diff --git a/services/audit/appender.go b/services/audit/appender.go new file mode 100644 index 0000000000000..5a886c7e70a0c --- /dev/null +++ b/services/audit/appender.go @@ -0,0 +1,40 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package audit + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/system" + "code.gitea.io/gitea/modules/log" +) + +type Appender interface { + Record(context.Context, *Event) + Close() +} + +// NoticeAppender creates an admin notice for every audit event +type NoticeAppender struct{} + +func (a *NoticeAppender) Record(ctx context.Context, e *Event) { + m := fmt.Sprintf("%s\n\nDoer: %s\nScope: %s[%v] %s\nTarget: %s[%v] %s", e.Message, e.Doer.FriendlyName, e.Scope.Type, e.Scope.PrimaryKey, e.Scope.FriendlyName, e.Target.Type, e.Target.PrimaryKey, e.Target.FriendlyName) + if err := system.CreateNotice(ctx, system.NoticeAudit, m); err != nil { + log.Error("CreateNotice: %v", err) + } +} + +func (a *NoticeAppender) Close() { +} + +// LogAppender writes an info log entry for every audit event +type LogAppender struct{} + +func (a *LogAppender) Record(ctx context.Context, e *Event) { + log.Info(e.Message) +} + +func (a *LogAppender) Close() { +} diff --git a/services/audit/audit.go b/services/audit/audit.go new file mode 100644 index 0000000000000..621b4bee9b4b3 --- /dev/null +++ b/services/audit/audit.go @@ -0,0 +1,155 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package audit + +import ( + "fmt" + "time" + + "code.gitea.io/gitea/models" + asymkey_model "code.gitea.io/gitea/models/asymkey" + auth_model "code.gitea.io/gitea/models/auth" + git_model "code.gitea.io/gitea/models/git" + organization_model "code.gitea.io/gitea/models/organization" + repository_model "code.gitea.io/gitea/models/repo" + secret_model "code.gitea.io/gitea/models/secret" + user_model "code.gitea.io/gitea/models/user" + webhook_model "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/graceful" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/queue" +) + +type TypeDescriptor struct { + Type string `json:"type"` + PrimaryKey any `json:"primary_key"` + FriendlyName string `json:"friendly_name"` + Target any `json:"-"` +} + +type Event struct { + Action Action `json:"action"` + Doer TypeDescriptor `json:"doer"` + Scope TypeDescriptor `json:"scope"` + Target TypeDescriptor `json:"target"` + Message string `json:"message"` + Time time.Time `json:"time"` +} + +// appender := make([]Appender, 0, 5) +var appender = []Appender{ + &NoticeAppender{}, + &LogAppender{}, +} + +var auditQueue queue.Queue + +func Init() { + /*if !setting.Audit.Enabled { + return + }*/ + + auditQueue = queue.CreateQueue( + "audit", + func(data ...queue.Data) []queue.Data { + ctx := graceful.GetManager().ShutdownContext() + + for _, d := range data { + e := d.(*Event) + + for _, a := range appender { + a.Record(ctx, e) + } + } + return nil + }, + &Event{}, + ) + + go graceful.GetManager().RunWithShutdownFns(auditQueue.Run) +} + +func Record(action Action, doer *user_model.User, scope, target any, format string, v ...interface{}) { + /*if !setting.Audit.Enabled { + return + }*/ + + e := &Event{ + Action: action, + Doer: typeToDescription(doer), + Scope: scopeToDescription(scope), + Target: typeToDescription(target), + Message: fmt.Sprintf(format, v...), + Time: time.Now(), + } + + if err := auditQueue.Push(e); err != nil { + log.Error("Error pushing audit event to queue: %v", err) + } +} + +func scopeToDescription(scope any) TypeDescriptor { + if scope == nil { + return TypeDescriptor{"system", 0, "System", nil} + } + + switch s := scope.(type) { + case *repository_model.Repository, *user_model.User, *organization_model.Organization: + return typeToDescription(scope) + default: + panic(fmt.Sprintf("unsupported scope type: %T", s)) + } +} + +func typeToDescription(val any) TypeDescriptor { + switch t := val.(type) { + case *repository_model.Repository: + return TypeDescriptor{"repository", t.ID, t.FullName(), val} + case *user_model.User: + if t.IsOrganization() { + return TypeDescriptor{"organization", t.ID, t.Name, val} + } + return TypeDescriptor{"user", t.ID, t.Name, val} + case *organization_model.Organization: + return TypeDescriptor{"organization", t.ID, t.Name, val} + case *user_model.EmailAddress: + return TypeDescriptor{"email_address", t.ID, t.Email, val} + case *organization_model.Team: + return TypeDescriptor{"team", t.ID, t.Name, val} + case *auth_model.TwoFactor: + return TypeDescriptor{"twofactor", t.ID, "", val} + case *auth_model.WebAuthnCredential: + return TypeDescriptor{"webauthn", t.ID, t.Name, val} + case *user_model.UserOpenID: + return TypeDescriptor{"openid", t.ID, t.URI, val} + case *auth_model.AccessToken: + return TypeDescriptor{"access_token", t.ID, t.Name, val} + case *auth_model.OAuth2Application: + return TypeDescriptor{"oauth2_application", t.ID, t.Name, val} + case *auth_model.OAuth2Grant: + return TypeDescriptor{"oauth2_grant", t.ID, "", val} + case *auth_model.Source: + return TypeDescriptor{"authentication_source", t.ID, t.Name, val} + case *user_model.ExternalLoginUser: + return TypeDescriptor{"external_account", t.ExternalID, t.ExternalID, val} + case *asymkey_model.PublicKey: + return TypeDescriptor{"public_key", t.ID, t.Fingerprint, val} + case *asymkey_model.GPGKey: + return TypeDescriptor{"gpg_key", t.ID, t.KeyID, val} + case *secret_model.Secret: + return TypeDescriptor{"secret", t.ID, t.Name, val} + case *webhook_model.Webhook: + return TypeDescriptor{"webhook", t.ID, t.URL, val} + case *git_model.ProtectedTag: + return TypeDescriptor{"protected_tag", t.ID, t.NamePattern, val} + case *git_model.ProtectedBranch: + return TypeDescriptor{"protected_branch", t.ID, t.RuleName, val} + case *repository_model.PushMirror: + return TypeDescriptor{"push_mirror", t.ID, "", val} + case *models.RepoTransfer: + return TypeDescriptor{"repo_transfer", t.ID, "", val} + default: + panic(fmt.Sprintf("unsupported type: %T", t)) + } +} diff --git a/services/audit/helper.go b/services/audit/helper.go new file mode 100644 index 0000000000000..36617b197c35c --- /dev/null +++ b/services/audit/helper.go @@ -0,0 +1,52 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package audit + +import ( + user_model "code.gitea.io/gitea/models/user" +) + +func NewCLIUser() *user_model.User { + return &user_model.User{ + ID: -1, + Name: "CLI", + LowerName: "cli", + } +} + +func NewAuthenticationSourceUser() *user_model.User { + return &user_model.User{ + ID: -1, + Name: "AuthenticationSource", + LowerName: "authenticationsource", + } +} + +func UserActiveString(isActive bool) string { + if isActive { + return "active" + } + return "inactive" +} + +func UserAdminString(isAdmin bool) string { + if isAdmin { + return "admin" + } + return "normal user" +} + +func UserRestrictedString(isRestricted bool) string { + if isRestricted { + return "restricted" + } + return "unrestricted" +} + +func PublicString(isPublic bool) string { + if isPublic { + return "public" + } + return "private" +} diff --git a/services/auth/reverseproxy.go b/services/auth/reverseproxy.go index 91acff90cc6c8..3748a8531e12f 100644 --- a/services/auth/reverseproxy.go +++ b/services/auth/reverseproxy.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web/middleware" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/mailer" gouuid "github.com/google/uuid" @@ -172,6 +173,8 @@ func (r *ReverseProxy) newUser(req *http.Request) *user_model.User { return nil } + audit.Record(audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + mailer.SendRegisterNotifyMail(user) return user diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index fba8da7934526..76db084c86959 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -13,6 +13,7 @@ import ( user_model "code.gitea.io/gitea/models/user" auth_module "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/audit" source_service "code.gitea.io/gitea/services/auth/source" "code.gitea.io/gitea/services/mailer" user_service "code.gitea.io/gitea/services/user" @@ -34,6 +35,8 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str isAttributeSSHPublicKeySet := len(strings.TrimSpace(source.AttributeSSHPublicKey)) > 0 // Update User admin flag if exist + isAdminChanged := false + isRestrictedChanged := false if isExist, err := user_model.IsUserExist(db.DefaultContext, 0, sr.Username); err != nil { return nil, err } else if isExist { @@ -49,11 +52,15 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str // Change existing admin flag only if AdminFilter option is set user.IsAdmin = sr.IsAdmin cols = append(cols, "is_admin") + + isAdminChanged = true } if !user.IsAdmin && len(source.RestrictedFilter) > 0 && user.IsRestricted != sr.IsRestricted { // Change existing restricted flag only if RestrictedFilter option is set user.IsRestricted = sr.IsRestricted cols = append(cols, "is_restricted") + + isRestrictedChanged = true } if len(cols) > 0 { err = user_model.UpdateUserCols(db.DefaultContext, user, cols...) @@ -65,9 +72,25 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str } if user != nil { - if isAttributeSSHPublicKeySet && asymkey_model.SynchronizePublicKeys(user, source.authSource, sr.SSHPublicKey) { - if err := asymkey_model.RewriteAllPublicKeys(); err != nil { - return user, err + if isAdminChanged { + audit.Record(audit.UserAdmin, audit.NewAuthenticationSourceUser(), user, user, "Admin status of user %s changed to %s.", user.Name, audit.UserAdminString(user.IsAdmin)) + } + if isRestrictedChanged { + audit.Record(audit.UserRestricted, audit.NewAuthenticationSourceUser(), user, user, "Restricted status of user %s changed to %s.", user.Name, audit.UserRestrictedString(user.IsRestricted)) + } + + if isAttributeSSHPublicKeySet { + if addedKeys, deletedKeys := asymkey_model.SynchronizePublicKeys(user, source.authSource, sr.SSHPublicKey); len(addedKeys) > 0 || len(deletedKeys) > 0 { + for _, key := range addedKeys { + audit.Record(audit.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), user, user, "Added SSH key %s.", key.Fingerprint) + } + for _, key := range deletedKeys { + audit.Record(audit.UserKeySSHRemove, audit.NewAuthenticationSourceUser(), user, user, "Removed SSH key %s.", key.Fingerprint) + } + + if err := asymkey_model.RewriteAllPublicKeys(); err != nil { + return user, err + } } } } else { @@ -100,11 +123,19 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str return user, err } + audit.Record(audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + mailer.SendRegisterNotifyMail(user) - if isAttributeSSHPublicKeySet && asymkey_model.AddPublicKeysBySource(user, source.authSource, sr.SSHPublicKey) { - if err := asymkey_model.RewriteAllPublicKeys(); err != nil { - return user, err + if isAttributeSSHPublicKeySet { + if addedKeys := asymkey_model.AddPublicKeysBySource(user, source.authSource, sr.SSHPublicKey); len(addedKeys) > 0 { + for _, key := range addedKeys { + audit.Record(audit.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), user, user, "Added SSH key %s.", key.Fingerprint) + } + + if err := asymkey_model.RewriteAllPublicKeys(); err != nil { + return user, err + } } } if len(source.AttributeAvatar) > 0 { diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index 4571ff6540c3e..0bbc26bd8f6b1 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -16,6 +16,7 @@ import ( auth_module "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/audit" source_service "code.gitea.io/gitea/services/auth/source" user_service "code.gitea.io/gitea/services/user" ) @@ -126,22 +127,37 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { err = user_model.CreateUser(usr, overwriteDefault) if err != nil { log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.authSource.Name, su.Username, err) - } + } else { + audit.Record(audit.UserCreate, audit.NewAuthenticationSourceUser(), usr, usr, "Created user %s.", usr.Name) - if err == nil && isAttributeSSHPublicKeySet { - log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", source.authSource.Name, usr.Name) - if asymkey_model.AddPublicKeysBySource(usr, source.authSource, su.SSHPublicKey) { - sshKeysNeedUpdate = true + if isAttributeSSHPublicKeySet { + log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", source.authSource.Name, usr.Name) + if addedKeys := asymkey_model.AddPublicKeysBySource(usr, source.authSource, su.SSHPublicKey); len(addedKeys) > 0 { + sshKeysNeedUpdate = true + + for _, key := range addedKeys { + audit.Record(audit.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), usr, usr, "Added SSH key %s.", key.Fingerprint) + } + } } - } - if err == nil && len(source.AttributeAvatar) > 0 { - _ = user_service.UploadAvatar(usr, su.Avatar) + if len(source.AttributeAvatar) > 0 { + _ = user_service.UploadAvatar(usr, su.Avatar) + } } } else if updateExisting { // Synchronize SSH Public Key if that attribute is set - if isAttributeSSHPublicKeySet && asymkey_model.SynchronizePublicKeys(usr, source.authSource, su.SSHPublicKey) { - sshKeysNeedUpdate = true + if isAttributeSSHPublicKeySet { + if addedKeys, deletedKeys := asymkey_model.SynchronizePublicKeys(usr, source.authSource, su.SSHPublicKey); len(addedKeys) > 0 || len(deletedKeys) > 0 { + sshKeysNeedUpdate = true + + for _, key := range addedKeys { + audit.Record(audit.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), usr, usr, "Added SSH key %s.", key.Fingerprint) + } + for _, key := range deletedKeys { + audit.Record(audit.UserKeySSHRemove, audit.NewAuthenticationSourceUser(), usr, usr, "Removed SSH key %s.", key.Fingerprint) + } + } } // Check if user data has changed @@ -157,19 +173,34 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { emailChanged := usr.Email != su.Mail usr.Email = su.Mail // Change existing admin flag only if AdminFilter option is set + isAdminChanged := false if len(source.AdminFilter) > 0 { + isAdminChanged = usr.IsAdmin != su.IsAdmin usr.IsAdmin = su.IsAdmin } // Change existing restricted flag only if RestrictedFilter option is set + isRestrictedChanged := false if !usr.IsAdmin && len(source.RestrictedFilter) > 0 { + isRestrictedChanged = usr.IsRestricted != su.IsRestricted usr.IsRestricted = su.IsRestricted } + isActiveChanged := !usr.IsActive usr.IsActive = true err = user_model.UpdateUser(ctx, usr, emailChanged, "full_name", "email", "is_admin", "is_restricted", "is_active") if err != nil { log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", source.authSource.Name, usr.Name, err) } + + if isActiveChanged { + audit.Record(audit.UserActive, audit.NewAuthenticationSourceUser(), usr, usr, "Activation status of user %s changed to %s.", usr.Name, audit.UserActiveString(usr.IsActive)) + } + if isAdminChanged { + audit.Record(audit.UserAdmin, audit.NewAuthenticationSourceUser(), usr, usr, "Admin status of user %s changed to %s.", usr.Name, audit.UserAdminString(usr.IsAdmin)) + } + if isRestrictedChanged { + audit.Record(audit.UserRestricted, audit.NewAuthenticationSourceUser(), usr, usr, "Restricted status of user %s changed to %s.", usr.Name, audit.UserRestrictedString(usr.IsRestricted)) + } } if usr.IsUploadAvatarChanged(su.Avatar) { @@ -212,9 +243,11 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { log.Trace("SyncExternalUsers[%s]: Deactivating user %s", source.authSource.Name, usr.Name) usr.IsActive = false - err = user_model.UpdateUserCols(ctx, usr, "is_active") - if err != nil { + + if err := user_model.UpdateUserCols(ctx, usr, "is_active"); err != nil { log.Error("SyncExternalUsers[%s]: Error deactivating user %s: %v", source.authSource.Name, usr.Name, err) + } else { + audit.Record(audit.UserActive, audit.NewAuthenticationSourceUser(), usr, usr, "Activation status of user %s changed to %s.", usr.Name, audit.UserActiveString(usr.IsActive)) } } } diff --git a/services/auth/source/pam/source_authenticate.go b/services/auth/source/pam/source_authenticate.go index 48cd905a0a5e6..eb0ddc43caf1a 100644 --- a/services/auth/source/pam/source_authenticate.go +++ b/services/auth/source/pam/source_authenticate.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/modules/auth/pam" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/mailer" "github.com/google/uuid" @@ -67,6 +68,8 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str return user, err } + audit.Record(audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + mailer.SendRegisterNotifyMail(user) return user, nil diff --git a/services/auth/source/smtp/source_authenticate.go b/services/auth/source/smtp/source_authenticate.go index 36b351198acba..a4375a9053ca5 100644 --- a/services/auth/source/smtp/source_authenticate.go +++ b/services/auth/source/smtp/source_authenticate.go @@ -12,6 +12,7 @@ import ( auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/mailer" ) @@ -82,6 +83,8 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str return user, err } + audit.Record(audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + mailer.SendRegisterNotifyMail(user) return user, nil diff --git a/services/auth/source/source_group_sync.go b/services/auth/source/source_group_sync.go index 20b6095345377..43598f00ddb11 100644 --- a/services/auth/source/source_group_sync.go +++ b/services/auth/source/source_group_sync.go @@ -12,6 +12,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/services/audit" ) type syncType int @@ -104,11 +105,15 @@ func syncGroupsToTeamsCached(ctx context.Context, user *user_model.User, orgTeam log.Error("group sync: Could not add user to team: %v", err) return err } + + audit.Record(audit.OrganizationTeamMemberAdd, audit.NewAuthenticationSourceUser(), org, team, "User %s was added to team %s/%s.", user.Name, org.Name, team.Name) } else if action == syncRemove && isMember { if err := models.RemoveTeamMember(team, user.ID); err != nil { log.Error("group sync: Could not remove user from team: %v", err) return err } + + audit.Record(audit.OrganizationTeamMemberRemove, audit.NewAuthenticationSourceUser(), org, team, "User %s was removed from team %s/%s.", user.Name, org.Name, team.Name) } } } diff --git a/services/auth/sspi_windows.go b/services/auth/sspi_windows.go index b6e8d42980914..57fe828f8e7a5 100644 --- a/services/auth/sspi_windows.go +++ b/services/auth/sspi_windows.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web/middleware" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/auth/source/sspi" "code.gitea.io/gitea/services/mailer" @@ -197,6 +198,8 @@ func (s *SSPI) newUser(username string, cfg *sspi.Source) (*user_model.User, err return nil, err } + audit.Record(audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + mailer.SendRegisterNotifyMail(user) return user, nil diff --git a/services/externalaccount/user.go b/services/externalaccount/user.go index 87d2e02b48582..3e36565f8b3cb 100644 --- a/services/externalaccount/user.go +++ b/services/externalaccount/user.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/services/audit" "github.com/markbates/goth" ) @@ -51,6 +52,8 @@ func LinkAccountToUser(user *user_model.User, gothUser goth.User) error { return err } + audit.Record(audit.UserExternalLoginAdd, user, user, externalLoginUser, "Added external login %s for user %s using provider %s.", externalLoginUser.ExternalID, user.Name, gothUser.Provider) + externalID := externalLoginUser.ExternalID var tp structs.GitServiceType diff --git a/services/org/org.go b/services/org/org.go index e45fb305debe8..7c3dcb33ee6ec 100644 --- a/services/org/org.go +++ b/services/org/org.go @@ -14,10 +14,11 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/audit" ) // DeleteOrganization completely and permanently deletes everything of organization. -func DeleteOrganization(org *organization.Organization) error { +func DeleteOrganization(doer *user_model.User, org *organization.Organization) error { ctx, commiter, err := db.TxContext(db.DefaultContext) if err != nil { return err @@ -47,6 +48,8 @@ func DeleteOrganization(org *organization.Organization) error { return err } + audit.Record(audit.OrganizationDelete, doer, org, org, "Organization %s was deleted.", org.Name) + // FIXME: system notice // Note: There are something just cannot be roll back, // so just keep error logs of those operations. diff --git a/services/org/org_test.go b/services/org/org_test.go index cc22595c6f198..3b6692d9ebbd7 100644 --- a/services/org/org_test.go +++ b/services/org/org_test.go @@ -23,18 +23,18 @@ func TestMain(m *testing.M) { func TestDeleteOrganization(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 6}) - assert.NoError(t, DeleteOrganization(org)) + assert.NoError(t, DeleteOrganization(user, org)) unittest.AssertNotExistsBean(t, &organization.Organization{ID: 6}) unittest.AssertNotExistsBean(t, &organization.OrgUser{OrgID: 6}) unittest.AssertNotExistsBean(t, &organization.Team{OrgID: 6}) org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) - err := DeleteOrganization(org) + err := DeleteOrganization(user, org) assert.Error(t, err) assert.True(t, models.IsErrUserOwnRepos(err)) - user := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 5}) - assert.Error(t, DeleteOrganization(user)) + assert.Error(t, DeleteOrganization(user, organization.OrgFromUser(user))) unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{}) } diff --git a/services/repository/fork.go b/services/repository/fork.go index fb93b10f1c312..37443220cc84e 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -19,6 +19,7 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/audit" ) // ErrForkAlreadyExist represents a "ForkAlreadyExist" kind of error. @@ -189,7 +190,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork } // ConvertForkToNormalRepository convert the provided repo from a forked repo to normal repo -func ConvertForkToNormalRepository(ctx context.Context, repo *repo_model.Repository) error { +func ConvertForkToNormalRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository) error { err := db.WithTx(ctx, func(ctx context.Context) error { repo, err := repo_model.GetRepositoryByID(ctx, repo.ID) if err != nil { @@ -215,6 +216,11 @@ func ConvertForkToNormalRepository(ctx context.Context, repo *repo_model.Reposit return nil }) + if err != nil { + return err + } + + audit.Record(audit.RepositoryConvertFork, doer, repo, repo, "Converted repository %s from fork to regular repository.", repo.FullName()) - return err + return nil } diff --git a/services/repository/repository.go b/services/repository/repository.go index 000b1a3da6bce..8ff3b817495d9 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/notification" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/audit" pull_service "code.gitea.io/gitea/services/pull" ) @@ -33,6 +34,8 @@ func CreateRepository(ctx context.Context, doer, owner *user_model.User, opts re notification.NotifyCreateRepository(ctx, doer, owner, repo) + audit.Record(audit.RepositoryCreate, doer, repo, repo, "Created repository %s.", repo.FullName()) // owner + return repo, nil } @@ -51,7 +54,13 @@ func DeleteRepository(ctx context.Context, doer *user_model.User, repo *repo_mod return err } - return packages_model.UnlinkRepositoryFromAllPackages(ctx, repo.ID) + if err := packages_model.UnlinkRepositoryFromAllPackages(ctx, repo.ID); err != nil { + return err + } + + audit.Record(audit.RepositoryDelete, doer, repo, repo, "Deleted repository %s.", repo.FullName()) // repo.Owner load? + + return nil } // PushCreateRepo creates a repository when a new repository is pushed to an appropriate namespace diff --git a/services/repository/transfer.go b/services/repository/transfer.go index b9b26f314c2c3..9354abce60fa1 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/modules/notification" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/sync" + "code.gitea.io/gitea/services/audit" ) // repoWorkingPool represents a working pool to order the parallel changes to the same repository @@ -48,10 +49,14 @@ func TransferOwnership(ctx context.Context, doer, newOwner *user_model.User, rep return err } + audit.Record(audit.RepositoryTransferAccept, doer, newRepo, newRepo, "Accepted repository transfer from %s to %s.", oldOwner.Name, newRepo.FullName()) + for _, team := range teams { if err := models.AddRepository(ctx, team, newRepo); err != nil { return err } + + audit.Record(audit.RepositoryCollaboratorTeamAdd, doer, newRepo, team, "Added team %s as collaborator for %s.", team.Name, newRepo.FullName()) } notification.NotifyTransferRepository(ctx, doer, repo, oldOwner.Name) @@ -77,6 +82,9 @@ func ChangeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo repoWorkingPool.CheckOut(fmt.Sprint(repo.ID)) repo.Name = newRepoName + + audit.Record(audit.RepositoryName, doer, repo, repo, "Repository name changed from %s to %s.", oldRepoName, newRepoName) + notification.NotifyRenameRepository(ctx, doer, repo, oldRepoName) return nil diff --git a/services/user/user.go b/services/user/user.go index d52a2f404bcf0..3befe8293ff73 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -24,29 +24,37 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/audit" + organization_service "code.gitea.io/gitea/services/org" "code.gitea.io/gitea/services/packages" ) // RenameUser renames a user -func RenameUser(ctx context.Context, u *user_model.User, newUserName string) error { +func RenameUser(ctx context.Context, doer, u *user_model.User, newUserName string) error { ctx, committer, err := db.TxContext(ctx) if err != nil { return err } defer committer.Close() + + oldUserName := u.Name + if err := renameUser(ctx, u, newUserName); err != nil { return err } if err := committer.Commit(); err != nil { return err } - return err + + audit.Record(audit.UserName, doer, u, u, "User %s changed name to %s.", oldUserName, newUserName) + + return nil } // DeleteUser completely and permanently deletes everything of a user, // but issues/comments/pulls will be kept and shown as someone has been deleted, // unless the user is younger than USER_DELETE_WITH_COMMENTS_MAX_DAYS. -func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error { +func DeleteUser(ctx context.Context, doer, u *user_model.User, purge bool) error { if u.IsOrganization() { return fmt.Errorf("%s is an organization not a user", u.Name) } @@ -129,7 +137,7 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error { for _, org := range orgs { if err := models.RemoveOrgUser(org.ID, u.ID); err != nil { if organization.IsErrLastOrgOwner(err) { - err = organization.DeleteOrganization(ctx, org) + err = organization_service.DeleteOrganization(doer, org) } if err != nil { return fmt.Errorf("unable to remove user %s[%d] from org %s[%d]. Error: %w", u.Name, u.ID, org.Name, org.ID, err) @@ -213,6 +221,8 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error { } } + audit.Record(audit.UserDelete, doer, u, u, "User %s was deleted.", u.Name) + return nil } @@ -230,7 +240,7 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error { return db.ErrCancelledf("Before delete inactive user %s", u.Name) default: } - if err := DeleteUser(ctx, u, false); err != nil { + if err := DeleteUser(ctx, user_model.NewGhostUser(), u, false); err != nil { // Ignore users that were set inactive by admin. if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) || models.IsErrUserOwnPackages(err) { continue diff --git a/services/user/user_test.go b/services/user/user_test.go index a25804fcebffd..81c4beaf44a75 100644 --- a/services/user/user_test.go +++ b/services/user/user_test.go @@ -32,7 +32,7 @@ func TestDeleteUser(t *testing.T) { ownedRepos := make([]*repo_model.Repository, 0, 10) assert.NoError(t, db.GetEngine(db.DefaultContext).Find(&ownedRepos, &repo_model.Repository{OwnerID: userID})) if len(ownedRepos) > 0 { - err := DeleteUser(db.DefaultContext, user, false) + err := DeleteUser(db.DefaultContext, user, user, false) assert.Error(t, err) assert.True(t, models.IsErrUserOwnRepos(err)) return @@ -46,7 +46,7 @@ func TestDeleteUser(t *testing.T) { return } } - assert.NoError(t, DeleteUser(db.DefaultContext, user, false)) + assert.NoError(t, DeleteUser(db.DefaultContext, user, user, false)) unittest.AssertNotExistsBean(t, &user_model.User{ID: userID}) unittest.CheckConsistencyFor(t, &user_model.User{}, &repo_model.Repository{}) } @@ -56,7 +56,7 @@ func TestDeleteUser(t *testing.T) { test(11) org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) - assert.Error(t, DeleteUser(db.DefaultContext, org, false)) + assert.Error(t, DeleteUser(db.DefaultContext, org, org, false)) } func TestPurgeUser(t *testing.T) { @@ -64,7 +64,7 @@ func TestPurgeUser(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID}) - err := DeleteUser(db.DefaultContext, user, true) + err := DeleteUser(db.DefaultContext, user, user, true) assert.NoError(t, err) unittest.AssertNotExistsBean(t, &user_model.User{ID: userID}) @@ -76,7 +76,7 @@ func TestPurgeUser(t *testing.T) { test(11) org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) - assert.Error(t, DeleteUser(db.DefaultContext, org, false)) + assert.Error(t, DeleteUser(db.DefaultContext, org, org, false)) } func TestCreateUser(t *testing.T) { @@ -91,7 +91,7 @@ func TestCreateUser(t *testing.T) { assert.NoError(t, user_model.CreateUser(user)) - assert.NoError(t, DeleteUser(db.DefaultContext, user, false)) + assert.NoError(t, DeleteUser(db.DefaultContext, user, user, false)) } func TestCreateUser_Issue5882(t *testing.T) { @@ -120,6 +120,6 @@ func TestCreateUser_Issue5882(t *testing.T) { assert.Equal(t, !u.AllowCreateOrganization, v.disableOrgCreation) - assert.NoError(t, DeleteUser(db.DefaultContext, v.user, false)) + assert.NoError(t, DeleteUser(db.DefaultContext, v.user, v.user, false)) } } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index e5cb2db02bdeb..3e0e2117e3fbc 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -21,6 +21,7 @@ import ( "code.gitea.io/gitea/modules/sync" "code.gitea.io/gitea/modules/util" asymkey_service "code.gitea.io/gitea/services/asymkey" + "code.gitea.io/gitea/services/audit" ) var ( @@ -378,11 +379,14 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model } // DeleteWiki removes the actual and local copy of repository wiki. -func DeleteWiki(ctx context.Context, repo *repo_model.Repository) error { +func DeleteWiki(ctx context.Context, doer *user_model.User, repo *repo_model.Repository) error { if err := repo_model.UpdateRepositoryUnits(repo, nil, []unit.Type{unit.TypeWiki}); err != nil { return err } system_model.RemoveAllWithNotice(ctx, "Delete repository wiki", repo.WikiPath()) + + audit.Record(audit.RepositoryWikiDelete, doer, repo, repo, "Deleted wiki data of repository %s.", repo.FullName()) + return nil } From 2dcff01e2ca1f9d626262717cf1174c17d9c9757 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Tue, 18 Apr 2023 19:13:00 +0100 Subject: [PATCH 02/44] Added file logging. --- modules/log/file.go | 210 ++--------------- modules/setting/audit.go | 62 +++++ modules/setting/setting.go | 1 + .../rotating_file_writer.go | 214 ++++++++++++++++++ routers/private/manager.go | 7 + routers/web/admin/auths.go | 4 +- services/audit/appender.go | 47 +++- services/audit/audit.go | 71 +++++- services/auth/source.go | 8 +- 9 files changed, 419 insertions(+), 205 deletions(-) create mode 100644 modules/setting/audit.go create mode 100644 modules/util/rotating_file_writer/rotating_file_writer.go diff --git a/modules/log/file.go b/modules/log/file.go index 16fe26f84d26d..d33fca1dea6f5 100644 --- a/modules/log/file.go +++ b/modules/log/file.go @@ -1,74 +1,40 @@ // Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package log import ( - "bufio" "compress/gzip" "errors" "fmt" "os" - "path/filepath" - "strings" - "sync" - "time" "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/util/rotating_file_writer" ) // FileLogger implements LoggerProvider. // It writes messages by lines limit, file size limit, or time frequency. type FileLogger struct { WriterLogger - mw *MuxWriter + + rfw *rotating_file_writer.RotatingFileWriter + // The opened file Filename string `json:"filename"` // Rotate at size - Maxsize int `json:"maxsize"` - maxsizeCursize int + Maxsize int64 `json:"maxsize"` // Rotate daily - Daily bool `json:"daily"` - Maxdays int64 `json:"maxdays"` - dailyOpenDate int + Daily bool `json:"daily"` + Maxdays int `json:"maxdays"` Rotate bool `json:"rotate"` Compress bool `json:"compress"` CompressionLevel int `json:"compressionLevel"` - - startLock sync.Mutex // Only one log can write to the file -} - -// MuxWriter an *os.File writer with locker. -type MuxWriter struct { - mu sync.Mutex - fd *os.File - owner *FileLogger -} - -// Write writes to os.File. -func (mw *MuxWriter) Write(b []byte) (int, error) { - mw.mu.Lock() - defer mw.mu.Unlock() - mw.owner.docheck(len(b)) - return mw.fd.Write(b) -} - -// Close the internal writer -func (mw *MuxWriter) Close() error { - return mw.fd.Close() -} - -// SetFd sets os.File in writer. -func (mw *MuxWriter) SetFd(fd *os.File) { - if mw.fd != nil { - mw.fd.Close() - } - mw.fd = fd } // NewFileLogger create a FileLogger returning as LoggerProvider. @@ -83,9 +49,6 @@ func NewFileLogger() LoggerProvider { CompressionLevel: gzip.DefaultCompression, } log.Level = TRACE - // use MuxWriter instead direct use os.File for lock write when rotate - log.mw = new(MuxWriter) - log.mw.owner = log return log } @@ -107,150 +70,33 @@ func (log *FileLogger) Init(config string) error { if len(log.Filename) == 0 { return errors.New("config must have filename") } - // set MuxWriter as Logger's io.Writer - log.NewWriterLogger(log.mw) - return log.StartLogger() -} -// StartLogger start file logger. create log file and set to locker-inside file writer. -func (log *FileLogger) StartLogger() error { - fd, err := log.createLogFile() + rfw, err := rotating_file_writer.Open( + log.Filename, + &rotating_file_writer.Options{ + MaximumSize: log.Maxsize, + RotateDaily: log.Daily, + KeepDays: log.Maxdays, + Rotate: log.Rotate, + Compress: log.Compress, + CompressionLevel: log.CompressionLevel, + }, + ) if err != nil { return err } - log.mw.SetFd(fd) - return log.initFd() -} -func (log *FileLogger) docheck(size int) { - log.startLock.Lock() - defer log.startLock.Unlock() - if log.Rotate && ((log.Maxsize > 0 && log.maxsizeCursize >= log.Maxsize) || - (log.Daily && time.Now().Day() != log.dailyOpenDate)) { - if err := log.DoRotate(); err != nil { - fmt.Fprintf(os.Stderr, "FileLogger(%q): %s\n", log.Filename, err) - return - } - } - log.maxsizeCursize += size -} + log.rfw = rfw -func (log *FileLogger) createLogFile() (*os.File, error) { - // Open the log file - return os.OpenFile(log.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o660) -} + log.NewWriterLogger(log.rfw) -func (log *FileLogger) initFd() error { - fd := log.mw.fd - finfo, err := fd.Stat() - if err != nil { - return fmt.Errorf("get stat: %w", err) - } - log.maxsizeCursize = int(finfo.Size()) - log.dailyOpenDate = time.Now().Day() return nil } // DoRotate means it need to write file in new file. // new file name like xx.log.2013-01-01.2 func (log *FileLogger) DoRotate() error { - _, err := os.Lstat(log.Filename) - if err == nil { // file exists - // Find the next available number - num := 1 - fname := "" - for ; err == nil && num <= 999; num++ { - fname = log.Filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num) - _, err = os.Lstat(fname) - if log.Compress && err != nil { - _, err = os.Lstat(fname + ".gz") - } - } - // return error if the last file checked still existed - if err == nil { - return fmt.Errorf("rotate: cannot find free log number to rename %s", log.Filename) - } - - fd := log.mw.fd - fd.Close() - - // close fd before rename - // Rename the file to its newfound home - if err = util.Rename(log.Filename, fname); err != nil { - return fmt.Errorf("Rotate: %w", err) - } - - if log.Compress { - go compressOldLogFile(fname, log.CompressionLevel) - } - - // re-start logger - if err = log.StartLogger(); err != nil { - return fmt.Errorf("Rotate StartLogger: %w", err) - } - - go log.deleteOldLog() - } - - return nil -} - -func compressOldLogFile(fname string, compressionLevel int) error { - reader, err := os.Open(fname) - if err != nil { - return err - } - defer reader.Close() - buffer := bufio.NewReader(reader) - fw, err := os.OpenFile(fname+".gz", os.O_WRONLY|os.O_CREATE, 0o660) - if err != nil { - return err - } - defer fw.Close() - zw, err := gzip.NewWriterLevel(fw, compressionLevel) - if err != nil { - return err - } - defer zw.Close() - _, err = buffer.WriteTo(zw) - if err != nil { - zw.Close() - fw.Close() - util.Remove(fname + ".gz") - return err - } - reader.Close() - return util.Remove(fname) -} - -func (log *FileLogger) deleteOldLog() { - dir := filepath.Dir(log.Filename) - _ = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) (returnErr error) { - defer func() { - if r := recover(); r != nil { - returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r) - } - }() - - if err != nil { - return err - } - if d.IsDir() { - return nil - } - info, err := d.Info() - if err != nil { - return err - } - if info.ModTime().Unix() < (time.Now().Unix() - 60*60*24*log.Maxdays) { - if strings.HasPrefix(filepath.Base(path), filepath.Base(log.Filename)) { - if err := util.Remove(path); err != nil { - returnErr = fmt.Errorf("Failed to remove %s: %w", path, err) - } - } - } - return returnErr - }) + return log.rfw.DoRotate() } // Content returns the content accumulated in the content provider @@ -266,20 +112,12 @@ func (log *FileLogger) Content() (string, error) { // there are no buffering messages in file logger in memory. // flush file means sync file from disk. func (log *FileLogger) Flush() { - _ = log.mw.fd.Sync() + _ = log.rfw.Flush() } // ReleaseReopen releases and reopens log files func (log *FileLogger) ReleaseReopen() error { - closingErr := log.mw.fd.Close() - startingErr := log.StartLogger() - if startingErr != nil { - if closingErr != nil { - return fmt.Errorf("Error during closing: %v Error during starting: %v", closingErr, startingErr) - } - return startingErr - } - return closingErr + return log.rfw.ReleaseReopen() } // GetName returns the default name for this implementation diff --git a/modules/setting/audit.go b/modules/setting/audit.go new file mode 100644 index 0000000000000..0eaee6b72bd7d --- /dev/null +++ b/modules/setting/audit.go @@ -0,0 +1,62 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "compress/gzip" + "path/filepath" + "strings" + + "code.gitea.io/gitea/modules/log" +) + +var Audit = struct { + Enabled bool + Appender string + AppenderOptions map[string]*AppenderOptions +}{ + Enabled: false, + AppenderOptions: make(map[string]*AppenderOptions), +} + +type AppenderOptions struct { + Filename string + Rotate bool + MaximumSize int64 + RotateDaily bool + KeepDays int + Compress bool + CompressionLevel int +} + +func loadAuditFrom(rootCfg ConfigProvider) { + mustMapSetting(rootCfg, "audit", &Audit) + + for _, name := range strings.Split(Audit.Appender, ",") { + name = strings.TrimSpace(name) + if name == "" { + continue + } + + sec, err := rootCfg.GetSection("audit." + name) + if err != nil { + sec, _ = rootCfg.NewSection("audit." + name) + } + + opts := &AppenderOptions{ + Filename: filepath.Join(Log.RootPath, "audit.log"), + Rotate: false, + MaximumSize: 1 << 28, + RotateDaily: true, + KeepDays: 7, + CompressionLevel: gzip.DefaultCompression, + } + + if err := sec.MapTo(opts); err != nil { + log.Error(": %v", err.Error()) + } + + Audit.AppenderOptions[name] = opts + } +} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 4d7a7caab8de3..9947b42fb10e6 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -278,6 +278,7 @@ func loadCommonSettingsFrom(cfg ConfigProvider) { loadMirrorFrom(cfg) loadMarkupFrom(cfg) loadOtherFrom(cfg) + loadAuditFrom(cfg) } func loadRunModeFrom(rootCfg ConfigProvider) { diff --git a/modules/util/rotating_file_writer/rotating_file_writer.go b/modules/util/rotating_file_writer/rotating_file_writer.go new file mode 100644 index 0000000000000..0500e0ab25c69 --- /dev/null +++ b/modules/util/rotating_file_writer/rotating_file_writer.go @@ -0,0 +1,214 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package rotating_file_writer + +import ( + "bufio" + "compress/gzip" + "fmt" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "code.gitea.io/gitea/modules/util" +) + +type Options struct { + Rotate bool + MaximumSize int64 + RotateDaily bool + KeepDays int + Compress bool + CompressionLevel int +} + +type RotatingFileWriter struct { + mu sync.Mutex + fd *os.File + + currentSize int64 + openDate int + + options Options +} + +func Open(filename string, options *Options) (*RotatingFileWriter, error) { + if options == nil { + options = &Options{} + } + + rfw := &RotatingFileWriter{ + options: *options, + } + + if err := rfw.open(filename); err != nil { + return nil, err + } + + return rfw, nil +} + +func (rfw *RotatingFileWriter) Write(b []byte) (int, error) { + if rfw.options.Rotate && ((rfw.options.MaximumSize > 0 && rfw.currentSize+int64(len(b)) >= rfw.options.MaximumSize) || (rfw.options.RotateDaily && time.Now().Day() != rfw.openDate)) { + if err := rfw.DoRotate(); err != nil { + return 0, err + } + } + + n, err := rfw.fd.Write(b) + if err == nil { + rfw.currentSize += int64(n) + } + return n, err +} + +func (rfw *RotatingFileWriter) Flush() error { + return rfw.fd.Sync() +} + +func (rfw *RotatingFileWriter) Close() error { + return rfw.fd.Close() +} + +func (rfw *RotatingFileWriter) open(filename string) error { + if rfw.fd != nil { + if err := rfw.fd.Close(); err != nil { + return err + } + } + + fd, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o660) + if err != nil { + return err + } + + rfw.fd = fd + + finfo, err := fd.Stat() + if err != nil { + return err + } + rfw.currentSize = finfo.Size() + rfw.openDate = time.Now().Day() + + return nil +} + +func (rfw *RotatingFileWriter) ReleaseReopen() error { + return rfw.open(rfw.fd.Name()) +} + +// Rotate the log file creating a backup like xx.2013-01-01.2 +func (rfw *RotatingFileWriter) DoRotate() error { + if !rfw.options.Rotate { + return nil + } + + rfw.mu.Lock() + defer rfw.mu.Unlock() + + prefix := fmt.Sprintf("%s.%s.", rfw.fd.Name(), time.Now().Format("2006-01-02")) + + var err error + fname := "" + for i := 1; err == nil && i <= 999; i++ { + fname = prefix + fmt.Sprintf("%03d", i) + _, err = os.Lstat(fname) + if rfw.options.Compress && err != nil { + _, err = os.Lstat(fname + ".gz") + } + } + // return error if the last file checked still existed + if err == nil { + return fmt.Errorf("cannot find free file to rename %s", rfw.fd.Name()) + } + + fd := rfw.fd + if err := fd.Close(); err != nil { // close file before rename + return err + } + + rfw.fd = nil + + if err := util.Rename(fd.Name(), fname); err != nil { + return err + } + + if rfw.options.Compress { + go compressOldFile(fname, rfw.options.CompressionLevel) + } + + if err := rfw.open(fd.Name()); err != nil { + return err + } + + go deleteOldFiles( + filepath.Dir(fd.Name()), + filepath.Base(fd.Name()), + time.Now().AddDate(0, 0, -rfw.options.KeepDays), + ) + + return nil +} + +func compressOldFile(fname string, compressionLevel int) error { + reader, err := os.Open(fname) + if err != nil { + return err + } + defer reader.Close() + + buffer := bufio.NewReader(reader) + fw, err := os.OpenFile(fname+".gz", os.O_WRONLY|os.O_CREATE, 0o660) + if err != nil { + return err + } + defer fw.Close() + + zw, err := gzip.NewWriterLevel(fw, compressionLevel) + if err != nil { + return err + } + defer zw.Close() + + _, err = buffer.WriteTo(zw) + if err != nil { + zw.Close() + fw.Close() + util.Remove(fname + ".gz") + return err + } + reader.Close() + + return util.Remove(fname) +} + +func deleteOldFiles(dir, prefix string, removeBefore time.Time) { + _ = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) (returnErr error) { + defer func() { + if r := recover(); r != nil { + returnErr = fmt.Errorf("unable to delete old file '%s', error: %+v", path, r) + } + }() + + if err != nil { + return err + } + if d.IsDir() { + return nil + } + info, err := d.Info() + if err != nil { + return err + } + if info.ModTime().Before(removeBefore) { + if strings.HasPrefix(filepath.Base(path), prefix) { + return util.Remove(path) + } + } + return nil + }) +} diff --git a/routers/private/manager.go b/routers/private/manager.go index 38ad83326fe10..744022126b4ec 100644 --- a/routers/private/manager.go +++ b/routers/private/manager.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/services/audit" ) // FlushQueues flushes all the Queues @@ -64,6 +65,12 @@ func ReleaseReopenLogging(ctx *context.PrivateContext) { }) return } + if err := audit.ReleaseReopen(); err != nil { + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: fmt.Sprintf("Error during audit release and reopen: %v", err), + }) + return + } ctx.PlainText(http.StatusOK, "success") } diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 5cde0a01d57f2..4f5d22c84594e 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -461,7 +461,7 @@ func DeleteAuthSource(ctx *context.Context) { return } - if err = auth_service.DeleteSource(source); err != nil { + if err = auth_service.DeleteSource(ctx.Doer, source); err != nil { if auth.IsErrSourceInUse(err) { ctx.Flash.Error(ctx.Tr("admin.auths.still_in_used")) } else { @@ -473,8 +473,6 @@ func DeleteAuthSource(ctx *context.Context) { return } - audit.Record(audit.SystemAuthenticationSourceRemove, nil, ctx.Doer, source, "Removed authentication source %s.", source.Name) - log.Trace("Authentication deleted by admin(%s): %d", ctx.Doer.Name, source.ID) ctx.Flash.Success(ctx.Tr("admin.auths.deletion_success")) diff --git a/services/audit/appender.go b/services/audit/appender.go index 5a886c7e70a0c..26565243643fd 100644 --- a/services/audit/appender.go +++ b/services/audit/appender.go @@ -8,12 +8,15 @@ import ( "fmt" "code.gitea.io/gitea/models/system" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util/rotating_file_writer" ) type Appender interface { Record(context.Context, *Event) - Close() + Close() error + ReleaseReopen() error } // NoticeAppender creates an admin notice for every audit event @@ -26,7 +29,12 @@ func (a *NoticeAppender) Record(ctx context.Context, e *Event) { } } -func (a *NoticeAppender) Close() { +func (a *NoticeAppender) Close() error { + return nil +} + +func (a *NoticeAppender) ReleaseReopen() error { + return nil } // LogAppender writes an info log entry for every audit event @@ -36,5 +44,38 @@ func (a *LogAppender) Record(ctx context.Context, e *Event) { log.Info(e.Message) } -func (a *LogAppender) Close() { +func (a *LogAppender) Close() error { + return nil +} + +func (a *LogAppender) ReleaseReopen() error { + return nil +} + +// File writes json object for every audit event +type FileAppender struct { + rfw *rotating_file_writer.RotatingFileWriter +} + +func NewFileAppender(filename string, opts *rotating_file_writer.Options) (*FileAppender, error) { + rfw, err := rotating_file_writer.Open(filename, opts) + if err != nil { + return nil, err + } + + return &FileAppender{rfw}, nil +} + +func (a *FileAppender) Record(ctx context.Context, e *Event) { + if err := json.NewEncoder(a.rfw).Encode(e); err != nil { + log.Error("encoding event to file failed: %v", err) + } +} + +func (a *FileAppender) Close() error { + return a.rfw.Close() +} + +func (a *FileAppender) ReleaseReopen() error { + return a.rfw.ReleaseReopen() } diff --git a/services/audit/audit.go b/services/audit/audit.go index 621b4bee9b4b3..c0e3990aac646 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -5,6 +5,8 @@ package audit import ( "fmt" + "os" + "path" "time" "code.gitea.io/gitea/models" @@ -19,6 +21,8 @@ import ( "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/queue" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util/rotating_file_writer" ) type TypeDescriptor struct { @@ -37,18 +41,47 @@ type Event struct { Time time.Time `json:"time"` } -// appender := make([]Appender, 0, 5) -var appender = []Appender{ - &NoticeAppender{}, - &LogAppender{}, -} - -var auditQueue queue.Queue +var ( + appenders = make([]Appender, 0, 5) + auditQueue queue.Queue +) func Init() { - /*if !setting.Audit.Enabled { + if !setting.Audit.Enabled { return - }*/ + } + + for name, opts := range setting.Audit.AppenderOptions { + var a Appender + switch name { + case "log": + a = &LogAppender{} + case "notice": + a = &NoticeAppender{} + case "file": + if err := os.MkdirAll(path.Dir(opts.Filename), os.ModePerm); err != nil { + panic(err.Error()) + } + + fa, err := NewFileAppender(opts.Filename, &rotating_file_writer.Options{ + Rotate: opts.Rotate, + MaximumSize: opts.MaximumSize, + RotateDaily: opts.RotateDaily, + KeepDays: opts.KeepDays, + Compress: opts.Compress, + CompressionLevel: opts.CompressionLevel, + }) + if err != nil { + log.Error("Failed to create file appender: %v", err) + continue + } + a = fa + } + + if a != nil { + appenders = append(appenders, a) + } + } auditQueue = queue.CreateQueue( "audit", @@ -58,7 +91,7 @@ func Init() { for _, d := range data { e := d.(*Event) - for _, a := range appender { + for _, a := range appenders { a.Record(ctx, e) } } @@ -71,9 +104,9 @@ func Init() { } func Record(action Action, doer *user_model.User, scope, target any, format string, v ...interface{}) { - /*if !setting.Audit.Enabled { + if !setting.Audit.Enabled { return - }*/ + } e := &Event{ Action: action, @@ -153,3 +186,17 @@ func typeToDescription(val any) TypeDescriptor { panic(fmt.Sprintf("unsupported type: %T", t)) } } + +func ReleaseReopen() error { + var accumulatedErr error + for _, a := range appenders { + if err := a.ReleaseReopen(); err != nil { + if accumulatedErr == nil { + accumulatedErr = fmt.Errorf("error reopening: %w", err) + } else { + accumulatedErr = fmt.Errorf("error reopening: %v & %w", err, accumulatedErr) + } + } + } + return accumulatedErr +} diff --git a/services/auth/source.go b/services/auth/source.go index aae3a781024aa..8fd9f3de5dfbe 100644 --- a/services/auth/source.go +++ b/services/auth/source.go @@ -7,10 +7,11 @@ import ( "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/services/audit" ) // DeleteSource deletes a AuthSource record in DB. -func DeleteSource(source *auth.Source) error { +func DeleteSource(doer *user_model.User, source *auth.Source) error { count, err := db.GetEngine(db.DefaultContext).Count(&user_model.User{LoginSource: source.ID}) if err != nil { return err @@ -36,5 +37,10 @@ func DeleteSource(source *auth.Source) error { } _, err = db.GetEngine(db.DefaultContext).ID(source.ID).Delete(new(auth.Source)) + + if err == nil { + audit.Record(audit.SystemAuthenticationSourceRemove, doer, nil, source, "Removed authentication source %s.", source.Name) + } + return err } From 9d7b3c8e23f60ef999fb7d49e98d7de615a4142a Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 21 Apr 2023 11:29:51 +0000 Subject: [PATCH 03/44] Fixes. --- cmd/admin.go | 37 +++++++-- cmd/admin_auth_ldap.go | 82 ++++++++----------- cmd/admin_user_create.go | 6 ++ cmd/admin_user_delete.go | 5 +- cmd/admin_user_generate_access_token.go | 4 + modules/graceful/manager_unix.go | 1 + modules/log/file_test.go | 36 -------- modules/setting/audit.go | 2 +- modules/setting/setting.go | 3 +- .../rotating_file_writer.go | 24 +++--- .../rotating_file_writer_test.go | 48 +++++++++++ routers/api/v1/org/team.go | 24 +++++- routers/api/v1/repo/main_test.go | 1 - routers/api/v1/repo/teams.go | 11 +-- routers/api/v1/user/app.go | 3 +- routers/web/admin/auths.go | 2 +- routers/web/org/teams.go | 4 +- routers/web/repo/setting.go | 4 +- services/audit/audit.go | 11 +-- services/org/repo.go | 13 ++- services/org/repo_test.go | 5 +- services/user/user.go | 3 +- services/webhook/main_test.go | 1 - 23 files changed, 188 insertions(+), 142 deletions(-) create mode 100644 modules/util/rotating_file_writer/rotating_file_writer_test.go diff --git a/cmd/admin.go b/cmd/admin.go index f9fb1b6c68f07..6cb7bb6484dfa 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -5,6 +5,7 @@ package cmd import ( + "context" "errors" "fmt" "net/url" @@ -21,6 +22,7 @@ import ( "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/audit" auth_service "code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/auth/source/smtp" @@ -469,6 +471,7 @@ func runAddOauth(c *cli.Context) error { if err := initDB(ctx); err != nil { return err } + audit.Init() config := parseOAuth2Config(c) if config.Provider == "openidConnect" { @@ -478,7 +481,7 @@ func runAddOauth(c *cli.Context) error { } } - return auth_model.CreateSource(&auth_model.Source{ + return createSource(ctx, &auth_model.Source{ Type: auth_model.OAuth2, Name: c.String("name"), IsActive: true, @@ -497,6 +500,7 @@ func runUpdateOauth(c *cli.Context) error { if err := initDB(ctx); err != nil { return err } + audit.Init() source, err := auth_model.GetSourceByID(c.Int64("id")) if err != nil { @@ -589,7 +593,7 @@ func runUpdateOauth(c *cli.Context) error { oAuth2Config.CustomURLMapping = customURLMapping source.Cfg = oAuth2Config - return auth_model.UpdateSource(source) + return updateSource(ctx, source) } func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error { @@ -635,6 +639,7 @@ func runAddSMTP(c *cli.Context) error { if err := initDB(ctx); err != nil { return err } + audit.Init() if !c.IsSet("name") || len(c.String("name")) == 0 { return errors.New("name must be set") @@ -660,7 +665,7 @@ func runAddSMTP(c *cli.Context) error { smtpConfig.Auth = "PLAIN" } - return auth_model.CreateSource(&auth_model.Source{ + return createSource(ctx, &auth_model.Source{ Type: auth_model.SMTP, Name: c.String("name"), IsActive: active, @@ -679,6 +684,7 @@ func runUpdateSMTP(c *cli.Context) error { if err := initDB(ctx); err != nil { return err } + audit.Init() source, err := auth_model.GetSourceByID(c.Int64("id")) if err != nil { @@ -701,7 +707,27 @@ func runUpdateSMTP(c *cli.Context) error { source.Cfg = smtpConfig - return auth_model.UpdateSource(source) + return updateSource(ctx, source) +} + +func createSource(ctx context.Context, source *auth_model.Source) error { + if err := auth_model.CreateSource(source); err != nil { + return err + } + + audit.Record(audit.SystemAuthenticationSourceAdd, audit.NewCLIUser(), nil, source, "Created authentication source %s [%s].", source.Name, source.Type.String()) + + return nil +} + +func updateSource(ctx context.Context, source *auth_model.Source) error { + if err := auth_model.UpdateSource(source); err != nil { + return err + } + + audit.Record(audit.SystemAuthenticationSourceUpdate, audit.NewCLIUser(), nil, source, "Updated authentication source %s.", source.Name) + + return nil } func runListAuth(c *cli.Context) error { @@ -749,11 +775,12 @@ func runDeleteAuth(c *cli.Context) error { if err := initDB(ctx); err != nil { return err } + audit.Init() source, err := auth_model.GetSourceByID(c.Int64("id")) if err != nil { return err } - return auth_service.DeleteSource(source) + return auth_service.DeleteSource(audit.NewCLIUser(), source) } diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index 91276f221ff7c..90a1682abb6ed 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -9,6 +9,7 @@ import ( "strings" "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/auth/source/ldap" "github.com/urfave/cli" @@ -308,58 +309,26 @@ func (a *authService) getAuthSource(c *cli.Context, authType auth.Type) (*auth.S // addLdapBindDn adds a new LDAP via Bind DN authentication source. func (a *authService) addLdapBindDn(c *cli.Context) error { - if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil { - return err - } - - ctx, cancel := installSignals() - defer cancel() - - if err := a.initDB(ctx); err != nil { - return err - } - - authSource := &auth.Source{ - Type: auth.LDAP, - IsActive: true, // active by default - Cfg: &ldap.Source{ - Enabled: true, // always true - }, - } - - parseAuthSource(c, authSource) - if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil { - return err - } - - return a.createAuthSource(authSource) + return addLdapSource(c, auth.LDAP, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute") } // updateLdapBindDn updates a new LDAP via Bind DN authentication source. func (a *authService) updateLdapBindDn(c *cli.Context) error { - ctx, cancel := installSignals() - defer cancel() - - if err := a.initDB(ctx); err != nil { - return err - } - - authSource, err := a.getAuthSource(c, auth.LDAP) - if err != nil { - return err - } - - parseAuthSource(c, authSource) - if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil { - return err - } - - return a.updateAuthSource(authSource) + return updateLdapSource(c, auth.LDAP) } // addLdapSimpleAuth adds a new LDAP (simple auth) authentication source. func (a *authService) addLdapSimpleAuth(c *cli.Context) error { - if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil { + return addLdapSource(c, auth.DLDAP, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute") +} + +// updateLdapBindDn updates a new LDAP (simple auth) authentication source. +func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { + return updateLdapSource(c, auth.DLDAP) +} + +func addLdapSource(c *cli.Context, authType auth.Type, args ...string) error { + if err := argsSet(c, args...); err != nil { return err } @@ -369,9 +338,10 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error { if err := a.initDB(ctx); err != nil { return err } + audit.Init() authSource := &auth.Source{ - Type: auth.DLDAP, + Type: authType, IsActive: true, // active by default Cfg: &ldap.Source{ Enabled: true, // always true @@ -383,19 +353,25 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error { return err } - return a.createAuthSource(authSource) + if err := a.createAuthSource(authSource); err != nil { + return err + } + + audit.Record(audit.SystemAuthenticationSourceAdd, audit.NewCLIUser(), nil, authSource, "Created authentication source %s [%s].", authSource.Name, authSource.Type.String()) + + return nil } -// updateLdapBindDn updates a new LDAP (simple auth) authentication source. -func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { +func updateLdapSource(c *cli.Context, authType auth.Type) error { ctx, cancel := installSignals() defer cancel() if err := a.initDB(ctx); err != nil { return err } + audit.Init() - authSource, err := a.getAuthSource(c, auth.DLDAP) + authSource, err := a.getAuthSource(c, authType) if err != nil { return err } @@ -405,5 +381,11 @@ func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { return err } - return a.updateAuthSource(authSource) + if err := a.updateAuthSource(authSource); err != nil { + return err + } + + audit.Record(audit.SystemAuthenticationSourceUpdate, audit.NewCLIUser(), nil, authSource, "Updated authentication source %s.", authSource.Name) + + return nil } diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 09eaad54becf9..071442b4e7619 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -13,6 +13,7 @@ import ( pwd "code.gitea.io/gitea/modules/auth/password" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/audit" "github.com/urfave/cli" ) @@ -96,6 +97,7 @@ func runCreateUser(c *cli.Context) error { if err := initDB(ctx); err != nil { return err } + audit.Init() var password string if c.IsSet("password") { @@ -151,6 +153,8 @@ func runCreateUser(c *cli.Context) error { return fmt.Errorf("CreateUser: %w", err) } + audit.Record(audit.UserCreate, audit.NewCLIUser(), u, u, "Created user %s.", u.Name) + if c.Bool("access-token") { t := &auth_model.AccessToken{ Name: "gitea-admin", @@ -161,6 +165,8 @@ func runCreateUser(c *cli.Context) error { return err } + audit.Record(audit.UserAccessTokenAdd, audit.NewCLIUser(), u, t, "Added access token %s for user %s with scope %s.", t.Name, u.Name, t.Scope) + fmt.Printf("Access token was successfully created... %s\n", t.Token) } diff --git a/cmd/admin_user_delete.go b/cmd/admin_user_delete.go index 30d6d11576f0d..563b43f487981 100644 --- a/cmd/admin_user_delete.go +++ b/cmd/admin_user_delete.go @@ -9,6 +9,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/storage" + "code.gitea.io/gitea/services/audit" user_service "code.gitea.io/gitea/services/user" "github.com/urfave/cli" @@ -49,7 +50,7 @@ func runDeleteUser(c *cli.Context) error { if err := initDB(ctx); err != nil { return err } - + audit.Init() if err := storage.Init(); err != nil { return err } @@ -74,5 +75,5 @@ func runDeleteUser(c *cli.Context) error { return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id")) } - return user_service.DeleteUser(ctx, user, c.Bool("purge")) + return user_service.DeleteUser(ctx, audit.NewCLIUser(), user, c.Bool("purge")) } diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go index 822bc5c2bc77b..4dbcf91cd789c 100644 --- a/cmd/admin_user_generate_access_token.go +++ b/cmd/admin_user_generate_access_token.go @@ -8,6 +8,7 @@ import ( auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/services/audit" "github.com/urfave/cli" ) @@ -49,6 +50,7 @@ func runGenerateAccessToken(c *cli.Context) error { if err := initDB(ctx); err != nil { return err } + audit.Init() user, err := user_model.GetUserByName(ctx, c.String("username")) if err != nil { @@ -70,6 +72,8 @@ func runGenerateAccessToken(c *cli.Context) error { return err } + audit.Record(audit.UserAccessTokenAdd, audit.NewCLIUser(), user, t, "Added access token %s for user %s with scope %s.", t.Name, user.Name, t.Scope) + if c.Bool("raw") { fmt.Printf("%s\n", t.Token) } else { diff --git a/modules/graceful/manager_unix.go b/modules/graceful/manager_unix.go index ca6ccc1b6648a..ec756243059d6 100644 --- a/modules/graceful/manager_unix.go +++ b/modules/graceful/manager_unix.go @@ -139,6 +139,7 @@ func (g *Manager) handleSignals(ctx context.Context) { if err := log.ReleaseReopen(); err != nil { log.Error("Error whilst releasing and reopening logs: %v", err) } + // TODO audit.ReleaseReopen case syscall.SIGUSR2: log.Warn("PID %d. Received SIGUSR2. Hammering...", pid) g.DoImmediateHammer() diff --git a/modules/log/file_test.go b/modules/log/file_test.go index f1ca23d342bfd..630336a13f24e 100644 --- a/modules/log/file_test.go +++ b/modules/log/file_test.go @@ -4,9 +4,7 @@ package log import ( - "compress/gzip" "fmt" - "io" "os" "path/filepath" "strings" @@ -199,37 +197,3 @@ func TestCompressFileLogger(t *testing.T) { err = realFileLogger.DoRotate() assert.Error(t, err) } - -func TestCompressOldFile(t *testing.T) { - tmpDir := t.TempDir() - fname := filepath.Join(tmpDir, "test") - nonGzip := filepath.Join(tmpDir, "test-nonGzip") - - f, err := os.OpenFile(fname, os.O_CREATE|os.O_WRONLY, 0o660) - assert.NoError(t, err) - ng, err := os.OpenFile(nonGzip, os.O_CREATE|os.O_WRONLY, 0o660) - assert.NoError(t, err) - - for i := 0; i < 999; i++ { - f.WriteString("This is a test file\n") - ng.WriteString("This is a test file\n") - } - f.Close() - ng.Close() - - err = compressOldLogFile(fname, -1) - assert.NoError(t, err) - - _, err = os.Lstat(fname + ".gz") - assert.NoError(t, err) - - f, err = os.Open(fname + ".gz") - assert.NoError(t, err) - zr, err := gzip.NewReader(f) - assert.NoError(t, err) - data, err := io.ReadAll(zr) - assert.NoError(t, err) - original, err := os.ReadFile(nonGzip) - assert.NoError(t, err) - assert.Equal(t, original, data) -} diff --git a/modules/setting/audit.go b/modules/setting/audit.go index 0eaee6b72bd7d..70fc5ffa8da74 100644 --- a/modules/setting/audit.go +++ b/modules/setting/audit.go @@ -54,7 +54,7 @@ func loadAuditFrom(rootCfg ConfigProvider) { } if err := sec.MapTo(opts); err != nil { - log.Error(": %v", err.Error()) + log.Error("audit.%s: %v", name, err.Error()) } Audit.AppenderOptions[name] = opts diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 9947b42fb10e6..54e61a2932a08 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -278,6 +278,7 @@ func loadCommonSettingsFrom(cfg ConfigProvider) { loadMirrorFrom(cfg) loadMarkupFrom(cfg) loadOtherFrom(cfg) + loadQueueFrom(cfg) loadAuditFrom(cfg) } @@ -371,7 +372,7 @@ func LoadSettings() { loadMigrationsFrom(CfgProvider) loadIndexerFrom(CfgProvider) loadTaskFrom(CfgProvider) - LoadQueueSettings() + loadQueueFrom(CfgProvider) loadProjectFrom(CfgProvider) loadMimeTypeMapFrom(CfgProvider) loadFederationFrom(CfgProvider) diff --git a/modules/util/rotating_file_writer/rotating_file_writer.go b/modules/util/rotating_file_writer/rotating_file_writer.go index 0500e0ab25c69..11c6327574d1e 100644 --- a/modules/util/rotating_file_writer/rotating_file_writer.go +++ b/modules/util/rotating_file_writer/rotating_file_writer.go @@ -6,6 +6,7 @@ package rotating_file_writer import ( "bufio" "compress/gzip" + "errors" "fmt" "os" "path/filepath" @@ -52,9 +53,12 @@ func Open(filename string, options *Options) (*RotatingFileWriter, error) { } func (rfw *RotatingFileWriter) Write(b []byte) (int, error) { - if rfw.options.Rotate && ((rfw.options.MaximumSize > 0 && rfw.currentSize+int64(len(b)) >= rfw.options.MaximumSize) || (rfw.options.RotateDaily && time.Now().Day() != rfw.openDate)) { + if rfw.options.Rotate && ((rfw.options.MaximumSize > 0 && rfw.currentSize >= rfw.options.MaximumSize) || (rfw.options.RotateDaily && time.Now().Day() != rfw.openDate)) { if err := rfw.DoRotate(); err != nil { - return 0, err + // This should be + // return 0, err + // but the old behaviour does not return. This may lead to other errors. + fmt.Fprintf(os.Stderr, "RotatingFileWriter: %s\n", err) } } @@ -74,12 +78,6 @@ func (rfw *RotatingFileWriter) Close() error { } func (rfw *RotatingFileWriter) open(filename string) error { - if rfw.fd != nil { - if err := rfw.fd.Close(); err != nil { - return err - } - } - fd, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o660) if err != nil { return err @@ -98,7 +96,10 @@ func (rfw *RotatingFileWriter) open(filename string) error { } func (rfw *RotatingFileWriter) ReleaseReopen() error { - return rfw.open(rfw.fd.Name()) + return errors.Join( + rfw.fd.Close(), + rfw.open(rfw.fd.Name()), + ) } // Rotate the log file creating a backup like xx.2013-01-01.2 @@ -131,8 +132,6 @@ func (rfw *RotatingFileWriter) DoRotate() error { return err } - rfw.fd = nil - if err := util.Rename(fd.Name(), fname); err != nil { return err } @@ -178,8 +177,7 @@ func compressOldFile(fname string, compressionLevel int) error { if err != nil { zw.Close() fw.Close() - util.Remove(fname + ".gz") - return err + return errors.Join(err, util.Remove(fname+".gz")) } reader.Close() diff --git a/modules/util/rotating_file_writer/rotating_file_writer_test.go b/modules/util/rotating_file_writer/rotating_file_writer_test.go new file mode 100644 index 0000000000000..d25e8255e6a4a --- /dev/null +++ b/modules/util/rotating_file_writer/rotating_file_writer_test.go @@ -0,0 +1,48 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package rotating_file_writer + +import ( + "compress/gzip" + "io" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCompressOldFile(t *testing.T) { + tmpDir := t.TempDir() + fname := filepath.Join(tmpDir, "test") + nonGzip := filepath.Join(tmpDir, "test-nonGzip") + + f, err := os.OpenFile(fname, os.O_CREATE|os.O_WRONLY, 0o660) + assert.NoError(t, err) + ng, err := os.OpenFile(nonGzip, os.O_CREATE|os.O_WRONLY, 0o660) + assert.NoError(t, err) + + for i := 0; i < 999; i++ { + f.WriteString("This is a test file\n") + ng.WriteString("This is a test file\n") + } + f.Close() + ng.Close() + + err = compressOldFile(fname, gzip.DefaultCompression) + assert.NoError(t, err) + + _, err = os.Lstat(fname + ".gz") + assert.NoError(t, err) + + f, err = os.Open(fname + ".gz") + assert.NoError(t, err) + zr, err := gzip.NewReader(f) + assert.NoError(t, err) + data, err := io.ReadAll(zr) + assert.NoError(t, err) + original, err := os.ReadFile(nonGzip) + assert.NoError(t, err) + assert.Equal(t, original, data) +} diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index 88537a823b01d..8daa63f6b0692 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -277,6 +277,13 @@ func EditTeam(ctx *context.APIContext) { form := web.GetForm(ctx).(*api.EditTeamOption) team := ctx.Org.Team + + org, err := organization.GetOrgByID(ctx, team.OrgID) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetOrgByID", err) + return + } + if err := team.LoadUnits(ctx); err != nil { ctx.InternalServerError(err) return @@ -330,9 +337,9 @@ func EditTeam(ctx *context.APIContext) { return } - audit.Record(audit.OrganizationTeamUpdate, ctx.Doer, ctx.Org.Organization, team, "Updated settings of team %s/%s.", ctx.Org.Organization.Name, team.Name) + audit.Record(audit.OrganizationTeamUpdate, ctx.Doer, org, team, "Updated settings of team %s/%s.", org.Name, team.Name) if isAuthChanged { - audit.Record(audit.OrganizationTeamPermission, ctx.Doer, ctx.Org.Organization, team, "Permission of team %s/%s changed from %s to %s.", ctx.Org.Organization.Name, team.Name, oldAccessMode.String(), team.AccessMode.String()) + audit.Record(audit.OrganizationTeamPermission, ctx.Doer, org, team, "Permission of team %s/%s changed from %s to %s.", org.Name, team.Name, oldAccessMode.String(), team.AccessMode.String()) } apiTeam, err := convert.ToTeam(ctx, team) @@ -359,12 +366,18 @@ func DeleteTeam(ctx *context.APIContext) { // "204": // description: team deleted + org, err := organization.GetOrgByID(ctx, ctx.Org.Team.OrgID) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetOrgByID", err) + return + } + if err := models.DeleteTeam(ctx.Org.Team); err != nil { ctx.Error(http.StatusInternalServerError, "DeleteTeam", err) return } - audit.Record(audit.OrganizationTeamRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Team %s was removed from organziation %s.", ctx.Org.Team.Name, ctx.Org.Organization.Name) + audit.Record(audit.OrganizationTeamRemove, ctx.Doer, org, ctx.Org.Team, "Team %s was removed from organziation %s.", ctx.Org.Team.Name, org.Name) ctx.Status(http.StatusNoContent) } @@ -706,7 +719,7 @@ func AddTeamRepository(ctx *context.APIContext) { ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository") return } - if err := org_service.TeamAddRepository(ctx.Org.Team, repo); err != nil { + if err := org_service.TeamAddRepository(ctx.Doer, ctx.Org.Team, repo); err != nil { ctx.Error(http.StatusInternalServerError, "TeamAddRepository", err) return } @@ -760,6 +773,9 @@ func RemoveTeamRepository(ctx *context.APIContext) { ctx.Error(http.StatusInternalServerError, "RemoveRepository", err) return } + + audit.Record(audit.RepositoryCollaboratorTeamRemove, ctx.Doer, repo, ctx.Org.Team, "Removed team %s as collaborator from %s.", ctx.Org.Team.Name, repo.FullName()) + ctx.Status(http.StatusNoContent) } diff --git a/routers/api/v1/repo/main_test.go b/routers/api/v1/repo/main_test.go index c7466c493f015..5584ba63caa80 100644 --- a/routers/api/v1/repo/main_test.go +++ b/routers/api/v1/repo/main_test.go @@ -14,7 +14,6 @@ import ( func TestMain(m *testing.M) { setting.InitProviderAndLoadCommonSettingsForTest() - setting.LoadQueueSettings() unittest.MainTest(m, &unittest.TestOptions{ GiteaRootPath: filepath.Join("..", "..", "..", ".."), SetUp: webhook_service.Init, diff --git a/routers/api/v1/repo/teams.go b/routers/api/v1/repo/teams.go index d5a357d62c78a..1b65230ad68b9 100644 --- a/routers/api/v1/repo/teams.go +++ b/routers/api/v1/repo/teams.go @@ -200,25 +200,22 @@ func changeRepoTeam(ctx *context.APIContext, add bool) { ctx.Error(http.StatusUnprocessableEntity, "alreadyAdded", fmt.Errorf("team '%s' is already added to repo", team.Name)) return } - err = org_service.TeamAddRepository(team, ctx.Repo.Repository) + err = org_service.TeamAddRepository(ctx.Doer, team, ctx.Repo.Repository) } else { if !repoHasTeam { ctx.Error(http.StatusUnprocessableEntity, "notAdded", fmt.Errorf("team '%s' was not added to repo", team.Name)) return } err = models.RemoveRepository(team, ctx.Repo.Repository.ID) + if err == nil { + audit.Record(audit.RepositoryCollaboratorTeamRemove, ctx.Doer, ctx.Repo.Repository, team, "Removed team %s as collaborator from %s.", team.Name, ctx.Repo.Repository.FullName()) + } } if err != nil { ctx.InternalServerError(err) return } - if add { - audit.Record(audit.RepositoryCollaboratorTeamAdd, ctx.Doer, ctx.Repo.Repository, team, "Added team %s as collaborator for %s.", team.Name, ctx.Repo.Repository.FullName()) - } else { - audit.Record(audit.RepositoryCollaboratorTeamRemove, ctx.Doer, ctx.Repo.Repository, team, "Removed team %s as collaborator from %s.", team.Name, ctx.Repo.Repository.FullName()) - } - ctx.Status(http.StatusNoContent) } diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index f9f94cd1636c2..5ae81fccfd324 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -189,7 +189,8 @@ func DeleteAccessToken(ctx *context.APIContext) { return } } else { - _, err := db.GetBeanByID(ctx, tokenID, &t) + t = &auth_model.AccessToken{} + _, err := db.GetBeanByID(ctx, tokenID, t) if err != nil { ctx.Error(http.StatusInternalServerError, "GetBeanByID", err) return diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 4f5d22c84594e..92ddbf2c43ec5 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -445,7 +445,7 @@ func EditAuthSourcePost(ctx *context.Context) { return } - audit.Record(audit.SystemAuthenticationSourceUpdate, nil, ctx.Doer, source, "Updated authentication source %s.", source.Name) + audit.Record(audit.SystemAuthenticationSourceUpdate, ctx.Doer, nil, source, "Updated authentication source %s.", source.Name) log.Trace("Authentication changed by admin(%s): %d", ctx.Doer.Name, source.ID) diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index 0d0bb38352b1d..738a11ae0dd47 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -240,12 +240,10 @@ func TeamsRepoAction(ctx *context.Context) { ctx.ServerError("GetRepositoryByName", err) return } - if err := org_service.TeamAddRepository(ctx.Org.Team, repo); err != nil { + if err := org_service.TeamAddRepository(ctx.Doer, ctx.Org.Team, repo); err != nil { ctx.ServerError("TeamAddRepository "+ctx.Org.Team.Name, err) return } - - audit.Record(audit.RepositoryCollaboratorTeamAdd, ctx.Doer, repo, ctx.Org.Team, "Added team %s as collaborator for %s.", ctx.Org.Team.Name, repo.FullName()) case "remove": repo, err := repo_model.GetRepositoryByID(db.DefaultContext, ctx.FormInt64("repoid")) if err != nil { diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go index 358276a397076..ede19cf1e6b78 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -1075,13 +1075,11 @@ func AddTeamPost(ctx *context.Context) { return } - if err = org_service.TeamAddRepository(team, ctx.Repo.Repository); err != nil { + if err = org_service.TeamAddRepository(ctx.Doer, team, ctx.Repo.Repository); err != nil { ctx.ServerError("TeamAddRepository", err) return } - audit.Record(audit.RepositoryCollaboratorTeamAdd, ctx.Doer, ctx.Repo.Repository, team, "Added team %s as collaborator for %s.", team.Name, ctx.Repo.Repository.FullName()) - ctx.Flash.Success(ctx.Tr("repo.settings.add_team_success")) ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration") } diff --git a/services/audit/audit.go b/services/audit/audit.go index c0e3990aac646..7735601981025 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -4,6 +4,7 @@ package audit import ( + "errors" "fmt" "os" "path" @@ -188,15 +189,11 @@ func typeToDescription(val any) TypeDescriptor { } func ReleaseReopen() error { - var accumulatedErr error + var joinedErr error for _, a := range appenders { if err := a.ReleaseReopen(); err != nil { - if accumulatedErr == nil { - accumulatedErr = fmt.Errorf("error reopening: %w", err) - } else { - accumulatedErr = fmt.Errorf("error reopening: %v & %w", err, accumulatedErr) - } + joinedErr = errors.Join(joinedErr, err) } } - return accumulatedErr + return joinedErr } diff --git a/services/org/repo.go b/services/org/repo.go index 179249c7a8d0f..d8e732bd6e7c1 100644 --- a/services/org/repo.go +++ b/services/org/repo.go @@ -11,17 +11,26 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/services/audit" ) // TeamAddRepository adds new repository to team of organization. -func TeamAddRepository(t *organization.Team, repo *repo_model.Repository) (err error) { +func TeamAddRepository(doer *user_model.User, t *organization.Team, repo *repo_model.Repository) error { if repo.OwnerID != t.OrgID { return errors.New("repository does not belong to organization") } else if models.HasRepository(t, repo.ID) { return nil } - return db.WithTx(db.DefaultContext, func(ctx context.Context) error { + err := db.WithTx(db.DefaultContext, func(ctx context.Context) error { return models.AddRepository(ctx, t, repo) }) + if err != nil { + return err + } + + audit.Record(audit.RepositoryCollaboratorTeamAdd, doer, repo, t, "Added team %s as collaborator for %s.", t.Name, repo.FullName()) + + return nil } diff --git a/services/org/repo_test.go b/services/org/repo_test.go index 40b0d17077b6b..04355a768e3b3 100644 --- a/services/org/repo_test.go +++ b/services/org/repo_test.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" "github.com/stretchr/testify/assert" ) @@ -19,7 +20,7 @@ func TestTeam_AddRepository(t *testing.T) { testSuccess := func(teamID, repoID int64) { team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) - assert.NoError(t, TeamAddRepository(team, repo)) + assert.NoError(t, TeamAddRepository(user_model.NewGhostUser(), team, repo)) unittest.AssertExistsAndLoadBean(t, &organization.TeamRepo{TeamID: teamID, RepoID: repoID}) unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &repo_model.Repository{ID: repoID}) } @@ -28,6 +29,6 @@ func TestTeam_AddRepository(t *testing.T) { team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - assert.Error(t, TeamAddRepository(team, repo)) + assert.Error(t, TeamAddRepository(user_model.NewGhostUser(), team, repo)) unittest.CheckConsistencyFor(t, &organization.Team{ID: 1}, &repo_model.Repository{ID: 1}) } diff --git a/services/user/user.go b/services/user/user.go index 3befe8293ff73..99fe04f535179 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -25,7 +25,6 @@ import ( "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/audit" - organization_service "code.gitea.io/gitea/services/org" "code.gitea.io/gitea/services/packages" ) @@ -137,7 +136,7 @@ func DeleteUser(ctx context.Context, doer, u *user_model.User, purge bool) error for _, org := range orgs { if err := models.RemoveOrgUser(org.ID, u.ID); err != nil { if organization.IsErrLastOrgOwner(err) { - err = organization_service.DeleteOrganization(doer, org) + err = organization.DeleteOrganization(ctx, org) } if err != nil { return fmt.Errorf("unable to remove user %s[%d] from org %s[%d]. Error: %w", u.Name, u.ID, org.Name, org.ID, err) diff --git a/services/webhook/main_test.go b/services/webhook/main_test.go index 210221b120413..86933cc7cf01b 100644 --- a/services/webhook/main_test.go +++ b/services/webhook/main_test.go @@ -16,7 +16,6 @@ import ( func TestMain(m *testing.M) { setting.InitProviderAndLoadCommonSettingsForTest() - setting.LoadQueueSettings() // for tests, allow only loopback IPs setting.Webhook.AllowedHostList = hostmatcher.MatchBuiltinLoopback From 6e6305db6834fb57cba55f0f297d0d61fe6471fc Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 21 Apr 2023 13:21:49 +0000 Subject: [PATCH 04/44] Added documentation. --- custom/conf/app.example.ini | 32 ++++ .../doc/administration/audit-logging.en-us.md | 164 ++++++++++++++++++ .../config-cheat-sheet.en-us.md | 15 ++ modules/setting/audit.go | 14 +- routers/api/v1/repo/collaborators.go | 2 +- routers/web/auth/oauth.go | 14 ++ routers/web/repo/setting.go | 2 +- routers/web/repo/setting_protected_branch.go | 2 - services/audit/action.go | 1 - services/repository/transfer.go | 4 +- 10 files changed, 242 insertions(+), 8 deletions(-) create mode 100644 docs/content/doc/administration/audit-logging.en-us.md diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index f9f207522c7df..371ef44668c19 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -676,6 +676,38 @@ ROUTER = console ;; Receivers, can be one or more, e.g. 1@example.com,2@example.com ;RECEIVERS = +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[audit] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Enable logging of audit events +;ENABLED = false +;; Comma seperated list of appenders to log to (possible values: notice, log, file) +;APPENDER = +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Creating specific log configuration +;; +;; You can set specific configuration for individual appenders. Currently only "file" uses additional configuration. +;; +;[audit.file] +;; Set the file name for the logger. If this is a relative path this +;; will be relative to log.ROOT_PATH +;; Defaults to log.ROOT_PATH/audit.log +;FILENAME = +;; This enables automated audit log rotate, default is true +;ROTATE = true +;; Maximum file size in bytes before rotating takes place (format `1000`, `1 MB`, `1 GiB`) +;MAXIMUM_SIZE = 256 MB +;; Rotate audit log daily, default is true +;ROTATE_DAILY = true +;; Delete the audit log file after n days, default is 7 +;KEEP_DAYS = 7 +;; Compress audit logs with gzip +;COMPRESS = true +;; Compression level see godoc for compress/gzip +;COMPRESSION_LEVEL = -1 + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; [git] diff --git a/docs/content/doc/administration/audit-logging.en-us.md b/docs/content/doc/administration/audit-logging.en-us.md new file mode 100644 index 0000000000000..2da46f7bf2861 --- /dev/null +++ b/docs/content/doc/administration/audit-logging.en-us.md @@ -0,0 +1,164 @@ +--- +date: "2023-04-21T00:00:00+00:00" +title: "Audit Logging" +slug: "audit-logging" +weight: 43 +toc: false +draft: false +menu: + sidebar: + parent: "administration" + name: "Audit Logging" + weight: 42 + identifier: "audit-logging" +--- + +# Audit Logging + +Audit logging is used to track security related events and provide documentary evidence of the sequence of important activities. + +**Table of Contents** + +{{< toc >}} + +## Appenders + +The audit log supports different appenders: +- `notice`: Log events as admin notices +- `log`: Log events as information to the configured Gitea logging +- `file`: Write events as JSON objects to a file + +The config documentation lists all available options to configure audit logging with appenders. + +## Events + +Audit events are grouped by `user`, `organization`, `repository` and `system`. + +### User Events + +| Event | Description | +| - | - | +| `user:impersonation` | Admin impersonating user | +| `user:create` | User was created | +| `user:update` | Updated settings of user | +| `user:delete` | User was deleted | +| `user:authentication:fail:twofactor` | Failed two-factor authentication for user | +| `user:authentication:source` | Authentication source of user changed | +| `user:active` | Activation status of user changed | +| `user:restricted` | Restriction status of user changed | +| `user:admin` | Admin status of user changed | +| `user:name` | User changed name | +| `user:password` | Password of user changed | +| `user:password:reset` | User requested a password reset | +| `user:visibility` | Visibility of user changed | +| `user:email:add` | Email added to user | +| `user:email:activate` | Email of user activated | +| `user:email:remove` | Email removed from user | +| `user:twofactor:enable` | User enabled two-factor authentication | +| `user:twofactor:regenerate` | User regenerated two-factor authentication secret | +| `user:twofactor:disable` | User disabled two-factor authentication | +| `user:webauth:add` | User added WebAuthn key | +| `user:webauth:remove` | User removed WebAuthn key | +| `user:externallogin:add` | Added external login for user | +| `user:externallogin:remove` | Removed external login for user | +| `user:openid:add` | Associated OpenID to user | +| `user:openid:remove` | Removed OpenID from user | +| `user:accesstoken:add` | Added access token for user | +| `user:accesstoken:remove` | Removed access token from user | +| `user:oauth2application:add` | Created OAuth2 application | +| `user:oauth2application:update` | Updated OAuth2 application | +| `user:oauth2application:secret` | Regenerated secret for OAuth2 application | +| `user:oauth2application:grant` | Granted OAuth2 access to application | +| `user:oauth2application:revoke` | Revoked OAuth2 grant for application | +| `user:oauth2application:remove` | Removed OAuth2 application | +| `user:key:ssh:add` | Added SSH key | +| `user:key:ssh:remove` | Removed SSH key | +| `user:key:principal:add` | Added principal key | +| `user:key:principal:remove` | Removed principal key | +| `user:key:gpg:add` | Added GPG key | +| `user:key:gpg:remove` | Added GPG key | +| `user:secret:add` | Added secret | +| `user:secret:remove` | Removed secret | +| `user:webhook:add` | Added webhook | +| `user:webhook:update` | Updated webhook | +| `user:webhook:remove` | Removed webhook | + +### Organization Events + +| Event | Description | +| - | - | +| `organization:create` | Organization was created | +| `organization:update` | Updated settings of organization | +| `organization:delete` | Organization was deleted | +| `organization:name` | Organization name changed | +| `organization:visibility` | Visibility of organization changed | +| `organization:team:add` | Team was added to organziation | +| `organization:team:update` | Updated settings of team | +| `organization:team:remove` | Team was removed from organziation | +| `organization:team:permission` | Permission of team changed | +| `organization:team:member:add` | User was added to team | +| `organization:team:member:remove` | User was removed from team | +| `organization:oauth2application:add` | Created OAuth2 application | +| `organization:oauth2application:update` | Updated OAuth2 application | +| `organization:oauth2application:secret` | Regenerated secret for OAuth2 application | +| `organization:oauth2application:remove` | Removed OAuth2 application | +| `organization:secret:add` | Added secret | +| `organization:secret:remove` | Removed secret | +| `organization:webhook:add` | Added webhook | +| `organization:webhook:update` | Updated webhook | +| `organization:webhook:remove` | Removed webhook | + +### Repository Events + +| Event | Description | +| - | - | +| `repository:create` | Repository was created | +| `repository:update` | Updated settings of repository | +| `repository:archive` | Archived repository | +| `repository:unarchive` | Unarchived repository | +| `repository:delete` | Repository was deleted | +| `repository:name` | Repository name changed | +| `repository:visibility` | Changed visibility of repository | +| `repository:convert:fork` | Converted repository from fork to regular repository | +| `repository:convert:mirror` | Converted repository from mirror to regular repository | +| `repository:mirror:push:add` | Added push mirror for repository | +| `repository:mirror:push:remove` | Removed push mirror from repository | +| `repository:signingverification` | Changed signing verification of repository | +| `repository:transfer:start` | Started repository transfer | +| `repository:transfer:accept` | Accepted repository transfer | +| `repository:transfer:reject` | Rejected repository transfer | +| `repository:wiki:delete` | Deleted wiki data of repository | +| `repository:collaborator:add` | Added user as collaborator for repository | +| `repository:collaborator:access` | Changed access mode of collaborator | +| `repository:collaborator:remove` | Removed user as collaborator of repository | +| `repository:collaborator:team:add` | Added team as collaborator for repository | +| `repository:collaborator:team:remove` | Removed team as collaborator of repository | +| `repository:branch:default` | Changed default branch | +| `repository:branch:protection:add` | Added branch protection | +| `repository:branch:protection:update` | Updated branch protection | +| `repository:branch:protection:remove` | Removed branch protection | +| `repository:tag:protection:add` | Added tag protection | +| `repository:tag:protection:update` | Updated tag protection | +| `repository:tag:protection:remove` | Removed tag protection | +| `repository:webhook:add` | Added webhook | +| `repository:webhook:update` | Updated webhook | +| `repository:webhook:remove` | Removed webhook | +| `repository:deploykey:add` | Added deploy key | +| `repository:deploykey:remove` | Removed deploy key | +| `repository:secret:add` | Added secret | +| `repository:secret:remove` | Removed secret | + +### System Events + +| Event | Description | +| - | - | +| `system:webhook:add` | Added webhook | +| `system:webhook:update` | Updated webhook | +| `system:webhook:remove` | Removed webhook | +| `system:authenticationsource:add` | Created authentication source | +| `system:authenticationsource:update` | Updated authentication source | +| `system:authenticationsource:remove` | Removed authentication source | +| `system:oauth2application:add` | Created OAuth2 application | +| `system:oauth2application:update` | Updated OAuth2 application | +| `system:oauth2application:secret` | Regenerated secret for OAuth2 application | +| `system:oauth2application:remove` | Removed OAuth2 application | diff --git a/docs/content/doc/administration/config-cheat-sheet.en-us.md b/docs/content/doc/administration/config-cheat-sheet.en-us.md index f26e7eaa085ab..bbbb08a8637ec 100644 --- a/docs/content/doc/administration/config-cheat-sheet.en-us.md +++ b/docs/content/doc/administration/config-cheat-sheet.en-us.md @@ -932,6 +932,21 @@ Default templates for project boards: - `RECEIVERS`: Email addresses to send to. - `SUBJECT`: **Diagnostic message from Gitea** +## Audit Log (`audit`) + +- `ENABLED`: **false**: Enable logging of audit events +- `APPENDER`: **\**: Comma seperated list of appenders to log to (possible values: `notice`, `log` or `file`). + +## File Audit Log (`audit.file`) + +- `FILENAME`: **\**: Set the file name for the logger. If this is a relative path this will be relative to `log.ROOT_PATH`. Defaults to `log.ROOT_PATH/audit.log`. +- `ROTATE`: **false**: This enables automated audit log rotate, default is true +- `MAXIMUM_SIZE`: **false**: Maximum file size in bytes before rotating takes place (format `1000`, `1 MB`, `1 GiB`) +- `ROTATE_DAILY`: **true**: Rotate audit log daily, default is true +- `KEEP_DAYS`: **7**: Delete the audit log file after n days, default is 7 +- `COMPRESS`: **true**: Compress audit logs with gzip +- `COMPRESSION_LEVEL`: **-1**: Compression level see godoc for `compress/gzip` + ## Cron (`cron`) - `ENABLED`: **false**: Enable to run all cron tasks periodically with default settings. diff --git a/modules/setting/audit.go b/modules/setting/audit.go index 70fc5ffa8da74..655728dee188e 100644 --- a/modules/setting/audit.go +++ b/modules/setting/audit.go @@ -5,6 +5,7 @@ package setting import ( "compress/gzip" + "path" "path/filepath" "strings" @@ -45,9 +46,8 @@ func loadAuditFrom(rootCfg ConfigProvider) { } opts := &AppenderOptions{ - Filename: filepath.Join(Log.RootPath, "audit.log"), + Filename: path.Join(Log.RootPath, "audit.log"), Rotate: false, - MaximumSize: 1 << 28, RotateDaily: true, KeepDays: 7, CompressionLevel: gzip.DefaultCompression, @@ -57,6 +57,16 @@ func loadAuditFrom(rootCfg ConfigProvider) { log.Error("audit.%s: %v", name, err.Error()) } + forcePathSeparator(opts.Filename) + if !filepath.IsAbs(opts.Filename) { + opts.Filename = path.Join(Log.RootPath, opts.Filename) + } + + opts.MaximumSize = mustBytes(sec, "MAXIMUM_SIZE") + if opts.MaximumSize <= 0 { + opts.MaximumSize = 1 << 28 + } + Audit.AppenderOptions[name] = opts } } diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go index 095fad7330405..f420497f2f51f 100644 --- a/routers/api/v1/repo/collaborators.go +++ b/routers/api/v1/repo/collaborators.go @@ -180,7 +180,7 @@ func AddCollaborator(ctx *context.APIContext) { return } - audit.Record(audit.RepositoryCollaboratorAdd, ctx.Doer, ctx.Repo.Repository, collaborator, "Added user %s as collaborator.", collaborator.Name) + audit.Record(audit.RepositoryCollaboratorAdd, ctx.Doer, ctx.Repo.Repository, collaborator, "Added user %s as collaborator for repository %s.", collaborator.Name, ctx.Repo.Repository.FullName()) if form.Permission != nil { accessMode := perm.ParseAccessMode(*form.Permission) diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index e5e9e454c54f3..3227780c9065c 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -544,6 +544,17 @@ func GrantApplicationOAuth(ctx *context.Context) { ctx.ServerError("GetOAuth2ApplicationByClientID", err) return } + + owner, err := user_model.GetUserByID(ctx, app.UID) + if err != nil && !errors.Is(err, util.ErrNotExist) { + handleAuthorizeError(ctx, AuthorizeError{ + State: form.State, + ErrorDescription: "cannot find user", + ErrorCode: ErrorCodeServerError, + }, form.RedirectURI) + return + } + grant, err := app.CreateGrant(ctx, ctx.Doer.ID, form.Scope) if err != nil { handleAuthorizeError(ctx, AuthorizeError{ @@ -553,6 +564,9 @@ func GrantApplicationOAuth(ctx *context.Context) { }, form.RedirectURI) return } + + audit.Record(audit.UserOAuth2ApplicationGrant, ctx.Doer, owner, grant, "Granted OAuth2 access to application %s.", app.Name) + if len(form.Nonce) > 0 { err := grant.SetNonce(ctx, form.Nonce) if err != nil { diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go index ede19cf1e6b78..d6865118f67e9 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -991,7 +991,7 @@ func CollaborationPost(ctx *context.Context) { mailer.SendCollaboratorMail(u, ctx.Doer, ctx.Repo.Repository) } - audit.Record(audit.RepositoryCollaboratorAdd, ctx.Doer, ctx.Repo.Repository, u, "Added user %s as collaborator.", u.Name) + audit.Record(audit.RepositoryCollaboratorAdd, ctx.Doer, ctx.Repo.Repository, u, "Added user %s as collaborator for repository %s.", u.Name, ctx.Repo.Repository.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.add_collaborator_success")) ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) diff --git a/routers/web/repo/setting_protected_branch.go b/routers/web/repo/setting_protected_branch.go index cdc71936726ab..8e501799b336f 100644 --- a/routers/web/repo/setting_protected_branch.go +++ b/routers/web/repo/setting_protected_branch.go @@ -388,8 +388,6 @@ func RenameBranchPost(ctx *context.Context) { return } - audit.Record(audit.RepositoryBranchRename, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Renamed branch from %s to %s.", form.From, form.To) - ctx.Flash.Success(ctx.Tr("repo.settings.rename_branch_success", form.From, form.To)) ctx.Redirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) } diff --git a/services/audit/action.go b/services/audit/action.go index ab5bdbfbbcb86..08d7cbc057424 100644 --- a/services/audit/action.go +++ b/services/audit/action.go @@ -94,7 +94,6 @@ const ( RepositoryCollaboratorTeamAdd Action = "repository:collaborator:team:add" RepositoryCollaboratorTeamRemove Action = "repository:collaborator:team:remove" RepositoryBranchDefault Action = "repository:branch:default" - RepositoryBranchRename Action = "repository:branch:rename" RepositoryBranchProtectionAdd Action = "repository:branch:protection:add" RepositoryBranchProtectionUpdate Action = "repository:branch:protection:update" RepositoryBranchProtectionRemove Action = "repository:branch:protection:remove" diff --git a/services/repository/transfer.go b/services/repository/transfer.go index 9354abce60fa1..b79a44fb1d519 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -49,7 +49,7 @@ func TransferOwnership(ctx context.Context, doer, newOwner *user_model.User, rep return err } - audit.Record(audit.RepositoryTransferAccept, doer, newRepo, newRepo, "Accepted repository transfer from %s to %s.", oldOwner.Name, newRepo.FullName()) + audit.Record(audit.RepositoryTransferAccept, doer, newRepo, newRepo, "Accepted repository transfer from %s to %s.", oldOwner.Name, newRepo.OwnerName) for _, team := range teams { if err := models.AddRepository(ctx, team, newRepo); err != nil { @@ -133,6 +133,8 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use return err } + audit.Record(audit.RepositoryTransferStart, doer, repo, repo, "Started repository transfer from %s to %s.", repo.OwnerName, newOwner.Name) + // notify users who are able to accept / reject transfer notification.NotifyRepoPendingTransfer(ctx, doer, newOwner, repo) From f36f2080b145c3ff1a96f3fb25fc48e8691f3321 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 21 Apr 2023 13:38:08 +0000 Subject: [PATCH 05/44] Lint docs. --- docs/content/doc/administration/audit-logging.en-us.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/content/doc/administration/audit-logging.en-us.md b/docs/content/doc/administration/audit-logging.en-us.md index 2da46f7bf2861..36adc149600b4 100644 --- a/docs/content/doc/administration/audit-logging.en-us.md +++ b/docs/content/doc/administration/audit-logging.en-us.md @@ -24,6 +24,7 @@ Audit logging is used to track security related events and provide documentary e ## Appenders The audit log supports different appenders: + - `notice`: Log events as admin notices - `log`: Log events as information to the configured Gitea logging - `file`: Write events as JSON objects to a file From 8939539425613e330ab0a82f427335de937d6e1b Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 21 Apr 2023 13:43:35 +0000 Subject: [PATCH 06/44] Fixed typo. --- docs/content/doc/administration/audit-logging.en-us.md | 4 ++-- routers/api/v1/org/hook.go | 2 +- routers/api/v1/org/team.go | 4 ++-- routers/web/org/teams.go | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/content/doc/administration/audit-logging.en-us.md b/docs/content/doc/administration/audit-logging.en-us.md index 36adc149600b4..b7493ce344669 100644 --- a/docs/content/doc/administration/audit-logging.en-us.md +++ b/docs/content/doc/administration/audit-logging.en-us.md @@ -93,9 +93,9 @@ Audit events are grouped by `user`, `organization`, `repository` and `system`. | `organization:delete` | Organization was deleted | | `organization:name` | Organization name changed | | `organization:visibility` | Visibility of organization changed | -| `organization:team:add` | Team was added to organziation | +| `organization:team:add` | Team was added to organization | | `organization:team:update` | Updated settings of team | -| `organization:team:remove` | Team was removed from organziation | +| `organization:team:remove` | Team was removed from organization | | `organization:team:permission` | Permission of team changed | | `organization:team:member:add` | User was added to team | | `organization:team:member:remove` | User was removed from team | diff --git a/routers/api/v1/org/hook.go b/routers/api/v1/org/hook.go index a6ea618a7d896..2fd9743d2e1b2 100644 --- a/routers/api/v1/org/hook.go +++ b/routers/api/v1/org/hook.go @@ -13,7 +13,7 @@ import ( webhook_service "code.gitea.io/gitea/services/webhook" ) -// ListHooks list an organziation's webhooks +// ListHooks list an organization's webhooks func ListHooks(ctx *context.APIContext) { // swagger:operation GET /orgs/{org}/hooks organization orgListHooks // --- diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index 8daa63f6b0692..406f876674d50 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -242,7 +242,7 @@ func CreateTeam(ctx *context.APIContext) { return } - audit.Record(audit.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, team, "Team %s was added to organziation %s.", team.Name, ctx.Org.Organization.Name) + audit.Record(audit.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, team, "Team %s was added to organization %s.", team.Name, ctx.Org.Organization.Name) apiTeam, err := convert.ToTeam(ctx, team) if err != nil { @@ -377,7 +377,7 @@ func DeleteTeam(ctx *context.APIContext) { return } - audit.Record(audit.OrganizationTeamRemove, ctx.Doer, org, ctx.Org.Team, "Team %s was removed from organziation %s.", ctx.Org.Team.Name, org.Name) + audit.Record(audit.OrganizationTeamRemove, ctx.Doer, org, ctx.Org.Team, "Team %s was removed from organization %s.", ctx.Org.Team.Name, org.Name) ctx.Status(http.StatusNoContent) } diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index 738a11ae0dd47..108a7d8e0df30 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -386,7 +386,7 @@ func NewTeamPost(ctx *context.Context) { return } - audit.Record(audit.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, t, "Team %s was added to organziation %s.", t.Name, ctx.Org.Organization.Name) + audit.Record(audit.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, t, "Team %s was added to organization %s.", t.Name, ctx.Org.Organization.Name) log.Trace("Team created: %s/%s", ctx.Org.Organization.Name, t.Name) ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(t.LowerName)) @@ -565,7 +565,7 @@ func DeleteTeam(ctx *context.Context) { if err := models.DeleteTeam(ctx.Org.Team); err != nil { ctx.Flash.Error("DeleteTeam: " + err.Error()) } else { - audit.Record(audit.OrganizationTeamRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Team %s was removed from organziation %s.", ctx.Org.Team.Name, ctx.Org.Organization.Name) + audit.Record(audit.OrganizationTeamRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Team %s was removed from organization %s.", ctx.Org.Team.Name, ctx.Org.Organization.Name) ctx.Flash.Success(ctx.Tr("org.teams.delete_team_success")) } From 7131cf6fff1ff5dd779c4ff63ba641740279072f Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 21 Apr 2023 20:42:52 +0000 Subject: [PATCH 07/44] Remove errors.Join. --- modules/util/rotatingfilewriter/writer.go | 20 ++++++++++++++++---- services/audit/audit.go | 18 +++++++++++++++--- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/modules/util/rotatingfilewriter/writer.go b/modules/util/rotatingfilewriter/writer.go index 2e103476d1a8d..dd466bebdcd76 100644 --- a/modules/util/rotatingfilewriter/writer.go +++ b/modules/util/rotatingfilewriter/writer.go @@ -6,7 +6,6 @@ package rotatingfilewriter import ( "bufio" "compress/gzip" - "errors" "fmt" "os" "path/filepath" @@ -96,10 +95,22 @@ func (rfw *RotatingFileWriter) open(filename string) error { } func (rfw *RotatingFileWriter) ReleaseReopen() error { - return errors.Join( + closeErr := rfw.fd.Close() + openErr := rfw.open(rfw.fd.Name()) + + if closeErr != nil { + if openErr != nil { + return fmt.Errorf("error closing and reopening file: %v & %v", closeErr, openErr) + } + return closeErr + } + return openErr + + // TODO Replace with errors.Join > Go 1.20 + /*return errors.Join( rfw.fd.Close(), rfw.open(rfw.fd.Name()), - ) + )*/ } // Rotate the log file creating a backup like xx.2013-01-01.2 @@ -177,7 +188,8 @@ func compressOldFile(fname string, compressionLevel int) error { if err != nil { zw.Close() fw.Close() - return errors.Join(err, util.Remove(fname+".gz")) + util.Remove(fname + ".gz") //nolint:errcheck + return err } reader.Close() diff --git a/services/audit/audit.go b/services/audit/audit.go index 9e283f617d456..229c8d63b0fc9 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -4,7 +4,6 @@ package audit import ( - "errors" "fmt" "os" "path" @@ -189,11 +188,24 @@ func typeToDescription(val any) TypeDescriptor { } func ReleaseReopen() error { - var joinedErr error + var accumulatedErr error + for _, a := range appenders { + if err := a.ReleaseReopen(); err != nil { + if accumulatedErr == nil { + accumulatedErr = fmt.Errorf("Error whilst reopening: %w", err) + } else { + accumulatedErr = fmt.Errorf("Error whilst reopening: %v & %w", err, accumulatedErr) + } + } + } + return accumulatedErr + + // TODO Replace with errors.Join > Go 1.20 + /*var joinedErr error for _, a := range appenders { if err := a.ReleaseReopen(); err != nil { joinedErr = errors.Join(joinedErr, err) } } - return joinedErr + return joinedErr*/ } From 0f56aa10e5b436856083047da65d5129a11fb421 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 21 Apr 2023 20:50:34 +0000 Subject: [PATCH 08/44] Fix merge error. --- modules/log/file.go | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/log/file.go b/modules/log/file.go index f21b8216caa48..90d84d18ce566 100644 --- a/modules/log/file.go +++ b/modules/log/file.go @@ -8,7 +8,6 @@ import ( "compress/gzip" "errors" "fmt" - "os" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/util/rotatingfilewriter" From 8038aee871c3183d06717eacf7465a9beec68d91 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sun, 23 Apr 2023 15:10:15 +0200 Subject: [PATCH 09/44] Update models/db/context.go Co-authored-by: delvh --- models/db/context.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/db/context.go b/models/db/context.go index c1fefa8e1d926..2c48e584111e7 100644 --- a/models/db/context.go +++ b/models/db/context.go @@ -179,8 +179,8 @@ func GetByBean(ctx context.Context, bean interface{}) (bool, error) { return GetEngine(ctx).Get(bean) } -// GetBeanByID -func GetBeanByID(ctx context.Context, id, bean interface{}) (bool, error) { +// GetByID retrieves the bean with the given ID (given that all non-empty fields match) +func GetByID(ctx context.Context, id, bean interface{}) (bool, error) { return GetEngine(ctx).ID(id).Get(bean) } From fb3f9541a1cc3aaed435a7e34e23f837035a8fe3 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sun, 23 Apr 2023 14:04:56 +0000 Subject: [PATCH 10/44] Change method name. --- routers/api/v1/user/app.go | 4 ++-- routers/web/shared/secrets/secrets.go | 4 ++-- routers/web/user/setting/applications.go | 4 ++-- routers/web/user/setting/security/openid.go | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index 5ae81fccfd324..e204babb14ef8 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -190,9 +190,9 @@ func DeleteAccessToken(ctx *context.APIContext) { } } else { t = &auth_model.AccessToken{} - _, err := db.GetBeanByID(ctx, tokenID, t) + _, err := db.GetByID(ctx, tokenID, t) if err != nil { - ctx.Error(http.StatusInternalServerError, "GetBeanByID", err) + ctx.Error(http.StatusInternalServerError, "GetByID", err) return } } diff --git a/routers/web/shared/secrets/secrets.go b/routers/web/shared/secrets/secrets.go index aa68b229b5cf8..13dec6e3ca6f5 100644 --- a/routers/web/shared/secrets/secrets.go +++ b/routers/web/shared/secrets/secrets.go @@ -51,8 +51,8 @@ func PerformSecretsDelete(ctx *context.Context, doer, owner *user_model.User, re id := ctx.FormInt64("id") s := &secret_model.Secret{} - if has, err := db.GetBeanByID(ctx, id, s); err != nil { - log.Error("GetBeanByID failed: %v", err) + if has, err := db.GetByID(ctx, id, s); err != nil { + log.Error("GetByID failed: %v", err) ctx.Flash.Error(ctx.Tr("secrets.deletion.failed")) return } else if !has || s.OwnerID != tryGetOwnerID(owner) || s.RepoID != tryGetRepositoryID(repo) { diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index 8db1922b8ac4d..dd4211a648c3d 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -86,9 +86,9 @@ func DeleteApplication(ctx *context.Context) { }) t := &auth_model.AccessToken{UID: ctx.Doer.ID} - has, err := db.GetBeanByID(ctx, ctx.FormInt64("id"), t) + has, err := db.GetByID(ctx, ctx.FormInt64("id"), t) if err != nil { - ctx.Flash.Error("GetBeanByID: " + err.Error()) + ctx.Flash.Error("GetByID: " + err.Error()) return } else if !has { return diff --git a/routers/web/user/setting/security/openid.go b/routers/web/user/setting/security/openid.go index d23d04b8f1649..fbffa5625d01b 100644 --- a/routers/web/user/setting/security/openid.go +++ b/routers/web/user/setting/security/openid.go @@ -111,9 +111,9 @@ func settingsOpenIDVerify(ctx *context.Context) { // DeleteOpenID response for delete user's openid func DeleteOpenID(ctx *context.Context) { oid := &user_model.UserOpenID{UID: ctx.Doer.ID} - _, err := db.GetBeanByID(ctx, ctx.FormInt64("id"), oid) + _, err := db.GetByID(ctx, ctx.FormInt64("id"), oid) if err != nil { - ctx.ServerError("GetBeanByID", err) + ctx.ServerError("GetByID", err) return } From 3c613b38a8eb1e0101f92fed448c8410c5178eb7 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sun, 7 May 2023 18:20:53 +0000 Subject: [PATCH 11/44] Remove notice appender. --- custom/conf/app.example.ini | 2 +- .../doc/administration/audit-logging.en-us.md | 1 - .../administration/config-cheat-sheet.en-us.md | 2 +- models/system/notice.go | 1 - options/locale/locale_en-US.ini | 1 - services/audit/appender.go | 18 ------------------ services/audit/audit.go | 2 -- 7 files changed, 2 insertions(+), 25 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 819471a750209..258c4de1600a7 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -683,7 +683,7 @@ ROUTER = console ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Enable logging of audit events ;ENABLED = false -;; Comma seperated list of appenders to log to (possible values: notice, log, file) +;; Comma seperated list of appenders to log to (possible values: log, file) ;APPENDER = ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Creating specific log configuration diff --git a/docs/content/doc/administration/audit-logging.en-us.md b/docs/content/doc/administration/audit-logging.en-us.md index b7493ce344669..52e6dcefd3fe1 100644 --- a/docs/content/doc/administration/audit-logging.en-us.md +++ b/docs/content/doc/administration/audit-logging.en-us.md @@ -25,7 +25,6 @@ Audit logging is used to track security related events and provide documentary e The audit log supports different appenders: -- `notice`: Log events as admin notices - `log`: Log events as information to the configured Gitea logging - `file`: Write events as JSON objects to a file diff --git a/docs/content/doc/administration/config-cheat-sheet.en-us.md b/docs/content/doc/administration/config-cheat-sheet.en-us.md index 551507b93e1b5..feadca14e515c 100644 --- a/docs/content/doc/administration/config-cheat-sheet.en-us.md +++ b/docs/content/doc/administration/config-cheat-sheet.en-us.md @@ -938,7 +938,7 @@ Default templates for project boards: ## Audit Log (`audit`) - `ENABLED`: **false**: Enable logging of audit events -- `APPENDER`: **\**: Comma seperated list of appenders to log to (possible values: `notice`, `log` or `file`). +- `APPENDER`: **\**: Comma seperated list of appenders to log to (possible values: `log` or `file`). ## File Audit Log (`audit.file`) diff --git a/models/system/notice.go b/models/system/notice.go index 787d47a2903dc..e598abe22251a 100644 --- a/models/system/notice.go +++ b/models/system/notice.go @@ -23,7 +23,6 @@ const ( NoticeRepository NoticeType = iota + 1 // NoticeTask type NoticeTask - NoticeAudit ) // Notice represents a system notice for admin. diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index ab372ef6e7d8b..573ddcf6f045c 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3120,7 +3120,6 @@ notices.delete_all = Delete All Notices notices.type = Type notices.type_1 = Repository notices.type_2 = Task -notices.type_3 = Audit notices.desc = Description notices.op = Op. notices.delete_success = The system notices have been deleted. diff --git a/services/audit/appender.go b/services/audit/appender.go index 0c490969d9652..4ca551dd13f6d 100644 --- a/services/audit/appender.go +++ b/services/audit/appender.go @@ -19,24 +19,6 @@ type Appender interface { ReleaseReopen() error } -// NoticeAppender creates an admin notice for every audit event -type NoticeAppender struct{} - -func (a *NoticeAppender) Record(ctx context.Context, e *Event) { - m := fmt.Sprintf("%s\n\nDoer: %s\nScope: %s[%v] %s\nTarget: %s[%v] %s", e.Message, e.Doer.FriendlyName, e.Scope.Type, e.Scope.PrimaryKey, e.Scope.FriendlyName, e.Target.Type, e.Target.PrimaryKey, e.Target.FriendlyName) - if err := system.CreateNotice(ctx, system.NoticeAudit, m); err != nil { - log.Error("CreateNotice: %v", err) - } -} - -func (a *NoticeAppender) Close() error { - return nil -} - -func (a *NoticeAppender) ReleaseReopen() error { - return nil -} - // LogAppender writes an info log entry for every audit event type LogAppender struct{} diff --git a/services/audit/audit.go b/services/audit/audit.go index 229c8d63b0fc9..579476bf16a6e 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -56,8 +56,6 @@ func Init() { switch name { case "log": a = &LogAppender{} - case "notice": - a = &NoticeAppender{} case "file": if err := os.MkdirAll(path.Dir(opts.Filename), os.ModePerm); err != nil { panic(err.Error()) From 11ddf58c92cea9ba821a468a69ef7f3b3278b56a Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sun, 7 May 2023 18:33:18 +0000 Subject: [PATCH 12/44] Use errors.Join. --- modules/util/rotatingfilewriter/writer.go | 16 ++-------------- services/audit/audit.go | 17 ++--------------- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/modules/util/rotatingfilewriter/writer.go b/modules/util/rotatingfilewriter/writer.go index dd466bebdcd76..0caa3bfe27640 100644 --- a/modules/util/rotatingfilewriter/writer.go +++ b/modules/util/rotatingfilewriter/writer.go @@ -95,22 +95,10 @@ func (rfw *RotatingFileWriter) open(filename string) error { } func (rfw *RotatingFileWriter) ReleaseReopen() error { - closeErr := rfw.fd.Close() - openErr := rfw.open(rfw.fd.Name()) - - if closeErr != nil { - if openErr != nil { - return fmt.Errorf("error closing and reopening file: %v & %v", closeErr, openErr) - } - return closeErr - } - return openErr - - // TODO Replace with errors.Join > Go 1.20 - /*return errors.Join( + return errors.Join( rfw.fd.Close(), rfw.open(rfw.fd.Name()), - )*/ + ) } // Rotate the log file creating a backup like xx.2013-01-01.2 diff --git a/services/audit/audit.go b/services/audit/audit.go index 579476bf16a6e..11f9b1fa9b51b 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -186,24 +186,11 @@ func typeToDescription(val any) TypeDescriptor { } func ReleaseReopen() error { - var accumulatedErr error - for _, a := range appenders { - if err := a.ReleaseReopen(); err != nil { - if accumulatedErr == nil { - accumulatedErr = fmt.Errorf("Error whilst reopening: %w", err) - } else { - accumulatedErr = fmt.Errorf("Error whilst reopening: %v & %w", err, accumulatedErr) - } - } - } - return accumulatedErr - - // TODO Replace with errors.Join > Go 1.20 - /*var joinedErr error + var joinedErr error for _, a := range appenders { if err := a.ReleaseReopen(); err != nil { joinedErr = errors.Join(joinedErr, err) } } - return joinedErr*/ + return joinedErr } From 033c8fe2f5fa667e6939964a27ca976e645d0944 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sun, 7 May 2023 18:37:26 +0000 Subject: [PATCH 13/44] lint --- modules/util/rotatingfilewriter/writer.go | 1 + services/audit/appender.go | 2 -- services/audit/audit.go | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/util/rotatingfilewriter/writer.go b/modules/util/rotatingfilewriter/writer.go index 0caa3bfe27640..fb0365be7e366 100644 --- a/modules/util/rotatingfilewriter/writer.go +++ b/modules/util/rotatingfilewriter/writer.go @@ -6,6 +6,7 @@ package rotatingfilewriter import ( "bufio" "compress/gzip" + "errors" "fmt" "os" "path/filepath" diff --git a/services/audit/appender.go b/services/audit/appender.go index 4ca551dd13f6d..1aee0ce22a5e1 100644 --- a/services/audit/appender.go +++ b/services/audit/appender.go @@ -5,9 +5,7 @@ package audit import ( "context" - "fmt" - "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util/rotatingfilewriter" diff --git a/services/audit/audit.go b/services/audit/audit.go index 11f9b1fa9b51b..403f7599b4720 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -4,6 +4,7 @@ package audit import ( + "errors" "fmt" "os" "path" From 3684f3a864ab1d1f06de45d2d89acc532f2f7d02 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Tue, 16 May 2023 20:46:17 +0000 Subject: [PATCH 14/44] Fix queue merge error. --- services/audit/audit.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/services/audit/audit.go b/services/audit/audit.go index 403f7599b4720..31749cf7d9be9 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -44,7 +44,7 @@ type Event struct { var ( appenders = make([]Appender, 0, 5) - auditQueue queue.Queue + auditQueue *queue.WorkerPoolQueue[*Event] ) func Init() { @@ -82,21 +82,18 @@ func Init() { } } - auditQueue = queue.CreateQueue( + auditQueue = queue.CreateSimpleQueue( "audit", - func(data ...queue.Data) []queue.Data { + func(data ...*Event) []*Event { ctx := graceful.GetManager().ShutdownContext() - for _, d := range data { - e := d.(*Event) - + for _, e := range data { for _, a := range appenders { a.Record(ctx, e) } } return nil }, - &Event{}, ) go graceful.GetManager().RunWithShutdownFns(auditQueue.Run) From 65e4f7cf7a97477a3fa1548d622a55542ada2d02 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Thu, 31 Aug 2023 19:13:00 +0100 Subject: [PATCH 15/44] Add tests. --- services/audit/appender.go | 7 +- services/audit/appender_test.go | 39 ++++++ services/audit/audit.go | 31 ++++- services/audit/audit_test.go | 222 ++++++++++++++++++++++++++++++++ tests/integration/audit_test.go | 200 ++++++++++++++++++++++++++++ tests/mssql.ini.tmpl | 4 + tests/mysql.ini.tmpl | 4 + tests/mysql8.ini.tmpl | 4 + tests/pgsql.ini.tmpl | 4 + tests/sqlite.ini.tmpl | 4 + 10 files changed, 512 insertions(+), 7 deletions(-) create mode 100644 services/audit/appender_test.go create mode 100644 services/audit/audit_test.go create mode 100644 tests/integration/audit_test.go diff --git a/services/audit/appender.go b/services/audit/appender.go index 1aee0ce22a5e1..c752b85bb4055 100644 --- a/services/audit/appender.go +++ b/services/audit/appender.go @@ -5,6 +5,7 @@ package audit import ( "context" + "io" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" @@ -47,7 +48,7 @@ func NewFileAppender(filename string, opts *rotatingfilewriter.Options) (*FileAp } func (a *FileAppender) Record(ctx context.Context, e *Event) { - if err := json.NewEncoder(a.rfw).Encode(e); err != nil { + if err := WriteEventAsJSON(a.rfw, e); err != nil { log.Error("encoding event to file failed: %v", err) } } @@ -59,3 +60,7 @@ func (a *FileAppender) Close() error { func (a *FileAppender) ReleaseReopen() error { return a.rfw.ReleaseReopen() } + +func WriteEventAsJSON(w io.Writer, e *Event) error { + return json.NewEncoder(w).Encode(e) +} diff --git a/services/audit/appender_test.go b/services/audit/appender_test.go new file mode 100644 index 0000000000000..db25d94176c27 --- /dev/null +++ b/services/audit/appender_test.go @@ -0,0 +1,39 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package audit + +import ( + "strings" + "testing" + "time" + + repository_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + + "github.com/stretchr/testify/assert" +) + +func TestWriteEventAsJSON(t *testing.T) { + r := &repository_model.Repository{ID: 3, Name: "TestRepo", OwnerName: "TestUser"} + m := &repository_model.PushMirror{ID: 4} + doer := &user_model.User{ID: 2, Name: "Doer"} + + e := BuildEvent( + RepositoryMirrorPushAdd, + doer, + r, + m, + "Added push mirror for repository %s.", + r.FullName(), + ) + e.Time = time.Time{} + + sb := strings.Builder{} + assert.NoError(t, WriteEventAsJSON(&sb, e)) + assert.Equal( + t, + `{"action":"repository:mirror:push:add","doer":{"type":"user","primary_key":2,"friendly_name":"Doer"},"scope":{"type":"repository","primary_key":3,"friendly_name":"TestUser/TestRepo"},"target":{"type":"push_mirror","primary_key":4,"friendly_name":""},"message":"Added push mirror for repository TestUser/TestRepo.","time":"0001-01-01T00:00:00Z"}`+"\n", + sb.String(), + ) +} diff --git a/services/audit/audit.go b/services/audit/audit.go index 500db563985fc..2c7ae090c107d 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -47,6 +47,21 @@ var ( auditQueue *queue.WorkerPoolQueue[*Event] ) +func TestingOnly_AddAppender(a Appender) { + appenders = append(appenders, a) +} + +func TestingOnly_RemoveAppender(a Appender) { + for i, app := range appenders { + if app == a { + last := len(appenders) - 1 + appenders[last], appenders[i] = nil, appenders[last] + appenders = appenders[:last] + return + } + } +} + func Init() { if !setting.Audit.Enabled { return @@ -100,12 +115,20 @@ func Init() { go graceful.GetManager().RunWithCancel(auditQueue) } -func Record(action Action, doer *user_model.User, scope, target any, format string, v ...interface{}) { +func Record(action Action, doer *user_model.User, scope, target any, format string, v ...any) { if !setting.Audit.Enabled { return } - e := &Event{ + e := BuildEvent(action, doer, scope, target, format, v...) + + if err := auditQueue.Push(e); err != nil { + log.Error("Error pushing audit event to queue: %v", err) + } +} + +func BuildEvent(action Action, doer *user_model.User, scope, target any, format string, v ...any) *Event { + return &Event{ Action: action, Doer: typeToDescription(doer), Scope: scopeToDescription(scope), @@ -113,10 +136,6 @@ func Record(action Action, doer *user_model.User, scope, target any, format stri Message: fmt.Sprintf(format, v...), Time: time.Now(), } - - if err := auditQueue.Push(e); err != nil { - log.Error("Error pushing audit event to queue: %v", err) - } } func scopeToDescription(scope any) TypeDescriptor { diff --git a/services/audit/audit_test.go b/services/audit/audit_test.go new file mode 100644 index 0000000000000..c95be9d758adc --- /dev/null +++ b/services/audit/audit_test.go @@ -0,0 +1,222 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package audit + +import ( + "testing" + "time" + + "code.gitea.io/gitea/models" + asymkey_model "code.gitea.io/gitea/models/asymkey" + auth_model "code.gitea.io/gitea/models/auth" + git_model "code.gitea.io/gitea/models/git" + organization_model "code.gitea.io/gitea/models/organization" + repository_model "code.gitea.io/gitea/models/repo" + secret_model "code.gitea.io/gitea/models/secret" + user_model "code.gitea.io/gitea/models/user" + webhook_model "code.gitea.io/gitea/models/webhook" + + "github.com/stretchr/testify/assert" +) + +func TestBuildEvent(t *testing.T) { + equal := func(expected, e *Event) { + expected.Time = time.Time{} + e.Time = time.Time{} + + assert.Equal(t, expected, e) + } + + u := &user_model.User{ID: 1, Name: "TestUser"} + r := &repository_model.Repository{ID: 3, Name: "TestRepo", OwnerName: "TestUser"} + m := &repository_model.PushMirror{ID: 4} + doer := &user_model.User{ID: 2, Name: "Doer"} + + equal( + &Event{ + Action: UserUpdate, + Doer: TypeDescriptor{Type: "user", PrimaryKey: int64(2), FriendlyName: "Doer", Target: doer}, + Scope: TypeDescriptor{Type: "user", PrimaryKey: int64(1), FriendlyName: "TestUser", Target: u}, + Target: TypeDescriptor{Type: "user", PrimaryKey: int64(1), FriendlyName: "TestUser", Target: u}, + Message: "Updated settings of user TestUser.", + }, + BuildEvent( + UserUpdate, + doer, + u, + u, + "Updated settings of user %s.", + u.Name, + ), + ) + equal( + &Event{ + Action: RepositoryMirrorPushAdd, + Doer: TypeDescriptor{Type: "user", PrimaryKey: int64(2), FriendlyName: "Doer", Target: doer}, + Scope: TypeDescriptor{Type: "repository", PrimaryKey: int64(3), FriendlyName: "TestUser/TestRepo", Target: r}, + Target: TypeDescriptor{Type: "push_mirror", PrimaryKey: int64(4), FriendlyName: "", Target: m}, + Message: "Added push mirror for repository TestUser/TestRepo.", + }, + BuildEvent( + RepositoryMirrorPushAdd, + doer, + r, + m, + "Added push mirror for repository %s.", + r.FullName(), + ), + ) +} + +func TestScopeToDescription(t *testing.T) { + cases := []struct { + ShouldPanic bool + Scope any + Expected TypeDescriptor + }{ + { + Scope: nil, + Expected: TypeDescriptor{Type: "system", PrimaryKey: 0, FriendlyName: "System"}, + }, + { + Scope: &user_model.User{ID: 1, Name: "TestUser"}, + Expected: TypeDescriptor{Type: "user", PrimaryKey: int64(1), FriendlyName: "TestUser"}, + }, + { + Scope: &organization_model.Organization{ID: 2, Name: "TestOrg"}, + Expected: TypeDescriptor{Type: "organization", PrimaryKey: int64(2), FriendlyName: "TestOrg"}, + }, + { + Scope: &repository_model.Repository{ID: 3, Name: "TestRepo", OwnerName: "TestUser"}, + Expected: TypeDescriptor{Type: "repository", PrimaryKey: int64(3), FriendlyName: "TestUser/TestRepo"}, + }, + { + ShouldPanic: true, + Scope: &organization_model.Team{ID: 345, Name: "Repo345"}, + }, + { + ShouldPanic: true, + Scope: 1234, + }, + } + for _, c := range cases { + c.Expected.Target = c.Scope + + if c.ShouldPanic { + assert.Panics(t, func() { + _ = scopeToDescription(c.Scope) + }) + } else { + assert.Equal(t, c.Expected, scopeToDescription(c.Scope), "Unexpected descriptor for scope: %T", c.Scope) + } + } +} + +func TestTypeToDescription(t *testing.T) { + cases := []struct { + ShouldPanic bool + Type any + Expected TypeDescriptor + }{ + { + ShouldPanic: true, + Type: nil, + }, + { + Type: &user_model.User{ID: 1, Name: "TestUser"}, + Expected: TypeDescriptor{Type: "user", PrimaryKey: int64(1), FriendlyName: "TestUser"}, + }, + { + Type: &organization_model.Organization{ID: 2, Name: "TestOrg"}, + Expected: TypeDescriptor{Type: "organization", PrimaryKey: int64(2), FriendlyName: "TestOrg"}, + }, + { + Type: &repository_model.Repository{ID: 3, Name: "TestRepo", OwnerName: "TestUser"}, + Expected: TypeDescriptor{Type: "repository", PrimaryKey: int64(3), FriendlyName: "TestUser/TestRepo"}, + }, + { + Type: &organization_model.Team{ID: 4, Name: "TestTeam"}, + Expected: TypeDescriptor{Type: "team", PrimaryKey: int64(4), FriendlyName: "TestTeam"}, + }, + { + Type: &auth_model.TwoFactor{ID: 5}, + Expected: TypeDescriptor{Type: "twofactor", PrimaryKey: int64(5), FriendlyName: ""}, + }, + { + Type: &auth_model.WebAuthnCredential{ID: 6, Name: "TestCredential"}, + Expected: TypeDescriptor{Type: "webauthn", PrimaryKey: int64(6), FriendlyName: "TestCredential"}, + }, + { + Type: &user_model.UserOpenID{ID: 7, URI: "test://uri"}, + Expected: TypeDescriptor{Type: "openid", PrimaryKey: int64(7), FriendlyName: "test://uri"}, + }, + { + Type: &auth_model.AccessToken{ID: 8, Name: "TestToken"}, + Expected: TypeDescriptor{Type: "access_token", PrimaryKey: int64(8), FriendlyName: "TestToken"}, + }, + { + Type: &auth_model.OAuth2Application{ID: 9, Name: "TestOAuth2Application"}, + Expected: TypeDescriptor{Type: "oauth2_application", PrimaryKey: int64(9), FriendlyName: "TestOAuth2Application"}, + }, + { + Type: &auth_model.OAuth2Grant{ID: 10}, + Expected: TypeDescriptor{Type: "oauth2_grant", PrimaryKey: int64(10), FriendlyName: ""}, + }, + { + Type: &auth_model.Source{ID: 11, Name: "TestSource"}, + Expected: TypeDescriptor{Type: "authentication_source", PrimaryKey: int64(11), FriendlyName: "TestSource"}, + }, + { + Type: &user_model.ExternalLoginUser{ExternalID: "12"}, + Expected: TypeDescriptor{Type: "external_account", PrimaryKey: "12", FriendlyName: "12"}, + }, + { + Type: &asymkey_model.PublicKey{ID: 13, Fingerprint: "TestPublicKey"}, + Expected: TypeDescriptor{Type: "public_key", PrimaryKey: int64(13), FriendlyName: "TestPublicKey"}, + }, + { + Type: &asymkey_model.GPGKey{ID: 14, KeyID: "TestGPGKey"}, + Expected: TypeDescriptor{Type: "gpg_key", PrimaryKey: int64(14), FriendlyName: "TestGPGKey"}, + }, + { + Type: &secret_model.Secret{ID: 15, Name: "TestSecret"}, + Expected: TypeDescriptor{Type: "secret", PrimaryKey: int64(15), FriendlyName: "TestSecret"}, + }, + { + Type: &webhook_model.Webhook{ID: 16, URL: "test://webhook"}, + Expected: TypeDescriptor{Type: "webhook", PrimaryKey: int64(16), FriendlyName: "test://webhook"}, + }, + { + Type: &git_model.ProtectedTag{ID: 17, NamePattern: "TestProtectedTag"}, + Expected: TypeDescriptor{Type: "protected_tag", PrimaryKey: int64(17), FriendlyName: "TestProtectedTag"}, + }, + { + Type: &git_model.ProtectedBranch{ID: 18, RuleName: "TestProtectedBranch"}, + Expected: TypeDescriptor{Type: "protected_branch", PrimaryKey: int64(18), FriendlyName: "TestProtectedBranch"}, + }, + { + Type: &repository_model.PushMirror{ID: 19}, + Expected: TypeDescriptor{Type: "push_mirror", PrimaryKey: int64(19), FriendlyName: ""}, + }, + { + Type: &models.RepoTransfer{ID: 20}, + Expected: TypeDescriptor{Type: "repo_transfer", PrimaryKey: int64(20), FriendlyName: ""}, + }, + { + ShouldPanic: true, + Type: 1234, + }, + } + for _, c := range cases { + c.Expected.Target = c.Type + + if c.ShouldPanic { + assert.Panics(t, func() { + _ = typeToDescription(c.Type) + }) + } else { + assert.Equal(t, c.Expected, typeToDescription(c.Type), "Unexpected descriptor for type: %T", c.Type) + } + } +} diff --git a/tests/integration/audit_test.go b/tests/integration/audit_test.go new file mode 100644 index 0000000000000..734f579bd76f1 --- /dev/null +++ b/tests/integration/audit_test.go @@ -0,0 +1,200 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "context" + "net/http" + "net/url" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/audit" + + "github.com/stretchr/testify/assert" +) + +type testAppender struct { + Events []*audit.Event +} + +func (a *testAppender) Record(ctx context.Context, e *audit.Event) { + a.Events = append(a.Events, e) +} + +func (a *testAppender) Close() error { + return nil +} + +func (a *testAppender) ReleaseReopen() error { + a.Events = nil + return nil +} + +func TestAuditLogging(t *testing.T) { + a := &testAppender{} + audit.TestingOnly_AddAppender(a) + defer audit.TestingOnly_RemoveAppender(a) + + onGiteaRun(t, func(*testing.T, *url.URL) { + return + token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization) + + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs?token="+token, &api.CreateOrgOption{ + UserName: "user1_audit_org", + FullName: "User1's organization", + Description: "This organization created by user1", + Website: "https://try.gitea.io", + Location: "Shanghai", + Visibility: "limited", + }) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/user1_audit_org?token="+token, &api.EditOrgOption{ + Description: "A new description", + Website: "https://try.gitea.io/new", + Location: "Beijing", + Visibility: "private", + }) + MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "DELETE", "/api/v1/orgs/user1_audit_org?token="+token) + MakeRequest(t, req, http.StatusNoContent) + + cases := []struct { + Action audit.Action + Scope audit.TypeDescriptor + Target audit.TypeDescriptor + }{ + { + Action: audit.UserAccessTokenAdd, + Scope: audit.TypeDescriptor{Type: "user", FriendlyName: "user1"}, + Target: audit.TypeDescriptor{Type: "access_token"}, // can't test name because it depends on other tests + }, + { + Action: audit.OrganizationCreate, + Scope: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, + Target: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, + }, + { + Action: audit.OrganizationUpdate, + Scope: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, + Target: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, + }, + { + Action: audit.OrganizationVisibility, + Scope: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, + Target: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, + }, + { + Action: audit.OrganizationDelete, + Scope: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, + Target: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, + }, + } + + assert.Len(t, a.Events, len(cases)) + for i, c := range cases { + e := a.Events[i] + + assert.Equal(t, c.Action, e.Action) + + assert.Equal(t, "user", e.Doer.Type) + assert.EqualValues(t, int64(1), e.Doer.PrimaryKey) + + // Can't test PrimaryKey because it depends on other tests + + assert.Equal(t, c.Scope.Type, e.Scope.Type) + if c.Scope.FriendlyName != "" { + assert.Equal(t, c.Scope.FriendlyName, e.Scope.FriendlyName) + } + + assert.Equal(t, c.Target.Type, e.Target.Type) + if c.Target.FriendlyName != "" { + assert.Equal(t, c.Target.FriendlyName, e.Target.FriendlyName) + } + } + }) + + audit.ReleaseReopen() + + onGiteaRun(t, func(*testing.T, *url.URL) { + token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos?token="+token, &api.CreateRepoOption{ + Name: "audit_repo", + }) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequestWithJSON(t, "PATCH", "/api/v1/repos/user1/audit_repo?token="+token, &api.EditRepoOption{ + Description: util.ToPointer("A new description"), + Private: util.ToPointer(true), + }) + MakeRequest(t, req, http.StatusOK) + + req = NewRequestWithJSON(t, "PUT", "/api/v1/repos/user1/audit_repo/actions/secrets/audit_secret?token="+token, &api.CreateOrUpdateSecretOption{ + Data: "my secret", + }) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "DELETE", "/api/v1/repos/user1/audit_repo?token="+token) + MakeRequest(t, req, http.StatusNoContent) + + cases := []struct { + Action audit.Action + Scope audit.TypeDescriptor + Target audit.TypeDescriptor + }{ + { + Action: audit.UserAccessTokenAdd, + Scope: audit.TypeDescriptor{Type: "user", FriendlyName: "user1"}, + Target: audit.TypeDescriptor{Type: "access_token"}, // can't test name because it depends on other tests + }, + { + Action: audit.RepositoryCreate, + Scope: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, + Target: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, + }, + { + Action: audit.RepositoryUpdate, + Scope: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, + Target: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, + }, + { + Action: audit.RepositoryVisibility, + Scope: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, + Target: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, + }, + { + Action: audit.RepositoryDelete, + Scope: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, + Target: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, + }, + } + + assert.Len(t, a.Events, len(cases)) + for i, c := range cases { + e := a.Events[i] + + assert.Equal(t, c.Action, e.Action) + + assert.Equal(t, "user", e.Doer.Type) + assert.EqualValues(t, int64(1), e.Doer.PrimaryKey) + + // Can't test PrimaryKey because it depends on other tests + + assert.Equal(t, c.Scope.Type, e.Scope.Type) + if c.Scope.FriendlyName != "" { + assert.Equal(t, c.Scope.FriendlyName, e.Scope.FriendlyName) + } + + assert.Equal(t, c.Target.Type, e.Target.Type) + if c.Target.FriendlyName != "" { + assert.Equal(t, c.Target.FriendlyName, e.Target.FriendlyName) + } + } + }) +} diff --git a/tests/mssql.ini.tmpl b/tests/mssql.ini.tmpl index d4562586f6bbc..40702b47941d8 100644 --- a/tests/mssql.ini.tmpl +++ b/tests/mssql.ini.tmpl @@ -110,3 +110,7 @@ ENABLED = true [actions] ENABLED = true + +[audit] +ENABLED = true +APPENDER = log diff --git a/tests/mysql.ini.tmpl b/tests/mysql.ini.tmpl index 15a7a379f33a4..1b2cb7f882d4c 100644 --- a/tests/mysql.ini.tmpl +++ b/tests/mysql.ini.tmpl @@ -119,3 +119,7 @@ REPLY_TO_ADDRESS = incoming+%{token}@localhost [actions] ENABLED = true + +[audit] +ENABLED = true +APPENDER = log diff --git a/tests/mysql8.ini.tmpl b/tests/mysql8.ini.tmpl index 7d4c27c03674f..2988459dc67eb 100644 --- a/tests/mysql8.ini.tmpl +++ b/tests/mysql8.ini.tmpl @@ -107,3 +107,7 @@ ENABLED = true [actions] ENABLED = true + +[audit] +ENABLED = true +APPENDER = log diff --git a/tests/pgsql.ini.tmpl b/tests/pgsql.ini.tmpl index ee1373d71c715..2d78b1ac9989a 100644 --- a/tests/pgsql.ini.tmpl +++ b/tests/pgsql.ini.tmpl @@ -131,3 +131,7 @@ ENABLED = true [actions] ENABLED = true + +[audit] +ENABLED = true +APPENDER = log diff --git a/tests/sqlite.ini.tmpl b/tests/sqlite.ini.tmpl index 74e1957113150..5a82ce2ad4c9d 100644 --- a/tests/sqlite.ini.tmpl +++ b/tests/sqlite.ini.tmpl @@ -116,3 +116,7 @@ RENDER_CONTENT_MODE=sanitized [actions] ENABLED = true + +[audit] +ENABLED = true +APPENDER = log From 59aa27f40394e79528d766c0fca13916dae68da6 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Tue, 5 Sep 2023 19:40:49 +0000 Subject: [PATCH 16/44] Apply suggestions. --- services/audit/audit.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/audit/audit.go b/services/audit/audit.go index 2c7ae090c107d..1ff3fcab50f93 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -7,7 +7,7 @@ import ( "errors" "fmt" "os" - "path" + "path/filepath" "time" "code.gitea.io/gitea/models" @@ -73,7 +73,7 @@ func Init() { case "log": a = &LogAppender{} case "file": - if err := os.MkdirAll(path.Dir(opts.Filename), os.ModePerm); err != nil { + if err := os.MkdirAll(filepath.Dir(opts.Filename), os.ModePerm); err != nil { panic(err.Error()) } From 1294a004cc9cf1f7dcd1439adfe5c006d43b6245 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Tue, 5 Sep 2023 19:53:10 +0000 Subject: [PATCH 17/44] Merge tests. --- services/audit/audit.go | 4 +- tests/integration/audit_test.go | 92 +++++++++++---------------------- 2 files changed, 31 insertions(+), 65 deletions(-) diff --git a/services/audit/audit.go b/services/audit/audit.go index 1ff3fcab50f93..74e94ccf0bf6e 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -47,11 +47,11 @@ var ( auditQueue *queue.WorkerPoolQueue[*Event] ) -func TestingOnly_AddAppender(a Appender) { +func TestingOnlyAddAppender(a Appender) { appenders = append(appenders, a) } -func TestingOnly_RemoveAppender(a Appender) { +func TestingOnlyRemoveAppender(a Appender) { for i, app := range appenders { if app == a { last := len(appenders) - 1 diff --git a/tests/integration/audit_test.go b/tests/integration/audit_test.go index 734f579bd76f1..4e8a100b6dbae 100644 --- a/tests/integration/audit_test.go +++ b/tests/integration/audit_test.go @@ -36,12 +36,11 @@ func (a *testAppender) ReleaseReopen() error { func TestAuditLogging(t *testing.T) { a := &testAppender{} - audit.TestingOnly_AddAppender(a) - defer audit.TestingOnly_RemoveAppender(a) + audit.TestingOnlyAddAppender(a) + defer audit.TestingOnlyRemoveAppender(a) onGiteaRun(t, func(*testing.T, *url.URL) { - return - token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization) + token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) req := NewRequestWithJSON(t, "POST", "/api/v1/orgs?token="+token, &api.CreateOrgOption{ UserName: "user1_audit_org", @@ -64,6 +63,25 @@ func TestAuditLogging(t *testing.T) { req = NewRequest(t, "DELETE", "/api/v1/orgs/user1_audit_org?token="+token) MakeRequest(t, req, http.StatusNoContent) + req = NewRequestWithJSON(t, "POST", "/api/v1/user/repos?token="+token, &api.CreateRepoOption{ + Name: "audit_repo", + }) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequestWithJSON(t, "PATCH", "/api/v1/repos/user1/audit_repo?token="+token, &api.EditRepoOption{ + Description: util.ToPointer("A new description"), + Private: util.ToPointer(true), + }) + MakeRequest(t, req, http.StatusOK) + + req = NewRequestWithJSON(t, "PUT", "/api/v1/repos/user1/audit_repo/actions/secrets/audit_secret?token="+token, &api.CreateOrUpdateSecretOption{ + Data: "my secret", + }) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "DELETE", "/api/v1/repos/user1/audit_repo?token="+token) + MakeRequest(t, req, http.StatusNoContent) + cases := []struct { Action audit.Action Scope audit.TypeDescriptor @@ -94,65 +112,6 @@ func TestAuditLogging(t *testing.T) { Scope: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, Target: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, }, - } - - assert.Len(t, a.Events, len(cases)) - for i, c := range cases { - e := a.Events[i] - - assert.Equal(t, c.Action, e.Action) - - assert.Equal(t, "user", e.Doer.Type) - assert.EqualValues(t, int64(1), e.Doer.PrimaryKey) - - // Can't test PrimaryKey because it depends on other tests - - assert.Equal(t, c.Scope.Type, e.Scope.Type) - if c.Scope.FriendlyName != "" { - assert.Equal(t, c.Scope.FriendlyName, e.Scope.FriendlyName) - } - - assert.Equal(t, c.Target.Type, e.Target.Type) - if c.Target.FriendlyName != "" { - assert.Equal(t, c.Target.FriendlyName, e.Target.FriendlyName) - } - } - }) - - audit.ReleaseReopen() - - onGiteaRun(t, func(*testing.T, *url.URL) { - token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - - req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos?token="+token, &api.CreateRepoOption{ - Name: "audit_repo", - }) - MakeRequest(t, req, http.StatusCreated) - - req = NewRequestWithJSON(t, "PATCH", "/api/v1/repos/user1/audit_repo?token="+token, &api.EditRepoOption{ - Description: util.ToPointer("A new description"), - Private: util.ToPointer(true), - }) - MakeRequest(t, req, http.StatusOK) - - req = NewRequestWithJSON(t, "PUT", "/api/v1/repos/user1/audit_repo/actions/secrets/audit_secret?token="+token, &api.CreateOrUpdateSecretOption{ - Data: "my secret", - }) - MakeRequest(t, req, http.StatusCreated) - - req = NewRequest(t, "DELETE", "/api/v1/repos/user1/audit_repo?token="+token) - MakeRequest(t, req, http.StatusNoContent) - - cases := []struct { - Action audit.Action - Scope audit.TypeDescriptor - Target audit.TypeDescriptor - }{ - { - Action: audit.UserAccessTokenAdd, - Scope: audit.TypeDescriptor{Type: "user", FriendlyName: "user1"}, - Target: audit.TypeDescriptor{Type: "access_token"}, // can't test name because it depends on other tests - }, { Action: audit.RepositoryCreate, Scope: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, @@ -168,6 +127,11 @@ func TestAuditLogging(t *testing.T) { Scope: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, Target: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, }, + { + Action: audit.UserSecretAdd, + Scope: audit.TypeDescriptor{Type: "user", FriendlyName: "user1"}, + Target: audit.TypeDescriptor{Type: "secret", FriendlyName: "AUDIT_SECRET"}, + }, { Action: audit.RepositoryDelete, Scope: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, @@ -197,4 +161,6 @@ func TestAuditLogging(t *testing.T) { } } }) + + audit.ReleaseReopen() } From d2930ecd52d35bac105474b281eb9e6ea0a595c5 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Wed, 6 Sep 2023 08:57:30 +0200 Subject: [PATCH 18/44] Apply suggestions from code review Co-authored-by: silverwind --- custom/conf/app.example.ini | 2 +- docs/content/administration/config-cheat-sheet.en-us.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 895c5a2a2740d..3fa9671791219 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -653,7 +653,7 @@ LEVEL = Info ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Enable logging of audit events ;ENABLED = false -;; Comma seperated list of appenders to log to (possible values: log, file) +;; Comma-separated list of destinations to output to (possible values: log, file) ;APPENDER = ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Creating specific log configuration diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index ba80b6bd972a2..9adcad456a1da 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -895,7 +895,7 @@ Default templates for project boards: ## Audit Log (`audit`) - `ENABLED`: **false**: Enable logging of audit events -- `APPENDER`: **\**: Comma seperated list of appenders to log to (possible values: `log` or `file`). +- `APPENDER`: **\**: Comma-separated list of destinations to output to (possible values: `log` or `file`). ## File Audit Log (`audit.file`) From bf5f725bf9ea4f4af432abcf63c382ce9355613f Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Wed, 6 Sep 2023 07:03:30 +0000 Subject: [PATCH 19/44] Fix format string. --- services/audit/appender.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/audit/appender.go b/services/audit/appender.go index c752b85bb4055..d917f98451834 100644 --- a/services/audit/appender.go +++ b/services/audit/appender.go @@ -22,7 +22,7 @@ type Appender interface { type LogAppender struct{} func (a *LogAppender) Record(ctx context.Context, e *Event) { - log.Info(e.Message) + log.Info("%s", e.Message) } func (a *LogAppender) Close() error { From d1445eb0c418ee0d5fd91caed326ec5dc87fc8a6 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Wed, 6 Sep 2023 12:09:00 +0100 Subject: [PATCH 20/44] Change log message. --- services/audit/appender.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/services/audit/appender.go b/services/audit/appender.go index d917f98451834..ab7aed9dccacb 100644 --- a/services/audit/appender.go +++ b/services/audit/appender.go @@ -5,6 +5,7 @@ package audit import ( "context" + "fmt" "io" "code.gitea.io/gitea/modules/json" @@ -22,7 +23,14 @@ type Appender interface { type LogAppender struct{} func (a *LogAppender) Record(ctx context.Context, e *Event) { - log.Info("%s", e.Message) + log.Info("Audit: %s (%s %s %s)", e.Message, formatDescriptor(e.Doer), formatDescriptor(e.Scope), formatDescriptor(e.Target)) +} + +func formatDescriptor(desc TypeDescriptor) string { + if desc.FriendlyName == "" { + return fmt.Sprintf("[%s: %v]", desc.Type, desc.PrimaryKey) + } + return fmt.Sprintf("[%s: %v, %s]", desc.Type, desc.PrimaryKey, desc.FriendlyName) } func (a *LogAppender) Close() error { From 1a28a979d83917c72c3bfc17ec9a818a889a457d Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 16 Sep 2023 16:55:56 +0000 Subject: [PATCH 21/44] Use new PushMirror.RemoteAddress. --- services/audit/audit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/audit/audit.go b/services/audit/audit.go index 74e94ccf0bf6e..e6c4bfd733a1f 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -195,7 +195,7 @@ func typeToDescription(val any) TypeDescriptor { case *git_model.ProtectedBranch: return TypeDescriptor{"protected_branch", t.ID, t.RuleName, val} case *repository_model.PushMirror: - return TypeDescriptor{"push_mirror", t.ID, "", val} + return TypeDescriptor{"push_mirror", t.ID, t.RemoteAddress, val} case *models.RepoTransfer: return TypeDescriptor{"repo_transfer", t.ID, "", val} default: From 5d5163939bfe861553ade3da238b9e9b86ca1b6c Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 22 Sep 2023 12:11:26 +0000 Subject: [PATCH 22/44] Add some suggestions. --- docs/content/administration/audit-logging.en-us.md | 2 +- models/auth/token.go | 13 +++++++++++++ routers/web/admin/hooks.go | 6 +++--- routers/web/org/setting.go | 6 +++--- routers/web/repo/setting/collaboration.go | 6 +++--- routers/web/repo/setting/webhook.go | 6 +++--- routers/web/user/setting/applications.go | 14 +++++--------- routers/web/user/setting/security/security.go | 12 ++++++------ routers/web/user/setting/webhooks.go | 8 ++++---- services/asymkey/deploy_key.go | 4 +++- services/wiki/wiki.go | 2 +- 11 files changed, 45 insertions(+), 34 deletions(-) diff --git a/docs/content/administration/audit-logging.en-us.md b/docs/content/administration/audit-logging.en-us.md index 45ea2eb48b89e..1cd4d41ee8a79 100644 --- a/docs/content/administration/audit-logging.en-us.md +++ b/docs/content/administration/audit-logging.en-us.md @@ -130,7 +130,7 @@ Audit events are grouped by `user`, `organization`, `repository` and `system`. | `repository:transfer:start` | Started repository transfer | | `repository:transfer:accept` | Accepted repository transfer | | `repository:transfer:reject` | Rejected repository transfer | -| `repository:wiki:delete` | Deleted wiki data of repository | +| `repository:wiki:delete` | Deleted wiki of repository | | `repository:collaborator:add` | Added user as collaborator for repository | | `repository:collaborator:access` | Changed access mode of collaborator | | `repository:collaborator:remove` | Removed user as collaborator of repository | diff --git a/models/auth/token.go b/models/auth/token.go index 8abcc622bc89c..183436c804871 100644 --- a/models/auth/token.go +++ b/models/auth/token.go @@ -237,6 +237,19 @@ func CountAccessTokens(ctx context.Context, opts ListAccessTokensOptions) (int64 return sess.Count(&AccessToken{}) } +func GetAccessTokenByID(ctx context.Context, id, userID int64) (*AccessToken, error) { + t := &AccessToken{ + UID: userID, + } + has, err := db.GetEngine(ctx).ID(id).Get(t) + if err != nil { + return nil, err + } else if !has { + return nil, ErrAccessTokenNotExist{} + } + return t, nil +} + // DeleteAccessTokenByID deletes access token by given ID. func DeleteAccessTokenByID(ctx context.Context, id, userID int64) error { cnt, err := db.GetEngine(ctx).ID(id).Delete(&AccessToken{ diff --git a/routers/web/admin/hooks.go b/routers/web/admin/hooks.go index 5f5cd0b7f57f6..b4a791486535f 100644 --- a/routers/web/admin/hooks.go +++ b/routers/web/admin/hooks.go @@ -62,11 +62,9 @@ func DefaultOrSystemWebhooks(ctx *context.Context) { // DeleteDefaultOrSystemWebhook handler to delete an admin-defined system or default webhook func DeleteDefaultOrSystemWebhook(ctx *context.Context) { - defer ctx.JSONRedirect(setting.AppSubURL + "/admin/hooks") - hook, err := webhook.GetSystemOrDefaultWebhook(ctx, ctx.FormInt64("id")) if err != nil { - ctx.Flash.Error("GetWebhookByOwnerID: " + err.Error()) + ctx.ServerError("GetSystemOrDefaultWebhook", err) return } @@ -77,4 +75,6 @@ func DeleteDefaultOrSystemWebhook(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } + + ctx.JSONRedirect(setting.AppSubURL + "/admin/hooks") } diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 12354613cf40e..7e96c88245e36 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -244,11 +244,9 @@ func Webhooks(ctx *context.Context) { // DeleteWebhook response for delete webhook func DeleteWebhook(ctx *context.Context) { - defer ctx.JSONRedirect(ctx.Org.OrgLink + "/settings/hooks") - hook, err := webhook.GetWebhookByOwnerID(ctx.Org.Organization.ID, ctx.FormInt64("id")) if err != nil { - ctx.Flash.Error("GetWebhookByOwnerID: " + err.Error()) + ctx.ServerError("GetWebhookByOwnerID", err) return } @@ -259,6 +257,8 @@ func DeleteWebhook(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } + + ctx.JSONRedirect(ctx.Org.OrgLink + "/settings/hooks") } // Labels render organization labels page diff --git a/routers/web/repo/setting/collaboration.go b/routers/web/repo/setting/collaboration.go index 2b49ea343fdeb..436d0214baf24 100644 --- a/routers/web/repo/setting/collaboration.go +++ b/routers/web/repo/setting/collaboration.go @@ -139,11 +139,9 @@ func ChangeCollaborationAccessMode(ctx *context.Context) { // DeleteCollaboration delete a collaboration for a repository func DeleteCollaboration(ctx *context.Context) { - defer ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/collaboration") - u, err := user_model.GetUserByID(ctx, ctx.FormInt64("id")) if err != nil { - ctx.Flash.Error("GetUserByID: " + err.Error()) + ctx.ServerError("GetUserByID", err) return } @@ -154,6 +152,8 @@ func DeleteCollaboration(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success")) } + + ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/collaboration") } // AddTeamPost response for adding a team to a repository diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go index 7dfb4aa59afa8..3edb39c952d45 100644 --- a/routers/web/repo/setting/webhook.go +++ b/routers/web/repo/setting/webhook.go @@ -762,11 +762,9 @@ func ReplayWebhook(ctx *context.Context) { // DeleteWebhook delete a webhook func DeleteWebhook(ctx *context.Context) { - defer ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/hooks") - hook, err := webhook.GetWebhookByRepoID(ctx.Repo.Repository.ID, ctx.FormInt64("id")) if err != nil { - ctx.Flash.Error("GetWebhookByRepoID: " + err.Error()) + ctx.ServerError("GetWebhookByRepoID", err) return } @@ -777,4 +775,6 @@ func DeleteWebhook(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } + + ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/hooks") } diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index e16c750be3ef8..ca7e65599ce6e 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -8,7 +8,6 @@ import ( "net/http" auth_model "code.gitea.io/gitea/models/auth" - "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" @@ -81,24 +80,21 @@ func ApplicationsPost(ctx *context.Context) { // DeleteApplication response for delete user access token func DeleteApplication(ctx *context.Context) { - defer ctx.JSONRedirect(setting.AppSubURL + "/user/settings/applications") - - t := &auth_model.AccessToken{UID: ctx.Doer.ID} - has, err := db.GetByID(ctx, ctx.FormInt64("id"), t) + t, err := auth_model.GetAccessTokenByID(ctx, ctx.FormInt64("id"), ctx.Doer.ID) if err != nil { - ctx.Flash.Error("GetByID: " + err.Error()) - return - } else if !has { + ctx.ServerError("GetAccessTokenByID", err) return } - if err := auth_model.DeleteAccessTokenByID(ctx, ctx.FormInt64("id"), ctx.Doer.ID); err != nil { + if err := auth_model.DeleteAccessTokenByID(ctx, t.ID, ctx.Doer.ID); err != nil { ctx.Flash.Error("DeleteAccessTokenByID: " + err.Error()) } else { audit.Record(audit.UserAccessTokenRemove, ctx.Doer, ctx.Doer, t, "Removed access token %s from user %s.", t.Name, ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.delete_token_success")) } + + ctx.JSONRedirect(setting.AppSubURL + "/user/settings/applications") } func loadApplicationsData(ctx *context.Context) { diff --git a/routers/web/user/setting/security/security.go b/routers/web/user/setting/security/security.go index ff53eacbacec6..24ed0b37dfbfc 100644 --- a/routers/web/user/setting/security/security.go +++ b/routers/web/user/setting/security/security.go @@ -38,25 +38,25 @@ func Security(ctx *context.Context) { // DeleteAccountLink delete a single account link func DeleteAccountLink(ctx *context.Context) { - defer ctx.JSONRedirect(setting.AppSubURL + "/user/settings/security") - elu := &user_model.ExternalLoginUser{UserID: ctx.Doer.ID, LoginSourceID: ctx.FormInt64("id")} if has, err := user_model.GetExternalLogin(elu); err != nil || !has { if !has { err = user_model.ErrExternalLoginUserNotExist{UserID: elu.UserID, LoginSourceID: elu.LoginSourceID} } - ctx.Flash.Error("RemoveAccountLink: " + err.Error()) + ctx.ServerError("RemoveAccountLink", err) return } if _, err := user_model.RemoveAccountLink(ctx.Doer, elu.LoginSourceID); err != nil { ctx.Flash.Error("RemoveAccountLink: " + err.Error()) return - } + } else { + audit.Record(audit.UserExternalLoginRemove, ctx.Doer, ctx.Doer, elu, "Removed external login %s for user %s.", elu.ExternalID, ctx.Doer.Name) - audit.Record(audit.UserExternalLoginRemove, ctx.Doer, ctx.Doer, elu, "Removed external login %s for user %s.", elu.ExternalID, ctx.Doer.Name) + ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success")) + } - ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success")) + ctx.JSONRedirect(setting.AppSubURL + "/user/settings/security") } func loadSecurityData(ctx *context.Context) { diff --git a/routers/web/user/setting/webhooks.go b/routers/web/user/setting/webhooks.go index 062e1634c6deb..429975867797f 100644 --- a/routers/web/user/setting/webhooks.go +++ b/routers/web/user/setting/webhooks.go @@ -37,19 +37,19 @@ func Webhooks(ctx *context.Context) { // DeleteWebhook response for delete webhook func DeleteWebhook(ctx *context.Context) { - defer ctx.JSONRedirect(setting.AppSubURL + "/user/settings/hooks") - hook, err := webhook.GetWebhookByOwnerID(ctx.Doer.ID, ctx.FormInt64("id")) if err != nil { - ctx.Flash.Error("GetWebhookByOwnerID: " + err.Error()) + ctx.ServerError("GetWebhookByOwnerID", err) return } - if err := webhook.DeleteWebhookByOwnerID(ctx.Doer.ID, ctx.FormInt64("id")); err != nil { + if err := webhook.DeleteWebhookByOwnerID(ctx.Doer.ID, hook.ID); err != nil { ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error()) } else { audit.Record(audit.UserWebhookRemove, ctx.Doer, ctx.Doer, hook, "Removed webhook %s.", hook.URL) ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } + + ctx.JSONRedirect(setting.AppSubURL + "/user/settings/hooks") } diff --git a/services/asymkey/deploy_key.go b/services/asymkey/deploy_key.go index e8cf58d10433d..76a66342261ea 100644 --- a/services/asymkey/deploy_key.go +++ b/services/asymkey/deploy_key.go @@ -4,6 +4,7 @@ package asymkey import ( + "errors" "fmt" "code.gitea.io/gitea/models" @@ -11,6 +12,7 @@ import ( "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/audit" ) @@ -23,7 +25,7 @@ func DeleteDeployKey(doer *user_model.User, id int64) error { defer committer.Close() key, err := asymkey_model.GetDeployKeyByID(ctx, id) - if err != nil && !asymkey_model.IsErrDeployKeyNotExist(err) { + if err != nil && !errors.Is(err, util.ErrNotExist) { return fmt.Errorf("GetDeployKeyByID: %w", err) } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index a077b5dea13ba..ece8585df3d79 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -347,7 +347,7 @@ func DeleteWiki(ctx context.Context, doer *user_model.User, repo *repo_model.Rep system_model.RemoveAllWithNotice(ctx, "Delete repository wiki", repo.WikiPath()) - audit.Record(audit.RepositoryWikiDelete, doer, repo, repo, "Deleted wiki data of repository %s.", repo.FullName()) + audit.Record(audit.RepositoryWikiDelete, doer, repo, repo, "Deleted wiki of repository %s.", repo.FullName()) return nil } From 4c6eabed2d7a787721618d2e2b545798b414f5a3 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 22 Sep 2023 12:15:47 +0000 Subject: [PATCH 23/44] Change sudo target. --- routers/api/v1/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 2d46134f571f9..bce7fe81cf972 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -121,7 +121,7 @@ func sudo() func(ctx *context.APIContext) { return } - audit.Record(audit.UserImpersonation, ctx.Doer, ctx.Doer, ctx.Doer, "User %s impersonating user %s.", ctx.Doer.Name, user.Name) + audit.Record(audit.UserImpersonation, ctx.Doer, ctx.Doer, user, "User %s impersonating user %s.", ctx.Doer.Name, user.Name) log.Trace("Sudo from (%s) to: %s", ctx.Doer.Name, user.Name) ctx.Doer = user From ec6ae2f0dea1b1e491abb7c60d4ccee2f282c63f Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 22 Sep 2023 13:45:02 +0000 Subject: [PATCH 24/44] Remove ReleaseReopen. --- modules/graceful/manager_unix.go | 1 - routers/private/manager.go | 7 ------- services/audit/appender.go | 9 --------- services/audit/audit.go | 11 ----------- services/repository/delete_test.go | 1 - 5 files changed, 29 deletions(-) diff --git a/modules/graceful/manager_unix.go b/modules/graceful/manager_unix.go index bc0a2ddb502d1..b1fd6da76dd1d 100644 --- a/modules/graceful/manager_unix.go +++ b/modules/graceful/manager_unix.go @@ -188,7 +188,6 @@ func (g *Manager) handleSignals(ctx context.Context) { if err := releasereopen.GetManager().ReleaseReopen(); err != nil { log.Error("Error whilst releasing and reopening logs: %v", err) } - // TODO audit.ReleaseReopen case syscall.SIGUSR2: log.Warn("PID %d. Received SIGUSR2. Hammering...", pid) g.DoImmediateHammer() diff --git a/routers/private/manager.go b/routers/private/manager.go index abe99036734b1..397e6fac7bf50 100644 --- a/routers/private/manager.go +++ b/routers/private/manager.go @@ -17,7 +17,6 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/web" - "code.gitea.io/gitea/services/audit" ) // ReloadTemplates reloads all the templates @@ -78,12 +77,6 @@ func ReleaseReopenLogging(ctx *context.PrivateContext) { }) return } - if err := audit.ReleaseReopen(); err != nil { - ctx.JSON(http.StatusInternalServerError, private.Response{ - Err: fmt.Sprintf("Error during audit release and reopen: %v", err), - }) - return - } ctx.PlainText(http.StatusOK, "success") } diff --git a/services/audit/appender.go b/services/audit/appender.go index ab7aed9dccacb..bb9bc85108542 100644 --- a/services/audit/appender.go +++ b/services/audit/appender.go @@ -16,7 +16,6 @@ import ( type Appender interface { Record(context.Context, *Event) Close() error - ReleaseReopen() error } // LogAppender writes an info log entry for every audit event @@ -37,10 +36,6 @@ func (a *LogAppender) Close() error { return nil } -func (a *LogAppender) ReleaseReopen() error { - return nil -} - // File writes json object for every audit event type FileAppender struct { rfw *rotatingfilewriter.RotatingFileWriter @@ -65,10 +60,6 @@ func (a *FileAppender) Close() error { return a.rfw.Close() } -func (a *FileAppender) ReleaseReopen() error { - return a.rfw.ReleaseReopen() -} - func WriteEventAsJSON(w io.Writer, e *Event) error { return json.NewEncoder(w).Encode(e) } diff --git a/services/audit/audit.go b/services/audit/audit.go index e6c4bfd733a1f..60cd01d34c3f9 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -4,7 +4,6 @@ package audit import ( - "errors" "fmt" "os" "path/filepath" @@ -202,13 +201,3 @@ func typeToDescription(val any) TypeDescriptor { panic(fmt.Sprintf("unsupported type: %T", t)) } } - -func ReleaseReopen() error { - var joinedErr error - for _, a := range appenders { - if err := a.ReleaseReopen(); err != nil { - joinedErr = errors.Join(joinedErr, err) - } - } - return joinedErr -} diff --git a/services/repository/delete_test.go b/services/repository/delete_test.go index a50fb305509fd..288c41d6cb41b 100644 --- a/services/repository/delete_test.go +++ b/services/repository/delete_test.go @@ -43,5 +43,4 @@ func TestTeam_RemoveRepository(t *testing.T) { } testSuccess(2, 3) testSuccess(2, 5) - testSuccess(1, unittest.NonexistentID) } From 10fed1c1054345f1fdc1d4e7788511aa9a0c7616 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 22 Sep 2023 13:50:14 +0000 Subject: [PATCH 25/44] Remove todo. --- models/asymkey/ssh_key.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/asymkey/ssh_key.go b/models/asymkey/ssh_key.go index c7618405029f5..99938a100d631 100644 --- a/models/asymkey/ssh_key.go +++ b/models/asymkey/ssh_key.go @@ -431,7 +431,7 @@ func SynchronizePublicKeys(usr *user_model.User, s *auth.Source, sshPublicKeys [ } } - addedKeys := AddPublicKeysBySource(usr, s, newKeys) // ToDo Audit + addedKeys := AddPublicKeysBySource(usr, s, newKeys) // Mark keys from DB that no longer exist in the source for deletion var giteaKeysToDelete []string From bbb03222107d41234b93ccf52e01ba2a185b37dc Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 23 Sep 2023 07:45:35 +0000 Subject: [PATCH 26/44] Use existing cron user. --- services/cron/tasks_extended.go | 4 ++-- services/user/user.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/cron/tasks_extended.go b/services/cron/tasks_extended.go index 48ea87df7fb57..74db64c219451 100644 --- a/services/cron/tasks_extended.go +++ b/services/cron/tasks_extended.go @@ -29,9 +29,9 @@ func registerDeleteInactiveUsers() { Schedule: "@annually", }, OlderThan: time.Minute * time.Duration(setting.Service.ActiveCodeLives), - }, func(ctx context.Context, _ *user_model.User, config Config) error { + }, func(ctx context.Context, doer *user_model.User, config Config) error { olderThanConfig := config.(*OlderThanConfig) - return user_service.DeleteInactiveUsers(ctx, olderThanConfig.OlderThan) + return user_service.DeleteInactiveUsers(ctx, doer, olderThanConfig.OlderThan) }) } diff --git a/services/user/user.go b/services/user/user.go index 582a62cf53d37..6ccef39c66a3c 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -300,7 +300,7 @@ func DeleteUser(ctx context.Context, doer, u *user_model.User, purge bool) error } // DeleteInactiveUsers deletes all inactive users and email addresses. -func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error { +func DeleteInactiveUsers(ctx context.Context, doer *user_model.User, olderThan time.Duration) error { users, err := user_model.GetInactiveUsers(ctx, olderThan) if err != nil { return err @@ -313,7 +313,7 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error { return db.ErrCancelledf("Before delete inactive user %s", u.Name) default: } - if err := DeleteUser(ctx, user_model.NewGhostUser(), u, false); err != nil { + if err := DeleteUser(ctx, doer, u, false); err != nil { // Ignore users that were set inactive by admin. if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) || models.IsErrUserOwnPackages(err) { continue From 41287b98abd2d392d43d73ed0108d124e5eee487 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sun, 12 Nov 2023 18:50:01 +0000 Subject: [PATCH 27/44] Add ip address to audit log. --- cmd/admin_auth.go | 4 +- cmd/admin_auth_ldap.go | 4 +- cmd/admin_user_create.go | 4 +- cmd/admin_user_generate_access_token.go | 2 +- modules/web/middleware/request.go | 14 ++++++ routers/api/v1/admin/org.go | 2 +- routers/api/v1/admin/user.go | 14 +++--- routers/api/v1/api.go | 2 +- routers/api/v1/org/org.go | 6 +-- routers/api/v1/org/team.go | 12 ++--- routers/api/v1/repo/collaborators.go | 6 +-- routers/api/v1/repo/repo.go | 4 +- routers/api/v1/repo/transfer.go | 2 +- routers/api/v1/user/app.go | 10 ++-- routers/common/middleware.go | 10 +++- routers/install/install.go | 2 +- routers/private/hook_post_receive.go | 2 +- routers/web/admin/auths.go | 4 +- routers/web/admin/emails.go | 2 +- routers/web/admin/hooks.go | 2 +- routers/web/admin/users.go | 14 +++--- routers/web/auth/2fa.go | 2 +- routers/web/auth/auth.go | 6 +-- routers/web/auth/oauth.go | 10 ++-- routers/web/auth/openid.go | 4 +- routers/web/auth/password.go | 8 ++-- routers/web/org/org.go | 2 +- routers/web/org/setting.go | 8 ++-- routers/web/org/teams.go | 22 ++++----- routers/web/repo/repo.go | 2 +- routers/web/repo/setting/collaboration.go | 8 ++-- routers/web/repo/setting/default_branch.go | 2 +- routers/web/repo/setting/deploy_key.go | 2 +- routers/web/repo/setting/protected_branch.go | 6 +-- routers/web/repo/setting/protected_tag.go | 6 +-- routers/web/repo/setting/setting.go | 20 ++++---- routers/web/repo/setting/webhook.go | 6 +-- routers/web/user/setting/account.go | 6 +-- routers/web/user/setting/applications.go | 4 +- routers/web/user/setting/keys.go | 8 ++-- routers/web/user/setting/oauth2_common.go | 10 ++-- routers/web/user/setting/profile.go | 4 +- routers/web/user/setting/security/2fa.go | 6 +-- routers/web/user/setting/security/openid.go | 4 +- routers/web/user/setting/security/security.go | 2 +- routers/web/user/setting/security/webauthn.go | 4 +- routers/web/user/setting/webhooks.go | 2 +- services/asymkey/deploy_key.go | 2 +- services/asymkey/ssh_key.go | 4 +- services/audit/audit.go | 46 +++++++++++++------ services/auth/basic.go | 2 +- services/auth/reverseproxy.go | 2 +- services/auth/source.go | 2 +- .../auth/source/ldap/source_authenticate.go | 12 ++--- services/auth/source/ldap/source_sync.go | 16 +++---- .../auth/source/pam/source_authenticate.go | 2 +- .../auth/source/smtp/source_authenticate.go | 2 +- services/auth/source/source_group_sync.go | 4 +- services/auth/sspi.go | 2 +- services/externalaccount/user.go | 2 +- services/org/org.go | 2 +- services/org/repo.go | 2 +- services/repository/delete.go | 2 +- services/repository/fork.go | 4 +- services/repository/repository.go | 4 +- services/repository/transfer.go | 8 ++-- services/secrets/secrets.go | 6 +-- services/user/user.go | 4 +- services/wiki/wiki.go | 2 +- 69 files changed, 228 insertions(+), 190 deletions(-) diff --git a/cmd/admin_auth.go b/cmd/admin_auth.go index 71eb3896718dc..6819db4d96f05 100644 --- a/cmd/admin_auth.go +++ b/cmd/admin_auth.go @@ -95,7 +95,7 @@ func createSource(ctx context.Context, source *auth_model.Source) error { return err } - audit.Record(audit.SystemAuthenticationSourceAdd, audit.NewCLIUser(), nil, source, "Created authentication source %s [%s].", source.Name, source.Type.String()) + audit.Record(ctx, audit.SystemAuthenticationSourceAdd, audit.NewCLIUser(), nil, source, "Created authentication source %s [%s].", source.Name, source.Type.String()) return nil } @@ -105,7 +105,7 @@ func updateSource(ctx context.Context, source *auth_model.Source) error { return err } - audit.Record(audit.SystemAuthenticationSourceUpdate, audit.NewCLIUser(), nil, source, "Updated authentication source %s.", source.Name) + audit.Record(ctx, audit.SystemAuthenticationSourceUpdate, audit.NewCLIUser(), nil, source, "Updated authentication source %s.", source.Name) return nil } diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index 7cab63b691f23..c6c84bf472a4a 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -357,7 +357,7 @@ func (a *authService) addLdapSource(c *cli.Context, authType auth.Type, args ... return err } - audit.Record(audit.SystemAuthenticationSourceAdd, audit.NewCLIUser(), nil, authSource, "Created authentication source %s [%s].", authSource.Name, authSource.Type.String()) + audit.Record(ctx, audit.SystemAuthenticationSourceAdd, audit.NewCLIUser(), nil, authSource, "Created authentication source %s [%s].", authSource.Name, authSource.Type.String()) return nil } @@ -385,7 +385,7 @@ func (a *authService) updateLdapSource(c *cli.Context, authType auth.Type) error return err } - audit.Record(audit.SystemAuthenticationSourceUpdate, audit.NewCLIUser(), nil, authSource, "Updated authentication source %s.", authSource.Name) + audit.Record(ctx, audit.SystemAuthenticationSourceUpdate, audit.NewCLIUser(), nil, authSource, "Updated authentication source %s.", authSource.Name) return nil } diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index ef159eea897d9..ca57879ec4d44 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -152,7 +152,7 @@ func runCreateUser(c *cli.Context) error { return fmt.Errorf("CreateUser: %w", err) } - audit.Record(audit.UserCreate, audit.NewCLIUser(), u, u, "Created user %s.", u.Name) + audit.Record(ctx, audit.UserCreate, audit.NewCLIUser(), u, u, "Created user %s.", u.Name) if c.Bool("access-token") { t := &auth_model.AccessToken{ @@ -164,7 +164,7 @@ func runCreateUser(c *cli.Context) error { return err } - audit.Record(audit.UserAccessTokenAdd, audit.NewCLIUser(), u, t, "Added access token %s for user %s with scope %s.", t.Name, u.Name, t.Scope) + audit.Record(ctx, audit.UserAccessTokenAdd, audit.NewCLIUser(), u, t, "Added access token %s for user %s with scope %s.", t.Name, u.Name, t.Scope) fmt.Printf("Access token was successfully created... %s\n", t.Token) } diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go index 080472bced5ab..ca1de2bc6d4c1 100644 --- a/cmd/admin_user_generate_access_token.go +++ b/cmd/admin_user_generate_access_token.go @@ -85,7 +85,7 @@ func runGenerateAccessToken(c *cli.Context) error { return err } - audit.Record(audit.UserAccessTokenAdd, audit.NewCLIUser(), user, t, "Added access token %s for user %s with scope %s.", t.Name, user.Name, t.Scope) + audit.Record(ctx, audit.UserAccessTokenAdd, audit.NewCLIUser(), user, t, "Added access token %s for user %s with scope %s.", t.Name, user.Name, t.Scope) if c.Bool("raw") { fmt.Printf("%s\n", t.Token) diff --git a/modules/web/middleware/request.go b/modules/web/middleware/request.go index 0bb155df70340..2a254942c677d 100644 --- a/modules/web/middleware/request.go +++ b/modules/web/middleware/request.go @@ -4,6 +4,7 @@ package middleware import ( + "context" "net/http" "strings" ) @@ -12,3 +13,16 @@ import ( func IsAPIPath(req *http.Request) bool { return strings.HasPrefix(req.URL.Path, "/api/") } + +type contextRequestKeyType struct{} + +var contextRequestKey contextRequestKeyType + +func WithContextRequest(c context.Context, req *http.Request) context.Context { + return context.WithValue(c, contextRequestKey, req) +} + +func GetContextRequest(c context.Context) *http.Request { + req, _ := c.Value(contextRequestKey).(*http.Request) + return req +} diff --git a/routers/api/v1/admin/org.go b/routers/api/v1/admin/org.go index 58b56882f4812..4dd3979a4ff80 100644 --- a/routers/api/v1/admin/org.go +++ b/routers/api/v1/admin/org.go @@ -75,7 +75,7 @@ func CreateOrg(ctx *context.APIContext) { return } - audit.Record(audit.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) + audit.Record(ctx, audit.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) ctx.JSON(http.StatusCreated, convert.ToOrganization(ctx, org)) } diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 9491f02984635..1205c253e0796 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -144,7 +144,7 @@ func CreateUser(ctx *context.APIContext) { return } - audit.Record(audit.UserCreate, ctx.Doer, u, u, "Created user %s.", u.Name) + audit.Record(ctx, audit.UserCreate, ctx.Doer, u, u, "Created user %s.", u.Name) log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name) @@ -309,22 +309,22 @@ func EditUser(ctx *context.APIContext) { } if passwordChanged { - audit.Record(audit.UserPassword, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Password of user %s changed.", ctx.ContextUser.Name) + audit.Record(ctx, audit.UserPassword, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Password of user %s changed.", ctx.ContextUser.Name) } if auditFields.LoginSource != ctx.ContextUser.LoginSource { - audit.Record(audit.UserAuthenticationSource, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Authentication source of user %s changed.", ctx.ContextUser.Name) + audit.Record(ctx, audit.UserAuthenticationSource, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Authentication source of user %s changed.", ctx.ContextUser.Name) } if auditFields.Visibility != ctx.ContextUser.Visibility { - audit.Record(audit.UserVisibility, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Visibility of user %s changed from %s to %s.", ctx.ContextUser.Name, auditFields.Visibility.String(), ctx.ContextUser.Visibility.String()) + audit.Record(ctx, audit.UserVisibility, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Visibility of user %s changed from %s to %s.", ctx.ContextUser.Name, auditFields.Visibility.String(), ctx.ContextUser.Visibility.String()) } if auditFields.IsActive != ctx.ContextUser.IsActive { - audit.Record(audit.UserActive, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Activation status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserActiveString(ctx.ContextUser.IsActive)) + audit.Record(ctx, audit.UserActive, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Activation status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserActiveString(ctx.ContextUser.IsActive)) } if auditFields.IsAdmin != ctx.ContextUser.IsAdmin { - audit.Record(audit.UserAdmin, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Admin status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserAdminString(ctx.ContextUser.IsAdmin)) + audit.Record(ctx, audit.UserAdmin, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Admin status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserAdminString(ctx.ContextUser.IsAdmin)) } if auditFields.IsRestricted != ctx.ContextUser.IsRestricted { - audit.Record(audit.UserRestricted, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Restricted status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserRestrictedString(ctx.ContextUser.IsRestricted)) + audit.Record(ctx, audit.UserRestricted, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Restricted status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserRestrictedString(ctx.ContextUser.IsRestricted)) } log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, ctx.ContextUser.Name) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 84c60a188369c..6039f2f662db6 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -122,7 +122,7 @@ func sudo() func(ctx *context.APIContext) { return } - audit.Record(audit.UserImpersonation, ctx.Doer, ctx.Doer, user, "User %s impersonating user %s.", ctx.Doer.Name, user.Name) + audit.Record(ctx, audit.UserImpersonation, ctx.Doer, ctx.Doer, user, "User %s impersonating user %s.", ctx.Doer.Name, user.Name) log.Trace("Sudo from (%s) to: %s", ctx.Doer.Name, user.Name) ctx.Doer = user diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go index c92831a85dbe4..aafbf702c7951 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -281,7 +281,7 @@ func Create(ctx *context.APIContext) { return } - audit.Record(audit.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) + audit.Record(ctx, audit.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) ctx.JSON(http.StatusCreated, convert.ToOrganization(ctx, org)) } @@ -368,9 +368,9 @@ func Edit(ctx *context.APIContext) { return } - audit.Record(audit.OrganizationUpdate, ctx.Doer, org, org, "Updated settings of organization %s.", org.Name) + audit.Record(ctx, audit.OrganizationUpdate, ctx.Doer, org, org, "Updated settings of organization %s.", org.Name) if org.Visibility != oldVisibility { - audit.Record(audit.OrganizationVisibility, ctx.Doer, org, org, "Visibility of organization %s changed from %s to %s.", org.Name, oldVisibility.String(), org.Visibility.String()) + audit.Record(ctx, audit.OrganizationVisibility, ctx.Doer, org, org, "Visibility of organization %s changed from %s to %s.", org.Name, oldVisibility.String(), org.Visibility.String()) } ctx.JSON(http.StatusOK, convert.ToOrganization(ctx, org)) diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index 957c363096710..62abb0abed5b4 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -249,7 +249,7 @@ func CreateTeam(ctx *context.APIContext) { return } - audit.Record(audit.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, team, "Team %s was added to organization %s.", team.Name, ctx.Org.Organization.Name) + audit.Record(ctx, audit.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, team, "Team %s was added to organization %s.", team.Name, ctx.Org.Organization.Name) apiTeam, err := convert.ToTeam(ctx, team, true) if err != nil { @@ -346,9 +346,9 @@ func EditTeam(ctx *context.APIContext) { return } - audit.Record(audit.OrganizationTeamUpdate, ctx.Doer, org, team, "Updated settings of team %s/%s.", org.Name, team.Name) + audit.Record(ctx, audit.OrganizationTeamUpdate, ctx.Doer, org, team, "Updated settings of team %s/%s.", org.Name, team.Name) if isAuthChanged { - audit.Record(audit.OrganizationTeamPermission, ctx.Doer, org, team, "Permission of team %s/%s changed from %s to %s.", org.Name, team.Name, oldAccessMode.String(), team.AccessMode.String()) + audit.Record(ctx, audit.OrganizationTeamPermission, ctx.Doer, org, team, "Permission of team %s/%s changed from %s to %s.", org.Name, team.Name, oldAccessMode.String(), team.AccessMode.String()) } apiTeam, err := convert.ToTeam(ctx, team) @@ -388,7 +388,7 @@ func DeleteTeam(ctx *context.APIContext) { return } - audit.Record(audit.OrganizationTeamRemove, ctx.Doer, org, ctx.Org.Team, "Team %s was removed from organization %s.", ctx.Org.Team.Name, org.Name) + audit.Record(ctx, audit.OrganizationTeamRemove, ctx.Doer, org, ctx.Org.Team, "Team %s was removed from organization %s.", ctx.Org.Team.Name, org.Name) ctx.Status(http.StatusNoContent) } @@ -529,7 +529,7 @@ func AddTeamMember(ctx *context.APIContext) { return } - audit.Record(audit.OrganizationTeamMemberAdd, ctx.Doer, org, ctx.Org.Team, "User %s was added to team %s/%s.", u.Name, org.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit.OrganizationTeamMemberAdd, ctx.Doer, org, ctx.Org.Team, "User %s was added to team %s/%s.", u.Name, org.Name, ctx.Org.Team.Name) ctx.Status(http.StatusNoContent) } @@ -575,7 +575,7 @@ func RemoveTeamMember(ctx *context.APIContext) { return } - audit.Record(audit.OrganizationTeamMemberRemove, ctx.Doer, org, ctx.Org.Team, "User %s was removed from team %s/%s.", u.Name, org.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit.OrganizationTeamMemberRemove, ctx.Doer, org, ctx.Org.Team, "User %s was removed from team %s/%s.", u.Name, org.Name, ctx.Org.Team.Name) ctx.Status(http.StatusNoContent) } diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go index 59dd14a05473e..e167cf18a585b 100644 --- a/routers/api/v1/repo/collaborators.go +++ b/routers/api/v1/repo/collaborators.go @@ -184,7 +184,7 @@ func AddCollaborator(ctx *context.APIContext) { return } - audit.Record(audit.RepositoryCollaboratorAdd, ctx.Doer, ctx.Repo.Repository, collaborator, "Added user %s as collaborator for repository %s.", collaborator.Name, ctx.Repo.Repository.FullName()) + audit.Record(ctx, audit.RepositoryCollaboratorAdd, ctx.Doer, ctx.Repo.Repository, collaborator, "Added user %s as collaborator for repository %s.", collaborator.Name, ctx.Repo.Repository.FullName()) if form.Permission != nil { accessMode := perm.ParseAccessMode(*form.Permission) @@ -193,7 +193,7 @@ func AddCollaborator(ctx *context.APIContext) { return } - audit.Record(audit.RepositoryCollaboratorAccess, ctx.Doer, ctx.Repo.Repository, collaborator, "Changed access mode of collaborator %s to %s.", collaborator.Name, accessMode.String()) + audit.Record(ctx, audit.RepositoryCollaboratorAccess, ctx.Doer, ctx.Repo.Repository, collaborator, "Changed access mode of collaborator %s to %s.", collaborator.Name, accessMode.String()) } ctx.Status(http.StatusNoContent) @@ -245,7 +245,7 @@ func DeleteCollaborator(ctx *context.APIContext) { return } - audit.Record(audit.RepositoryCollaboratorRemove, ctx.Doer, ctx.Repo.Repository, collaborator, "Removed collaborator %s.", collaborator.Name) + audit.Record(ctx, audit.RepositoryCollaboratorRemove, ctx.Doer, ctx.Repo.Repository, collaborator, "Removed collaborator %s.", collaborator.Name) ctx.Status(http.StatusNoContent) } diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 24dd05e5c738f..bf09a7d1357b3 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -744,9 +744,9 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err return err } - audit.Record(audit.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) + audit.Record(ctx, audit.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) if visibilityChanged { - audit.Record(audit.RepositoryVisibility, ctx.Doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) + audit.Record(ctx, audit.RepositoryVisibility, ctx.Doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) } log.Trace("Repository basic settings updated: %s/%s", owner.Name, repo.Name) diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go index e2c86722700dd..8df49aa08ac3f 100644 --- a/routers/api/v1/repo/transfer.go +++ b/routers/api/v1/repo/transfer.go @@ -235,7 +235,7 @@ func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error { return err } - audit.Record(audit.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected repository transfer.") + audit.Record(ctx, audit.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected repository transfer.") return nil } diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index 9a077173a2661..1a3290ed12100 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -132,7 +132,7 @@ func CreateAccessToken(ctx *context.APIContext) { return } - audit.Record(audit.UserAccessTokenAdd, ctx.Doer, ctx.Doer, t, "Added access token %s for user %s with scope %s.", t.Name, ctx.Doer.Name, t.Scope) + audit.Record(ctx, audit.UserAccessTokenAdd, ctx.Doer, ctx.Doer, t, "Added access token %s for user %s with scope %s.", t.Name, ctx.Doer.Name, t.Scope) ctx.JSON(http.StatusCreated, &api.AccessToken{ Name: t.Name, @@ -216,7 +216,7 @@ func DeleteAccessToken(ctx *context.APIContext) { return } - audit.Record(audit.UserAccessTokenRemove, ctx.Doer, ctx.Doer, t, "Removed access token %s from user %s.", t.Name, ctx.Doer.Name) + audit.Record(ctx, audit.UserAccessTokenRemove, ctx.Doer, ctx.Doer, t, "Removed access token %s from user %s.", t.Name, ctx.Doer.Name) ctx.Status(http.StatusNoContent) } @@ -259,7 +259,7 @@ func CreateOauth2Application(ctx *context.APIContext) { } app.ClientSecret = secret - audit.Record(audit.UserOAuth2ApplicationAdd, ctx.Doer, ctx.Doer, app, "Created OAuth2 application %s.", app.Name) + audit.Record(ctx, audit.UserOAuth2ApplicationAdd, ctx.Doer, ctx.Doer, app, "Created OAuth2 application %s.", app.Name) ctx.JSON(http.StatusCreated, convert.ToOAuth2Application(app)) } @@ -339,7 +339,7 @@ func DeleteOauth2Application(ctx *context.APIContext) { return } - audit.Record(audit.UserOAuth2ApplicationRemove, ctx.Doer, ctx.Doer, app, "Removed OAuth2 application %s.", app.Name) + audit.Record(ctx, audit.UserOAuth2ApplicationRemove, ctx.Doer, ctx.Doer, app, "Removed OAuth2 application %s.", app.Name) ctx.Status(http.StatusNoContent) } @@ -428,7 +428,7 @@ func UpdateOauth2Application(ctx *context.APIContext) { return } - audit.Record(audit.UserOAuth2ApplicationUpdate, ctx.Doer, ctx.Doer, app, "Updated OAuth2 application %s.", app.Name) + audit.Record(ctx, audit.UserOAuth2ApplicationUpdate, ctx.Doer, ctx.Doer, app, "Updated OAuth2 application %s.", app.Name) ctx.JSON(http.StatusOK, convert.ToOAuth2Application(app)) } diff --git a/routers/common/middleware.go b/routers/common/middleware.go index 8a39dda179893..e39b1f48ecf24 100644 --- a/routers/common/middleware.go +++ b/routers/common/middleware.go @@ -33,7 +33,15 @@ func ProtocolMiddlewares() (handlers []any) { RenderPanicErrorPage(resp, req, err) // it should never panic } }() - req = req.WithContext(middleware.WithContextData(req.Context())) + + req = req.WithContext( + middleware.WithContextData( + middleware.WithContextRequest( + req.Context(), + req, + ), + ), + ) next.ServeHTTP(resp, req) }) }) diff --git a/routers/install/install.go b/routers/install/install.go index 5ac48e25bf4e5..1a88d7927d1a1 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -550,7 +550,7 @@ func SubmitInstall(ctx *context.Context) { u, _ = user_model.GetUserByName(ctx, u.Name) } - audit.Record(audit.UserCreate, u, u, u, "Created user %s.", u.Name) + audit.Record(ctx, audit.UserCreate, u, u, u, "Created user %s.", u.Name) nt, token, err := auth_service.CreateAuthTokenForUserID(ctx, u.ID) if err != nil { diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index 59ac1aff1500a..bf75169211a80 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -123,7 +123,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { return } - audit.Record(audit.RepositoryVisibility, doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) + audit.Record(ctx, audit.RepositoryVisibility, doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) } } diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 1fc29310ddeb4..c7d883b053429 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -324,7 +324,7 @@ func NewAuthSourcePost(ctx *context.Context) { return } - audit.Record(audit.SystemAuthenticationSourceAdd, ctx.Doer, nil, source, "Created authentication source %s [%s].", source.Name, source.Type.String()) + audit.Record(ctx, audit.SystemAuthenticationSourceAdd, ctx.Doer, nil, source, "Created authentication source %s [%s].", source.Name, source.Type.String()) log.Trace("Authentication created by admin(%s): %s", ctx.Doer.Name, form.Name) @@ -440,7 +440,7 @@ func EditAuthSourcePost(ctx *context.Context) { return } - audit.Record(audit.SystemAuthenticationSourceUpdate, ctx.Doer, nil, source, "Updated authentication source %s.", source.Name) + audit.Record(ctx, audit.SystemAuthenticationSourceUpdate, ctx.Doer, nil, source, "Updated authentication source %s.", source.Name) log.Trace("Authentication changed by admin(%s): %d", ctx.Doer.Name, source.ID) diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go index 458a120160e88..ebd5e38c1fa24 100644 --- a/routers/web/admin/emails.go +++ b/routers/web/admin/emails.go @@ -134,7 +134,7 @@ func ActivateEmail(ctx *context.Context) { ctx.Flash.Error(ctx.Tr("admin.emails.not_updated", err)) } } else { - audit.Record(audit.UserEmailActivate, ctx.Doer, u, u, "Email %s of user %s activated.", email, u.Name) + audit.Record(ctx, audit.UserEmailActivate, ctx.Doer, u, u, "Email %s of user %s activated.", email, u.Name) log.Info("Activation for User ID: %d, email: %s, primary: %v changed to %v", uid, email, primary, activate) ctx.Flash.Info(ctx.Tr("admin.emails.updated")) diff --git a/routers/web/admin/hooks.go b/routers/web/admin/hooks.go index b4a791486535f..977df3fc536c6 100644 --- a/routers/web/admin/hooks.go +++ b/routers/web/admin/hooks.go @@ -71,7 +71,7 @@ func DeleteDefaultOrSystemWebhook(ctx *context.Context) { if err := webhook.DeleteDefaultSystemWebhook(ctx, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteDefaultWebhook: " + err.Error()) } else { - audit.Record(audit.SystemWebhookRemove, ctx.Doer, nil, hook, "Removed webhook %s.", hook.URL) + audit.Record(ctx, audit.SystemWebhookRemove, ctx.Doer, nil, hook, "Removed webhook %s.", hook.URL) ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 3a36c55280836..2cc8f1f45f95f 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -207,7 +207,7 @@ func NewUserPost(ctx *context.Context) { return } - audit.Record(audit.UserCreate, ctx.Doer, u, u, "Created user %s.", u.Name) + audit.Record(ctx, audit.UserCreate, ctx.Doer, u, u, "Created user %s.", u.Name) log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name) @@ -499,22 +499,22 @@ func EditUserPost(ctx *context.Context) { } if passwordChanged { - audit.Record(audit.UserPassword, ctx.Doer, u, u, "Password of user %s changed.", u.Name) + audit.Record(ctx, audit.UserPassword, ctx.Doer, u, u, "Password of user %s changed.", u.Name) } if auditFields.LoginSource != u.LoginSource { - audit.Record(audit.UserAuthenticationSource, ctx.Doer, u, u, "Authentication source of user %s changed.", u.Name) + audit.Record(ctx, audit.UserAuthenticationSource, ctx.Doer, u, u, "Authentication source of user %s changed.", u.Name) } if auditFields.Visibility != u.Visibility { - audit.Record(audit.UserVisibility, ctx.Doer, u, u, "Visibility of user %s changed from %s to %s.", u.Name, auditFields.Visibility.String(), u.Visibility.String()) + audit.Record(ctx, audit.UserVisibility, ctx.Doer, u, u, "Visibility of user %s changed from %s to %s.", u.Name, auditFields.Visibility.String(), u.Visibility.String()) } if auditFields.IsActive != u.IsActive { - audit.Record(audit.UserActive, ctx.Doer, u, u, "Activation status of user %s changed to %s.", u.Name, audit.UserActiveString(u.IsActive)) + audit.Record(ctx, audit.UserActive, ctx.Doer, u, u, "Activation status of user %s changed to %s.", u.Name, audit.UserActiveString(u.IsActive)) } if auditFields.IsAdmin != u.IsAdmin { - audit.Record(audit.UserAdmin, ctx.Doer, u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) + audit.Record(ctx, audit.UserAdmin, ctx.Doer, u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) } if auditFields.IsRestricted != u.IsRestricted { - audit.Record(audit.UserRestricted, ctx.Doer, u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) + audit.Record(ctx, audit.UserRestricted, ctx.Doer, u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) } log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, u.Name) diff --git a/routers/web/auth/2fa.go b/routers/web/auth/2fa.go index 62cde8b4b0dc6..77327561b10ad 100644 --- a/routers/web/auth/2fa.go +++ b/routers/web/auth/2fa.go @@ -94,7 +94,7 @@ func TwoFactorPost(ctx *context.Context) { return } - audit.Record(audit.UserAuthenticationFailTwoFactor, user_model.NewGhostUser(), u, twofa, "Failed two-factor authentication for user %s.", u.Name) + audit.Record(ctx, audit.UserAuthenticationFailTwoFactor, user_model.NewGhostUser(), u, twofa, "Failed two-factor authentication for user %s.", u.Name) ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplTwofa, forms.TwoFactorAuthForm{}) } diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index d80c700a7d7ed..4915510174ddc 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -581,7 +581,7 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *us return false } - audit.Record(audit.UserCreate, audit.NewAuthenticationSourceUser(), u, u, "Created user %s.", u.Name) + audit.Record(ctx, audit.UserCreate, audit.NewAuthenticationSourceUser(), u, u, "Created user %s.", u.Name) log.Trace("Account created: %s", u.Name) return true @@ -744,7 +744,7 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) { return } - audit.Record(audit.UserActive, user, user, user, "Activation status of user %s changed to %s.", user.Name, audit.UserActiveString(user.IsActive)) + audit.Record(ctx, audit.UserActive, user, user, user, "Activation status of user %s changed to %s.", user.Name, audit.UserActiveString(user.IsActive)) log.Trace("User activated: %s", user.Name) @@ -798,7 +798,7 @@ func ActivateEmail(ctx *context.Context) { _ = ctx.Cache.Delete("MailResendLimit_" + user.LowerName) } - audit.Record(audit.UserEmailActivate, user, user, email, "Email %s of user %s activated.", email.Email, user.Name) + audit.Record(ctx, audit.UserEmailActivate, user, user, email, "Email %s of user %s activated.", email.Email, user.Name) } // FIXME: e-mail verification does not require the user to be logged in, diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 15445323727c2..72ad79ad22807 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -565,7 +565,7 @@ func GrantApplicationOAuth(ctx *context.Context) { return } - audit.Record(audit.UserOAuth2ApplicationGrant, ctx.Doer, owner, grant, "Granted OAuth2 access to application %s.", app.Name) + audit.Record(ctx, audit.UserOAuth2ApplicationGrant, ctx.Doer, owner, grant, "Granted OAuth2 access to application %s.", app.Name) if len(form.Nonce) > 0 { err := grant.SetNonce(ctx, form.Nonce) @@ -1159,10 +1159,10 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model } if changedIsAdmin { - audit.Record(audit.UserAdmin, audit.NewAuthenticationSourceUser(), u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) + audit.Record(ctx, audit.UserAdmin, audit.NewAuthenticationSourceUser(), u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) } if changedIsRestricted { - audit.Record(audit.UserRestricted, audit.NewAuthenticationSourceUser(), u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) + audit.Record(ctx, audit.UserRestricted, audit.NewAuthenticationSourceUser(), u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) } if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval { @@ -1203,10 +1203,10 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model } if changedIsAdmin { - audit.Record(audit.UserAdmin, audit.NewAuthenticationSourceUser(), u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) + audit.Record(ctx, audit.UserAdmin, audit.NewAuthenticationSourceUser(), u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) } if changedIsRestricted { - audit.Record(audit.UserRestricted, audit.NewAuthenticationSourceUser(), u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) + audit.Record(ctx, audit.UserRestricted, audit.NewAuthenticationSourceUser(), u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) } if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval { diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go index 1bf7e3aca942f..cbfce76195025 100644 --- a/routers/web/auth/openid.go +++ b/routers/web/auth/openid.go @@ -281,7 +281,7 @@ func ConnectOpenIDPost(ctx *context.Context) { return } - audit.Record(audit.UserOpenIDAdd, u, u, userOID, "Associated OpenID %s to user %s.", userOID.URI, u.Name) + audit.Record(ctx, audit.UserOpenIDAdd, u, u, userOID, "Associated OpenID %s to user %s.", userOID.URI, u.Name) ctx.Flash.Success(ctx.Tr("settings.add_openid_success")) @@ -387,7 +387,7 @@ func RegisterOpenIDPost(ctx *context.Context) { return } - audit.Record(audit.UserOpenIDAdd, u, u, userOID, "Associated OpenID %s to user %s.", userOID.URI, u.Name) + audit.Record(ctx, audit.UserOpenIDAdd, u, u, userOID, "Associated OpenID %s to user %s.", userOID.URI, u.Name) remember, _ := ctx.Session.Get("openid_signin_remember").(bool) log.Trace("Session stored openid-remember: %t", remember) diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go index 8d6b469872307..11688c03b10a6 100644 --- a/routers/web/auth/password.go +++ b/routers/web/auth/password.go @@ -88,7 +88,7 @@ func ForgotPasswdPost(ctx *context.Context) { mailer.SendResetPasswordMail(u) - audit.Record(audit.UserPasswordReset, u, u, u, "User %s requested a password reset.", u.Name) + audit.Record(ctx, audit.UserPasswordReset, u, u, u, "User %s requested a password reset.", u.Name) if setting.CacheService.Enabled { if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { @@ -213,7 +213,7 @@ func ResetPasswdPost(ctx *context.Context) { return } if !ok || twofa.LastUsedPasscode == passcode { - audit.Record(audit.UserAuthenticationFailTwoFactor, u, u, twofa, "Failed two-factor authentication for user %s.", u.Name) + audit.Record(ctx, audit.UserAuthenticationFailTwoFactor, u, u, twofa, "Failed two-factor authentication for user %s.", u.Name) ctx.Data["IsResetForm"] = true ctx.Data["Err_Passcode"] = true @@ -243,7 +243,7 @@ func ResetPasswdPost(ctx *context.Context) { return } - audit.Record(audit.UserPassword, u, u, u, "Password of user %s changed.", u.Name) + audit.Record(ctx, audit.UserPassword, u, u, u, "Password of user %s changed.", u.Name) log.Trace("User password reset: %s", u.Name) ctx.Data["IsResetFailed"] = true @@ -344,7 +344,7 @@ func MustChangePasswordPost(ctx *context.Context) { log.Trace("User updated password: %s", u.Name) - audit.Record(audit.UserPassword, u, u, u, "Password of user %s changed.", u.Name) + audit.Record(ctx, audit.UserPassword, u, u, u, "Password of user %s changed.", u.Name) if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) { middleware.DeleteRedirectToCookie(ctx.Resp) diff --git a/routers/web/org/org.go b/routers/web/org/org.go index 443c01664fdb3..e04c582558a0f 100644 --- a/routers/web/org/org.go +++ b/routers/web/org/org.go @@ -76,7 +76,7 @@ func CreatePost(ctx *context.Context) { return } - audit.Record(audit.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) + audit.Record(ctx, audit.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) log.Trace("Organization created: %s", org.Name) diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 0c3239e54a7e3..71cebb7875e2b 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -143,12 +143,12 @@ func SettingsPost(ctx *context.Context) { } } - audit.Record(audit.OrganizationUpdate, ctx.Doer, org, org, "Updated settings of organization %s.", org.Name) + audit.Record(ctx, audit.OrganizationUpdate, ctx.Doer, org, org, "Updated settings of organization %s.", org.Name) if nameChanged { - audit.Record(audit.OrganizationName, ctx.Doer, org, org, "Organization name changed from %s to %s.", oldName, org.Name) + audit.Record(ctx, audit.OrganizationName, ctx.Doer, org, org, "Organization name changed from %s to %s.", oldName, org.Name) } if org.Visibility != oldVisibility { - audit.Record(audit.OrganizationVisibility, ctx.Doer, org, org, "Visibility of organization %s changed from %s to %s.", org.Name, oldVisibility.String(), org.Visibility.String()) + audit.Record(ctx, audit.OrganizationVisibility, ctx.Doer, org, org, "Visibility of organization %s changed from %s to %s.", org.Name, oldVisibility.String(), org.Visibility.String()) } log.Trace("Organization setting updated: %s", org.Name) @@ -253,7 +253,7 @@ func DeleteWebhook(ctx *context.Context) { if err := webhook.DeleteWebhookByOwnerID(ctx, ctx.Org.Organization.ID, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error()) } else { - audit.Record(audit.OrganizationWebhookRemove, ctx.Doer, ctx.Org.Organization, hook, "Removed webhook %s.", hook.URL) + audit.Record(ctx, audit.OrganizationWebhookRemove, ctx.Doer, ctx.Org.Organization, hook, "Removed webhook %s.", hook.URL) ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index d7ece083b6561..f69871c23a8d5 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -81,7 +81,7 @@ func TeamsAction(ctx *context.Context) { } err = models.AddTeamMember(ctx, ctx.Org.Team, ctx.Doer.ID) if err == nil { - audit.Record(audit.OrganizationTeamMemberAdd, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was added to team %s/%s.", ctx.Doer.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit.OrganizationTeamMemberAdd, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was added to team %s/%s.", ctx.Doer.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) } case "leave": err = models.RemoveTeamMember(ctx, ctx.Org.Team, ctx.Doer.ID) @@ -97,7 +97,7 @@ func TeamsAction(ctx *context.Context) { return } } else { - audit.Record(audit.OrganizationTeamMemberRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was removed from team %s/%s.", ctx.Doer.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit.OrganizationTeamMemberRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was removed from team %s/%s.", ctx.Doer.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) } checkIsOrgMemberAndRedirect(ctx, ctx.Org.OrgLink+"/teams/") return @@ -126,7 +126,7 @@ func TeamsAction(ctx *context.Context) { return } } else { - audit.Record(audit.OrganizationTeamMemberRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was removed from team %s/%s.", u.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit.OrganizationTeamMemberRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was removed from team %s/%s.", u.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) } checkIsOrgMemberAndRedirect(ctx, ctx.Org.OrgLink+"/teams/"+url.PathEscape(ctx.Org.Team.LowerName)) return @@ -172,7 +172,7 @@ func TeamsAction(ctx *context.Context) { } else { err = models.AddTeamMember(ctx, ctx.Org.Team, u.ID) if err == nil { - audit.Record(audit.OrganizationTeamMemberAdd, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was added to team %s/%s.", u.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit.OrganizationTeamMemberAdd, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was added to team %s/%s.", u.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) } } @@ -277,7 +277,7 @@ func TeamsRepoAction(ctx *context.Context) { } for _, repo := range added { - audit.Record(audit.RepositoryCollaboratorTeamAdd, ctx.Doer, repo, ctx.Org.Team, "Added team %s as collaborator for %s.", ctx.Org.Team.Name, repo.FullName()) + audit.Record(ctx, audit.RepositoryCollaboratorTeamAdd, ctx.Doer, repo, ctx.Org.Team, "Added team %s as collaborator for %s.", ctx.Org.Team.Name, repo.FullName()) } case "removeall": if err := ctx.Org.Team.LoadRepositories(ctx); err != nil { @@ -291,7 +291,7 @@ func TeamsRepoAction(ctx *context.Context) { } for _, repo := range ctx.Org.Team.Repos { - audit.Record(audit.RepositoryCollaboratorTeamRemove, ctx.Doer, repo, ctx.Org.Team, "Removed team %s as collaborator from %s.", ctx.Org.Team.Name, repo.FullName()) + audit.Record(ctx, audit.RepositoryCollaboratorTeamRemove, ctx.Doer, repo, ctx.Org.Team, "Removed team %s as collaborator from %s.", ctx.Org.Team.Name, repo.FullName()) } } @@ -400,7 +400,7 @@ func NewTeamPost(ctx *context.Context) { return } - audit.Record(audit.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, t, "Team %s was added to organization %s.", t.Name, ctx.Org.Organization.Name) + audit.Record(ctx, audit.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, t, "Team %s was added to organization %s.", t.Name, ctx.Org.Organization.Name) log.Trace("Team created: %s/%s", ctx.Org.Organization.Name, t.Name) ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(t.LowerName)) @@ -582,9 +582,9 @@ func EditTeamPost(ctx *context.Context) { return } - audit.Record(audit.OrganizationTeamUpdate, ctx.Doer, ctx.Org.Organization, t, "Updated settings of team %s/%s.", ctx.Org.Organization.Name, t.Name) + audit.Record(ctx, audit.OrganizationTeamUpdate, ctx.Doer, ctx.Org.Organization, t, "Updated settings of team %s/%s.", ctx.Org.Organization.Name, t.Name) if isAuthChanged { - audit.Record(audit.OrganizationTeamPermission, ctx.Doer, ctx.Org.Organization, t, "Permission of team %s/%s changed from %s to %s.", ctx.Org.Organization.Name, t.Name, oldAccessMode.String(), t.AccessMode.String()) + audit.Record(ctx, audit.OrganizationTeamPermission, ctx.Doer, ctx.Org.Organization, t, "Permission of team %s/%s changed from %s to %s.", ctx.Org.Organization.Name, t.Name, oldAccessMode.String(), t.AccessMode.String()) } ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(t.LowerName)) @@ -595,7 +595,7 @@ func DeleteTeam(ctx *context.Context) { if err := models.DeleteTeam(ctx, ctx.Org.Team); err != nil { ctx.Flash.Error("DeleteTeam: " + err.Error()) } else { - audit.Record(audit.OrganizationTeamRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Team %s was removed from organization %s.", ctx.Org.Team.Name, ctx.Org.Organization.Name) + audit.Record(ctx, audit.OrganizationTeamRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Team %s was removed from organization %s.", ctx.Org.Team.Name, ctx.Org.Organization.Name) ctx.Flash.Success(ctx.Tr("org.teams.delete_team_success")) } @@ -641,7 +641,7 @@ func TeamInvitePost(ctx *context.Context) { return } - audit.Record(audit.OrganizationTeamMemberAdd, ctx.Doer, org, team, "User %s was added to team %s/%s.", ctx.Doer.Name, org.Name, team.Name) + audit.Record(ctx, audit.OrganizationTeamMemberAdd, ctx.Doer, org, team, "User %s was added to team %s/%s.", ctx.Doer.Name, org.Name, team.Name) if err := org_model.RemoveInviteByID(ctx, invite.ID, team.ID); err != nil { log.Error("RemoveInviteByID: %v", err) diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index f4aef07d48612..5dddc89c3c38e 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -364,7 +364,7 @@ func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) error { return err } - audit.Record(audit.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected repository transfer.") + audit.Record(ctx, audit.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected repository transfer.") ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected")) } diff --git a/routers/web/repo/setting/collaboration.go b/routers/web/repo/setting/collaboration.go index 436d0214baf24..2abd8d3608e16 100644 --- a/routers/web/repo/setting/collaboration.go +++ b/routers/web/repo/setting/collaboration.go @@ -111,7 +111,7 @@ func CollaborationPost(ctx *context.Context) { mailer.SendCollaboratorMail(u, ctx.Doer, ctx.Repo.Repository) } - audit.Record(audit.RepositoryCollaboratorAdd, ctx.Doer, ctx.Repo.Repository, u, "Added user %s as collaborator for repository %s.", u.Name, ctx.Repo.Repository.FullName()) + audit.Record(ctx, audit.RepositoryCollaboratorAdd, ctx.Doer, ctx.Repo.Repository, u, "Added user %s as collaborator for repository %s.", u.Name, ctx.Repo.Repository.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.add_collaborator_success")) ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) @@ -134,7 +134,7 @@ func ChangeCollaborationAccessMode(ctx *context.Context) { return } - audit.Record(audit.RepositoryCollaboratorAccess, ctx.Doer, ctx.Repo.Repository, u, "Changed access mode of collaborator %s to %s.", u.Name, perm.AccessMode(ctx.FormInt("mode")).String()) + audit.Record(ctx, audit.RepositoryCollaboratorAccess, ctx.Doer, ctx.Repo.Repository, u, "Changed access mode of collaborator %s to %s.", u.Name, perm.AccessMode(ctx.FormInt("mode")).String()) } // DeleteCollaboration delete a collaboration for a repository @@ -148,7 +148,7 @@ func DeleteCollaboration(ctx *context.Context) { if err := repo_service.DeleteCollaboration(ctx, ctx.Repo.Repository, u.ID); err != nil { ctx.Flash.Error("DeleteCollaboration: " + err.Error()) } else { - audit.Record(audit.RepositoryCollaboratorRemove, ctx.Doer, ctx.Repo.Repository, u, "Removed user %s as collaborator.", u.Name) + audit.Record(ctx, audit.RepositoryCollaboratorRemove, ctx.Doer, ctx.Repo.Repository, u, "Removed user %s as collaborator.", u.Name) ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success")) } @@ -221,7 +221,7 @@ func DeleteTeam(ctx *context.Context) { return } - audit.Record(audit.RepositoryCollaboratorTeamRemove, ctx.Doer, ctx.Repo.Repository, team, "Removed team %s as collaborator from %s.", team.Name, ctx.Repo.Repository.FullName()) + audit.Record(ctx, audit.RepositoryCollaboratorTeamRemove, ctx.Doer, ctx.Repo.Repository, team, "Removed team %s as collaborator from %s.", team.Name, ctx.Repo.Repository.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.remove_team_success")) ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/collaboration") diff --git a/routers/web/repo/setting/default_branch.go b/routers/web/repo/setting/default_branch.go index d56e0f8a4f6fc..0292d92f0aca8 100644 --- a/routers/web/repo/setting/default_branch.go +++ b/routers/web/repo/setting/default_branch.go @@ -55,7 +55,7 @@ func SetDefaultBranchPost(ctx *context.Context) { notify_service.ChangeDefaultBranch(ctx, repo) - audit.Record(audit.RepositoryBranchDefault, ctx.Doer, repo, repo, "Changed default branch from %s to %s.", oldBranch, branch) + audit.Record(ctx, audit.RepositoryBranchDefault, ctx.Doer, repo, repo, "Changed default branch from %s to %s.", oldBranch, branch) } log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) diff --git a/routers/web/repo/setting/deploy_key.go b/routers/web/repo/setting/deploy_key.go index 9eacec6cd60de..26e58addfe517 100644 --- a/routers/web/repo/setting/deploy_key.go +++ b/routers/web/repo/setting/deploy_key.go @@ -93,7 +93,7 @@ func DeployKeysPost(ctx *context.Context) { return } - audit.Record(audit.RepositoryDeployKeyAdd, ctx.Doer, ctx.Repo.Repository, key, "Added deploy key %s.", key.Name) + audit.Record(ctx, audit.RepositoryDeployKeyAdd, ctx.Doer, ctx.Repo.Repository, key, "Added deploy key %s.", key.Name) log.Trace("Deploy key added: %d", ctx.Repo.Repository.ID) ctx.Flash.Success(ctx.Tr("repo.settings.add_key_success", key.Name)) diff --git a/routers/web/repo/setting/protected_branch.go b/routers/web/repo/setting/protected_branch.go index 5092fe5f58185..9b6f8a7f21b78 100644 --- a/routers/web/repo/setting/protected_branch.go +++ b/routers/web/repo/setting/protected_branch.go @@ -250,9 +250,9 @@ func SettingsProtectedBranchPost(ctx *context.Context) { } if isNewProtectedBranch { - audit.Record(audit.RepositoryBranchProtectionAdd, ctx.Doer, ctx.Repo.Repository, protectBranch, "Added branch protection %s.", protectBranch.RuleName) + audit.Record(ctx, audit.RepositoryBranchProtectionAdd, ctx.Doer, ctx.Repo.Repository, protectBranch, "Added branch protection %s.", protectBranch.RuleName) } else { - audit.Record(audit.RepositoryBranchProtectionUpdate, ctx.Doer, ctx.Repo.Repository, protectBranch, "Updated branch protection %s.", protectBranch.RuleName) + audit.Record(ctx, audit.RepositoryBranchProtectionUpdate, ctx.Doer, ctx.Repo.Repository, protectBranch, "Updated branch protection %s.", protectBranch.RuleName) } // FIXME: since we only need to recheck files protected rules, we could improve this @@ -300,7 +300,7 @@ func DeleteProtectedBranchRulePost(ctx *context.Context) { return } - audit.Record(audit.RepositoryBranchProtectionRemove, ctx.Doer, ctx.Repo.Repository, rule, "Removed branch protection %s.", rule.RuleName) + audit.Record(ctx, audit.RepositoryBranchProtectionRemove, ctx.Doer, ctx.Repo.Repository, rule, "Removed branch protection %s.", rule.RuleName) ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", rule.RuleName)) ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) diff --git a/routers/web/repo/setting/protected_tag.go b/routers/web/repo/setting/protected_tag.go index c5fb8d4d2b9ef..318428a93f331 100644 --- a/routers/web/repo/setting/protected_tag.go +++ b/routers/web/repo/setting/protected_tag.go @@ -64,7 +64,7 @@ func NewProtectedTagPost(ctx *context.Context) { return } - audit.Record(audit.RepositoryTagProtectionAdd, ctx.Doer, repo, pt, "Added tag protection for %s.", pt.NamePattern) + audit.Record(ctx, audit.RepositoryTagProtectionAdd, ctx.Doer, repo, pt, "Added tag protection for %s.", pt.NamePattern) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) @@ -119,7 +119,7 @@ func EditProtectedTagPost(ctx *context.Context) { return } - audit.Record(audit.RepositoryTagProtectionUpdate, ctx.Doer, ctx.Repo.Repository, pt, "Updated tag protection for %s.", pt.NamePattern) + audit.Record(ctx, audit.RepositoryTagProtectionUpdate, ctx.Doer, ctx.Repo.Repository, pt, "Updated tag protection for %s.", pt.NamePattern) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/tags") @@ -137,7 +137,7 @@ func DeleteProtectedTagPost(ctx *context.Context) { return } - audit.Record(audit.RepositoryTagProtectionRemove, ctx.Doer, ctx.Repo.Repository, pt, "Removed tag protection for %s.", pt.NamePattern) + audit.Record(ctx, audit.RepositoryTagProtectionRemove, ctx.Doer, ctx.Repo.Repository, pt, "Removed tag protection for %s.", pt.NamePattern) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/tags") diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index 9160d02be87a9..bea4d427f8126 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -181,9 +181,9 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(audit.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) + audit.Record(ctx, audit.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) if visibilityChanged { - audit.Record(audit.RepositoryVisibility, ctx.Doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) + audit.Record(ctx, audit.RepositoryVisibility, ctx.Doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) } log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) @@ -375,7 +375,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(audit.RepositoryMirrorPushRemove, ctx.Doer, repo, m, "Removed push mirror for repository %s.", repo.FullName()) + audit.Record(ctx, audit.RepositoryMirrorPushRemove, ctx.Doer, repo, m, "Removed push mirror for repository %s.", repo.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(repo.Link() + "/settings") @@ -440,7 +440,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(audit.RepositoryMirrorPushAdd, ctx.Doer, repo, m, "Added push mirror for repository %s.", repo.FullName()) + audit.Record(ctx, audit.RepositoryMirrorPushAdd, ctx.Doer, repo, m, "Added push mirror for repository %s.", repo.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(repo.Link() + "/settings") @@ -616,7 +616,7 @@ func SettingsPost(ctx *context.Context) { } } - audit.Record(audit.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) + audit.Record(ctx, audit.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) @@ -637,7 +637,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(audit.RepositorySigningVerification, ctx.Doer, repo, repo, "Changed signing verification of repository %s to %s.", repo.FullName(), repo.TrustModel.String()) + audit.Record(ctx, audit.RepositorySigningVerification, ctx.Doer, repo, repo, "Changed signing verification of repository %s to %s.", repo.FullName(), repo.TrustModel.String()) } log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) @@ -716,7 +716,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(audit.RepositoryConvertMirror, ctx.Doer, repo, repo, "Converted repository %s from mirror to regular repository.", repo.FullName()) + audit.Record(ctx, audit.RepositoryConvertMirror, ctx.Doer, repo, repo, "Converted repository %s from mirror to regular repository.", repo.FullName()) log.Trace("Repository converted from mirror to regular: %s", repo.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.convert_succeed")) @@ -837,7 +837,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(audit.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected transfer of repository %s.", ctx.Repo.Repository.FullName()) + audit.Record(ctx, audit.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected transfer of repository %s.", ctx.Repo.Repository.FullName()) log.Trace("Repository transfer process was cancelled: %s/%s ", ctx.Repo.Owner.Name, repo.Name) ctx.Flash.Success(ctx.Tr("repo.settings.transfer_abort_success", repoTransfer.Recipient.Name)) @@ -906,7 +906,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(audit.RepositoryArchive, ctx.Doer, repo, repo, "Archived repository %s.", repo.FullName()) + audit.Record(ctx, audit.RepositoryArchive, ctx.Doer, repo, repo, "Archived repository %s.", repo.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.archive.success")) @@ -926,7 +926,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(audit.RepositoryUnarchive, ctx.Doer, repo, repo, "Unarchived repository %s.", repo.FullName()) + audit.Record(ctx, audit.RepositoryUnarchive, ctx.Doer, repo, repo, "Unarchived repository %s.", repo.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success")) diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go index 027336f068256..d2b472cfc8ecb 100644 --- a/routers/web/repo/setting/webhook.go +++ b/routers/web/repo/setting/webhook.go @@ -287,7 +287,7 @@ func createWebhook(ctx *context.Context, params webhookParams) { return } - audit.Record(orCtx.auditActionSwitch(audit.UserWebhookAdd, audit.OrganizationWebhookAdd, audit.RepositoryWebhookAdd, audit.SystemWebhookAdd), ctx.Doer, orCtx.auditScopeSwitch(), w, "Added webhook %s.", w.URL) + audit.Record(ctx, orCtx.auditActionSwitch(audit.UserWebhookAdd, audit.OrganizationWebhookAdd, audit.RepositoryWebhookAdd, audit.SystemWebhookAdd), ctx.Doer, orCtx.auditScopeSwitch(), w, "Added webhook %s.", w.URL) ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) ctx.Redirect(orCtx.Link) @@ -341,7 +341,7 @@ func editWebhook(ctx *context.Context, params webhookParams) { return } - audit.Record(orCtx.auditActionSwitch(audit.UserWebhookUpdate, audit.OrganizationWebhookUpdate, audit.RepositoryWebhookUpdate, audit.SystemWebhookUpdate), ctx.Doer, orCtx.auditScopeSwitch(), w, "Updated webhook %s.", w.URL) + audit.Record(ctx, orCtx.auditActionSwitch(audit.UserWebhookUpdate, audit.OrganizationWebhookUpdate, audit.RepositoryWebhookUpdate, audit.SystemWebhookUpdate), ctx.Doer, orCtx.auditScopeSwitch(), w, "Updated webhook %s.", w.URL) ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) @@ -771,7 +771,7 @@ func DeleteWebhook(ctx *context.Context) { if err := webhook.DeleteWebhookByRepoID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteWebhookByRepoID: " + err.Error()) } else { - audit.Record(audit.RepositoryWebhookRemove, ctx.Doer, ctx.Repo.Repository, hook, "Removed webhook %s.", hook.URL) + audit.Record(ctx, audit.RepositoryWebhookRemove, ctx.Doer, ctx.Repo.Repository, hook, "Removed webhook %s.", hook.URL) ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index 16e2b36e0ee48..96ce98650431d 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -80,7 +80,7 @@ func AccountPost(ctx *context.Context) { return } - audit.Record(audit.UserPassword, ctx.Doer, ctx.Doer, ctx.Doer, "Password of user %s changed.", ctx.Doer.Name) + audit.Record(ctx, audit.UserPassword, ctx.Doer, ctx.Doer, ctx.Doer, "Password of user %s changed.", ctx.Doer.Name) log.Trace("User password updated: %s", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.change_password_success")) @@ -218,7 +218,7 @@ func EmailPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.add_email_success")) } - audit.Record(audit.UserEmailAdd, ctx.Doer, ctx.Doer, email, "Email %s added to user %s.", email.Email, ctx.Doer.Name) + audit.Record(ctx, audit.UserEmailAdd, ctx.Doer, ctx.Doer, email, "Email %s added to user %s.", email.Email, ctx.Doer.Name) log.Trace("Email address added: %s", email.Email) ctx.Redirect(setting.AppSubURL + "/user/settings/account") @@ -237,7 +237,7 @@ func DeleteEmail(ctx *context.Context) { return } - audit.Record(audit.UserEmailRemove, ctx.Doer, ctx.Doer, email, "Email %s removed from user %s.", email.Email, ctx.Doer.Name) + audit.Record(ctx, audit.UserEmailRemove, ctx.Doer, ctx.Doer, email, "Email %s removed from user %s.", email.Email, ctx.Doer.Name) log.Trace("Email address deleted: %s", ctx.Doer.Name) diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index ca7e65599ce6e..5872ea3e742c2 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -70,7 +70,7 @@ func ApplicationsPost(ctx *context.Context) { return } - audit.Record(audit.UserAccessTokenAdd, ctx.Doer, ctx.Doer, t, "Added access token %s for user %s with scope %s.", t.Name, ctx.Doer.Name, t.Scope) + audit.Record(ctx, audit.UserAccessTokenAdd, ctx.Doer, ctx.Doer, t, "Added access token %s for user %s with scope %s.", t.Name, ctx.Doer.Name, t.Scope) ctx.Flash.Success(ctx.Tr("settings.generate_token_success")) ctx.Flash.Info(t.Token) @@ -89,7 +89,7 @@ func DeleteApplication(ctx *context.Context) { if err := auth_model.DeleteAccessTokenByID(ctx, t.ID, ctx.Doer.ID); err != nil { ctx.Flash.Error("DeleteAccessTokenByID: " + err.Error()) } else { - audit.Record(audit.UserAccessTokenRemove, ctx.Doer, ctx.Doer, t, "Removed access token %s from user %s.", t.Name, ctx.Doer.Name) + audit.Record(ctx, audit.UserAccessTokenRemove, ctx.Doer, ctx.Doer, t, "Removed access token %s from user %s.", t.Name, ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.delete_token_success")) } diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index bd4ebbcfd11b1..ed0ba1394034e 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -77,7 +77,7 @@ func KeysPost(ctx *context.Context) { return } - audit.Record(audit.UserKeyPrincipalAdd, ctx.Doer, ctx.Doer, key, "Added principal key %s.", key.Name) + audit.Record(ctx, audit.UserKeyPrincipalAdd, ctx.Doer, ctx.Doer, key, "Added principal key %s.", key.Name) ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") @@ -124,7 +124,7 @@ func KeysPost(ctx *context.Context) { } for _, key := range keys { - audit.Record(audit.UserKeyGPGAdd, ctx.Doer, ctx.Doer, key, "Added GPG key %s.", key.KeyID) + audit.Record(ctx, audit.UserKeyGPGAdd, ctx.Doer, ctx.Doer, key, "Added GPG key %s.", key.KeyID) } keyIDs := "" @@ -201,7 +201,7 @@ func KeysPost(ctx *context.Context) { return } - audit.Record(audit.UserKeySSHAdd, ctx.Doer, ctx.Doer, key, "Added SSH key %s.", key.Fingerprint) + audit.Record(ctx, audit.UserKeySSHAdd, ctx.Doer, ctx.Doer, key, "Added SSH key %s.", key.Fingerprint) ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") @@ -245,7 +245,7 @@ func DeleteKey(ctx *context.Context) { if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, key.ID); err != nil { ctx.Flash.Error("DeleteGPGKey: " + err.Error()) } else { - audit.Record(audit.UserKeyGPGRemove, ctx.Doer, ctx.Doer, key, "Removed GPG key %s.", key.KeyID) + audit.Record(ctx, audit.UserKeyGPGRemove, ctx.Doer, ctx.Doer, key, "Removed GPG key %s.", key.KeyID) ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success")) } diff --git a/routers/web/user/setting/oauth2_common.go b/routers/web/user/setting/oauth2_common.go index 52bdcbaee724d..3fb396f4cffb0 100644 --- a/routers/web/user/setting/oauth2_common.go +++ b/routers/web/user/setting/oauth2_common.go @@ -80,7 +80,7 @@ func (oa *OAuth2CommonHandlers) AddApp(ctx *context.Context) { return } - audit.Record(oa.auditActionSwitch(audit.UserOAuth2ApplicationAdd, audit.OrganizationOAuth2ApplicationAdd, audit.SystemOAuth2ApplicationAdd), oa.Doer, oa.Owner, app, "Created OAuth2 application %s.", app.Name) + audit.Record(ctx, oa.auditActionSwitch(audit.UserOAuth2ApplicationAdd, audit.OrganizationOAuth2ApplicationAdd, audit.SystemOAuth2ApplicationAdd), oa.Doer, oa.Owner, app, "Created OAuth2 application %s.", app.Name) // render the edit page with secret ctx.Flash.Success(ctx.Tr("settings.create_oauth2_application_success"), true) @@ -137,7 +137,7 @@ func (oa *OAuth2CommonHandlers) EditSave(ctx *context.Context) { ctx.Data["App"] = app - audit.Record(oa.auditActionSwitch(audit.UserOAuth2ApplicationUpdate, audit.OrganizationOAuth2ApplicationUpdate, audit.SystemOAuth2ApplicationUpdate), oa.Doer, oa.Owner, app, "Updated OAuth2 application %s.", app.Name) + audit.Record(ctx, oa.auditActionSwitch(audit.UserOAuth2ApplicationUpdate, audit.OrganizationOAuth2ApplicationUpdate, audit.SystemOAuth2ApplicationUpdate), oa.Doer, oa.Owner, app, "Updated OAuth2 application %s.", app.Name) ctx.Flash.Success(ctx.Tr("settings.update_oauth2_application_success")) ctx.Redirect(oa.BasePathList) @@ -165,7 +165,7 @@ func (oa *OAuth2CommonHandlers) RegenerateSecret(ctx *context.Context) { return } - audit.Record(oa.auditActionSwitch(audit.UserOAuth2ApplicationSecret, audit.OrganizationOAuth2ApplicationSecret, audit.SystemOAuth2ApplicationSecret), oa.Doer, oa.Owner, app, "Regenerated secret for OAuth2 application %s.", app.Name) + audit.Record(ctx, oa.auditActionSwitch(audit.UserOAuth2ApplicationSecret, audit.OrganizationOAuth2ApplicationSecret, audit.SystemOAuth2ApplicationSecret), oa.Doer, oa.Owner, app, "Regenerated secret for OAuth2 application %s.", app.Name) ctx.Flash.Success(ctx.Tr("settings.update_oauth2_application_success"), true) oa.renderEditPage(ctx) @@ -188,7 +188,7 @@ func (oa *OAuth2CommonHandlers) DeleteApp(ctx *context.Context) { return } - audit.Record(oa.auditActionSwitch(audit.UserOAuth2ApplicationRemove, audit.OrganizationOAuth2ApplicationRemove, audit.SystemOAuth2ApplicationRemove), oa.Doer, oa.Owner, app, "Removed OAuth2 application %s.", app.Name) + audit.Record(ctx, oa.auditActionSwitch(audit.UserOAuth2ApplicationRemove, audit.OrganizationOAuth2ApplicationRemove, audit.SystemOAuth2ApplicationRemove), oa.Doer, oa.Owner, app, "Removed OAuth2 application %s.", app.Name) ctx.Flash.Success(ctx.Tr("settings.remove_oauth2_application_success")) ctx.JSONRedirect(oa.BasePathList) @@ -221,7 +221,7 @@ func (oa *OAuth2CommonHandlers) RevokeGrant(ctx *context.Context) { return } - audit.Record(audit.UserOAuth2ApplicationRevoke, oa.Doer, oa.Owner, grant, "Revoked OAuth2 grant for application %s.", app.Name) + audit.Record(ctx, audit.UserOAuth2ApplicationRevoke, oa.Doer, oa.Owner, grant, "Revoked OAuth2 grant for application %s.", app.Name) ctx.Flash.Success(ctx.Tr("settings.revoke_oauth2_grant_success")) ctx.JSONRedirect(oa.BasePathList) diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 2df3509536b2c..f46704d540634 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -124,10 +124,10 @@ func ProfilePost(ctx *context.Context) { log.Trace("User settings updated: %s", ctx.Doer.Name) - audit.Record(audit.UserUpdate, ctx.Doer, ctx.Doer, ctx.Doer, "Updated settings of user %s.", ctx.Doer.Name) + audit.Record(ctx, audit.UserUpdate, ctx.Doer, ctx.Doer, ctx.Doer, "Updated settings of user %s.", ctx.Doer.Name) if oldVisibility != ctx.Doer.Visibility { - audit.Record(audit.UserVisibility, ctx.Doer, ctx.Doer, ctx.Doer, "Visibility of user %s changed from %s to %s.", ctx.Doer.Name, oldVisibility.String(), ctx.Doer.Visibility.String()) + audit.Record(ctx, audit.UserVisibility, ctx.Doer, ctx.Doer, ctx.Doer, "Visibility of user %s changed from %s to %s.", ctx.Doer.Name, oldVisibility.String(), ctx.Doer.Visibility.String()) } ctx.Flash.Success(ctx.Tr("settings.update_profile_success")) diff --git a/routers/web/user/setting/security/2fa.go b/routers/web/user/setting/security/2fa.go index ecf23e2d25564..d85ea3ad0d9fe 100644 --- a/routers/web/user/setting/security/2fa.go +++ b/routers/web/user/setting/security/2fa.go @@ -50,7 +50,7 @@ func RegenerateScratchTwoFactor(ctx *context.Context) { return } - audit.Record(audit.UserTwoFactorRegenerate, ctx.Doer, ctx.Doer, t, "User %s regenerated two-factor authentication secret.", ctx.Doer.Name) + audit.Record(ctx, audit.UserTwoFactorRegenerate, ctx.Doer, ctx.Doer, t, "User %s regenerated two-factor authentication secret.", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.twofa_scratch_token_regenerated", token)) ctx.Redirect(setting.AppSubURL + "/user/settings/security") @@ -81,7 +81,7 @@ func DisableTwoFactor(ctx *context.Context) { return } - audit.Record(audit.UserTwoFactorDisable, ctx.Doer, ctx.Doer, t, "User %s disabled two-factor authentication.", ctx.Doer.Name) + audit.Record(ctx, audit.UserTwoFactorDisable, ctx.Doer, ctx.Doer, t, "User %s disabled two-factor authentication.", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.twofa_disabled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") @@ -249,7 +249,7 @@ func EnrollTwoFactorPost(ctx *context.Context) { return } - audit.Record(audit.UserTwoFactorEnable, ctx.Doer, ctx.Doer, t, "User %s enabled two-factor authentication.", ctx.Doer.Name) + audit.Record(ctx, audit.UserTwoFactorEnable, ctx.Doer, ctx.Doer, t, "User %s enabled two-factor authentication.", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.twofa_enrolled", token)) ctx.Redirect(setting.AppSubURL + "/user/settings/security") diff --git a/routers/web/user/setting/security/openid.go b/routers/web/user/setting/security/openid.go index fa93014ffb462..3bf22931cb7c2 100644 --- a/routers/web/user/setting/security/openid.go +++ b/routers/web/user/setting/security/openid.go @@ -100,7 +100,7 @@ func settingsOpenIDVerify(ctx *context.Context) { return } - audit.Record(audit.UserOpenIDAdd, ctx.Doer, ctx.Doer, oid, "Associated OpenID %s to user %s.", oid.URI, ctx.Doer.Name) + audit.Record(ctx, audit.UserOpenIDAdd, ctx.Doer, ctx.Doer, oid, "Associated OpenID %s to user %s.", oid.URI, ctx.Doer.Name) log.Trace("Associated OpenID %s to user %s", id, ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.add_openid_success")) @@ -122,7 +122,7 @@ func DeleteOpenID(ctx *context.Context) { return } - audit.Record(audit.UserOpenIDAdd, ctx.Doer, ctx.Doer, oid, "Removed OpenID %s from user %s.", oid.URI, ctx.Doer.Name) + audit.Record(ctx, audit.UserOpenIDAdd, ctx.Doer, ctx.Doer, oid, "Removed OpenID %s from user %s.", oid.URI, ctx.Doer.Name) log.Trace("OpenID address deleted: %s", ctx.Doer.Name) diff --git a/routers/web/user/setting/security/security.go b/routers/web/user/setting/security/security.go index 6a96cea86f13c..e4d74ef246148 100644 --- a/routers/web/user/setting/security/security.go +++ b/routers/web/user/setting/security/security.go @@ -53,7 +53,7 @@ func DeleteAccountLink(ctx *context.Context) { ctx.Flash.Error("RemoveAccountLink: " + err.Error()) return } else { - audit.Record(audit.UserExternalLoginRemove, ctx.Doer, ctx.Doer, elu, "Removed external login %s for user %s.", elu.ExternalID, ctx.Doer.Name) + audit.Record(ctx, audit.UserExternalLoginRemove, ctx.Doer, ctx.Doer, elu, "Removed external login %s for user %s.", elu.ExternalID, ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success")) } diff --git a/routers/web/user/setting/security/webauthn.go b/routers/web/user/setting/security/webauthn.go index 00c9f931f11c3..55dcd7620ba9d 100644 --- a/routers/web/user/setting/security/webauthn.go +++ b/routers/web/user/setting/security/webauthn.go @@ -107,7 +107,7 @@ func WebauthnRegisterPost(ctx *context.Context) { } _ = ctx.Session.Delete("webauthnName") - audit.Record(audit.UserWebAuthAdd, ctx.Doer, ctx.Doer, dbCred, "User %s added WebAuthn key %s.", ctx.Doer.Name, dbCred.Name) + audit.Record(ctx, audit.UserWebAuthAdd, ctx.Doer, ctx.Doer, dbCred, "User %s added WebAuthn key %s.", ctx.Doer.Name, dbCred.Name) ctx.JSON(http.StatusCreated, cred) } @@ -126,7 +126,7 @@ func WebauthnDelete(ctx *context.Context) { ctx.ServerError("DeleteCredential", err) return } else if ok { - audit.Record(audit.UserWebAuthRemove, ctx.Doer, ctx.Doer, cred, "User %s removed WebAuthn key %s.", ctx.Doer.Name, cred.Name) + audit.Record(ctx, audit.UserWebAuthRemove, ctx.Doer, ctx.Doer, cred, "User %s removed WebAuthn key %s.", ctx.Doer.Name, cred.Name) } ctx.JSONRedirect(setting.AppSubURL + "/user/settings/security") diff --git a/routers/web/user/setting/webhooks.go b/routers/web/user/setting/webhooks.go index d59bec252fe26..a18ec02488915 100644 --- a/routers/web/user/setting/webhooks.go +++ b/routers/web/user/setting/webhooks.go @@ -46,7 +46,7 @@ func DeleteWebhook(ctx *context.Context) { if err := webhook.DeleteWebhookByOwnerID(ctx, ctx.Doer.ID, hook.ID); err != nil { ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error()) } else { - audit.Record(audit.UserWebhookRemove, ctx.Doer, ctx.Doer, hook, "Removed webhook %s.", hook.URL) + audit.Record(ctx, audit.UserWebhookRemove, ctx.Doer, ctx.Doer, hook, "Removed webhook %s.", hook.URL) ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } diff --git a/services/asymkey/deploy_key.go b/services/asymkey/deploy_key.go index 2722f36a6c241..62cbf1ddcaf0f 100644 --- a/services/asymkey/deploy_key.go +++ b/services/asymkey/deploy_key.go @@ -42,7 +42,7 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error return err } - audit.Record(audit.RepositoryDeployKeyRemove, doer, repo, key, "Removed deploy key %s.", key.Name) + audit.Record(ctx, audit.RepositoryDeployKeyRemove, doer, repo, key, "Removed deploy key %s.", key.Name) return asymkey_model.RewriteAllPublicKeys(ctx) } diff --git a/services/asymkey/ssh_key.go b/services/asymkey/ssh_key.go index 444f7f2efab9e..4fc46ca261f1c 100644 --- a/services/asymkey/ssh_key.go +++ b/services/asymkey/ssh_key.go @@ -49,12 +49,12 @@ func DeletePublicKey(ctx context.Context, doer *user_model.User, id int64) (err committer.Close() if key.Type == asymkey_model.KeyTypePrincipal { - audit.Record(audit.UserKeyPrincipalRemove, doer, owner, key, "Removed principal key %s.", key.Name) + audit.Record(ctx, audit.UserKeyPrincipalRemove, doer, owner, key, "Removed principal key %s.", key.Name) return asymkey_model.RewriteAllPrincipalKeys(ctx) } - audit.Record(audit.UserKeySSHRemove, doer, owner, key, "Removed SSH key %s.", key.Fingerprint) + audit.Record(ctx, audit.UserKeySSHRemove, doer, owner, key, "Removed SSH key %s.", key.Fingerprint) return asymkey_model.RewriteAllPublicKeys(ctx) } diff --git a/services/audit/audit.go b/services/audit/audit.go index 60cd01d34c3f9..4206de7ed5d69 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -4,7 +4,9 @@ package audit import ( + "context" "fmt" + "net" "os" "path/filepath" "time" @@ -23,6 +25,7 @@ import ( "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util/rotatingfilewriter" + "code.gitea.io/gitea/modules/web/middleware" ) type TypeDescriptor struct { @@ -33,12 +36,13 @@ type TypeDescriptor struct { } type Event struct { - Action Action `json:"action"` - Doer TypeDescriptor `json:"doer"` - Scope TypeDescriptor `json:"scope"` - Target TypeDescriptor `json:"target"` - Message string `json:"message"` - Time time.Time `json:"time"` + Action Action `json:"action"` + Doer TypeDescriptor `json:"doer"` + Scope TypeDescriptor `json:"scope"` + Target TypeDescriptor `json:"target"` + Message string `json:"message"` + Time time.Time `json:"time"` + IPAddress string `json:"ip_address"` } var ( @@ -114,26 +118,27 @@ func Init() { go graceful.GetManager().RunWithCancel(auditQueue) } -func Record(action Action, doer *user_model.User, scope, target any, format string, v ...any) { +func Record(ctx context.Context, action Action, doer *user_model.User, scope, target any, message string, v ...any) { if !setting.Audit.Enabled { return } - e := BuildEvent(action, doer, scope, target, format, v...) + e := BuildEvent(ctx, action, doer, scope, target, message, v...) if err := auditQueue.Push(e); err != nil { log.Error("Error pushing audit event to queue: %v", err) } } -func BuildEvent(action Action, doer *user_model.User, scope, target any, format string, v ...any) *Event { +func BuildEvent(ctx context.Context, action Action, doer *user_model.User, scope, target any, message string, v ...any) *Event { return &Event{ - Action: action, - Doer: typeToDescription(doer), - Scope: scopeToDescription(scope), - Target: typeToDescription(target), - Message: fmt.Sprintf(format, v...), - Time: time.Now(), + Action: action, + Doer: typeToDescription(doer), + Scope: scopeToDescription(scope), + Target: typeToDescription(target), + Message: fmt.Sprintf(message, v...), + Time: time.Now(), + IPAddress: tryGetIPAddress(ctx), } } @@ -201,3 +206,14 @@ func typeToDescription(val any) TypeDescriptor { panic(fmt.Sprintf("unsupported type: %T", t)) } } + +func tryGetIPAddress(ctx context.Context) string { + if req := middleware.GetContextRequest(ctx); req != nil { + host, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil { + return req.RemoteAddr + } + return host + } + return "" +} diff --git a/services/auth/basic.go b/services/auth/basic.go index 79bb7b38b8b7a..7878e6c647bb0 100644 --- a/services/auth/basic.go +++ b/services/auth/basic.go @@ -156,7 +156,7 @@ func validateTOTP(req *http.Request, u *user_model.User) error { if ok, err := twofa.ValidateTOTP(req.Header.Get("X-Gitea-OTP")); err != nil { return err } else if !ok { - audit.Record(audit.UserAuthenticationFailTwoFactor, u, u, twofa, "Failed two-factor authentication for user %s.", u.Name) + audit.Record(req.Context(), audit.UserAuthenticationFailTwoFactor, u, u, twofa, "Failed two-factor authentication for user %s.", u.Name) return util.NewInvalidArgumentErrorf("invalid provided OTP") } diff --git a/services/auth/reverseproxy.go b/services/auth/reverseproxy.go index 62b6fb7e92276..873332013b5c1 100644 --- a/services/auth/reverseproxy.go +++ b/services/auth/reverseproxy.go @@ -171,7 +171,7 @@ func (r *ReverseProxy) newUser(req *http.Request) *user_model.User { return nil } - audit.Record(audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + audit.Record(req.Context(), audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) return user } diff --git a/services/auth/source.go b/services/auth/source.go index 6a6c3941a41ca..d8af497619804 100644 --- a/services/auth/source.go +++ b/services/auth/source.go @@ -41,7 +41,7 @@ func DeleteSource(ctx context.Context, doer *user_model.User, source *auth.Sourc _, err = db.GetEngine(ctx).ID(source.ID).Delete(new(auth.Source)) if err == nil { - audit.Record(audit.SystemAuthenticationSourceRemove, doer, nil, source, "Removed authentication source %s.", source.Name) + audit.Record(ctx, audit.SystemAuthenticationSourceRemove, doer, nil, source, "Removed authentication source %s.", source.Name) } return err diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index 1aeb318ea9189..fef2bb0f1bd4f 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -78,19 +78,19 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u if user != nil { if isAdminChanged { - audit.Record(audit.UserAdmin, audit.NewAuthenticationSourceUser(), user, user, "Admin status of user %s changed to %s.", user.Name, audit.UserAdminString(user.IsAdmin)) + audit.Record(ctx, audit.UserAdmin, audit.NewAuthenticationSourceUser(), user, user, "Admin status of user %s changed to %s.", user.Name, audit.UserAdminString(user.IsAdmin)) } if isRestrictedChanged { - audit.Record(audit.UserRestricted, audit.NewAuthenticationSourceUser(), user, user, "Restricted status of user %s changed to %s.", user.Name, audit.UserRestrictedString(user.IsRestricted)) + audit.Record(ctx, audit.UserRestricted, audit.NewAuthenticationSourceUser(), user, user, "Restricted status of user %s changed to %s.", user.Name, audit.UserRestrictedString(user.IsRestricted)) } if isAttributeSSHPublicKeySet { if addedKeys, deletedKeys := asymkey_model.SynchronizePublicKeys(ctx, user, source.authSource, sr.SSHPublicKey); len(addedKeys) > 0 || len(deletedKeys) > 0 { for _, key := range addedKeys { - audit.Record(audit.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), user, user, "Added SSH key %s.", key.Fingerprint) + audit.Record(ctx, audit.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), user, user, "Added SSH key %s.", key.Fingerprint) } for _, key := range deletedKeys { - audit.Record(audit.UserKeySSHRemove, audit.NewAuthenticationSourceUser(), user, user, "Removed SSH key %s.", key.Fingerprint) + audit.Record(ctx, audit.UserKeySSHRemove, audit.NewAuthenticationSourceUser(), user, user, "Removed SSH key %s.", key.Fingerprint) } if err := asymkey_model.RewriteAllPublicKeys(ctx); err != nil { @@ -119,12 +119,12 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u return user, err } - audit.Record(audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + audit.Record(ctx, audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) if isAttributeSSHPublicKeySet { if addedKeys := asymkey_model.AddPublicKeysBySource(ctx, user, source.authSource, sr.SSHPublicKey); len(addedKeys) > 0 { for _, key := range addedKeys { - audit.Record(audit.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), user, user, "Added SSH key %s.", key.Fingerprint) + audit.Record(ctx, audit.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), user, user, "Added SSH key %s.", key.Fingerprint) } if err := asymkey_model.RewriteAllPublicKeys(ctx); err != nil { diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index 72414dedf4aa2..26da9f0ed1de8 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -133,7 +133,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { if err != nil { log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.authSource.Name, su.Username, err) } else { - audit.Record(audit.UserCreate, audit.NewAuthenticationSourceUser(), usr, usr, "Created user %s.", usr.Name) + audit.Record(ctx, audit.UserCreate, audit.NewAuthenticationSourceUser(), usr, usr, "Created user %s.", usr.Name) if isAttributeSSHPublicKeySet { log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", source.authSource.Name, usr.Name) @@ -141,7 +141,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { sshKeysNeedUpdate = true for _, key := range addedKeys { - audit.Record(audit.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), usr, usr, "Added SSH key %s.", key.Fingerprint) + audit.Record(ctx, audit.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), usr, usr, "Added SSH key %s.", key.Fingerprint) } } } @@ -157,10 +157,10 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { sshKeysNeedUpdate = true for _, key := range addedKeys { - audit.Record(audit.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), usr, usr, "Added SSH key %s.", key.Fingerprint) + audit.Record(ctx, audit.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), usr, usr, "Added SSH key %s.", key.Fingerprint) } for _, key := range deletedKeys { - audit.Record(audit.UserKeySSHRemove, audit.NewAuthenticationSourceUser(), usr, usr, "Removed SSH key %s.", key.Fingerprint) + audit.Record(ctx, audit.UserKeySSHRemove, audit.NewAuthenticationSourceUser(), usr, usr, "Removed SSH key %s.", key.Fingerprint) } } } @@ -198,13 +198,13 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { } if isActiveChanged { - audit.Record(audit.UserActive, audit.NewAuthenticationSourceUser(), usr, usr, "Activation status of user %s changed to %s.", usr.Name, audit.UserActiveString(usr.IsActive)) + audit.Record(ctx, audit.UserActive, audit.NewAuthenticationSourceUser(), usr, usr, "Activation status of user %s changed to %s.", usr.Name, audit.UserActiveString(usr.IsActive)) } if isAdminChanged { - audit.Record(audit.UserAdmin, audit.NewAuthenticationSourceUser(), usr, usr, "Admin status of user %s changed to %s.", usr.Name, audit.UserAdminString(usr.IsAdmin)) + audit.Record(ctx, audit.UserAdmin, audit.NewAuthenticationSourceUser(), usr, usr, "Admin status of user %s changed to %s.", usr.Name, audit.UserAdminString(usr.IsAdmin)) } if isRestrictedChanged { - audit.Record(audit.UserRestricted, audit.NewAuthenticationSourceUser(), usr, usr, "Restricted status of user %s changed to %s.", usr.Name, audit.UserRestrictedString(usr.IsRestricted)) + audit.Record(ctx, audit.UserRestricted, audit.NewAuthenticationSourceUser(), usr, usr, "Restricted status of user %s changed to %s.", usr.Name, audit.UserRestrictedString(usr.IsRestricted)) } } @@ -251,7 +251,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { if err := user_model.UpdateUserCols(ctx, usr, "is_active"); err != nil { log.Error("SyncExternalUsers[%s]: Error deactivating user %s: %v", source.authSource.Name, usr.Name, err) } else { - audit.Record(audit.UserActive, audit.NewAuthenticationSourceUser(), usr, usr, "Activation status of user %s changed to %s.", usr.Name, audit.UserActiveString(usr.IsActive)) + audit.Record(ctx, audit.UserActive, audit.NewAuthenticationSourceUser(), usr, usr, "Activation status of user %s changed to %s.", usr.Name, audit.UserActiveString(usr.IsActive)) } } } diff --git a/services/auth/source/pam/source_authenticate.go b/services/auth/source/pam/source_authenticate.go index 26ff14fd8ec0d..1bbf76c5e9e40 100644 --- a/services/auth/source/pam/source_authenticate.go +++ b/services/auth/source/pam/source_authenticate.go @@ -68,7 +68,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u return user, err } - audit.Record(audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + audit.Record(ctx, audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) return user, nil } diff --git a/services/auth/source/smtp/source_authenticate.go b/services/auth/source/smtp/source_authenticate.go index 892081b1eb4c3..f179529ef04d8 100644 --- a/services/auth/source/smtp/source_authenticate.go +++ b/services/auth/source/smtp/source_authenticate.go @@ -83,7 +83,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u return user, err } - audit.Record(audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + audit.Record(ctx, audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) return user, nil } diff --git a/services/auth/source/source_group_sync.go b/services/auth/source/source_group_sync.go index 6ecfe8e399231..ed66f6c5f07ab 100644 --- a/services/auth/source/source_group_sync.go +++ b/services/auth/source/source_group_sync.go @@ -106,14 +106,14 @@ func syncGroupsToTeamsCached(ctx context.Context, user *user_model.User, orgTeam return err } - audit.Record(audit.OrganizationTeamMemberAdd, audit.NewAuthenticationSourceUser(), org, team, "User %s was added to team %s/%s.", user.Name, org.Name, team.Name) + audit.Record(ctx, audit.OrganizationTeamMemberAdd, audit.NewAuthenticationSourceUser(), org, team, "User %s was added to team %s/%s.", user.Name, org.Name, team.Name) } else if action == syncRemove && isMember { if err := models.RemoveTeamMember(ctx, team, user.ID); err != nil { log.Error("group sync: Could not remove user from team: %v", err) return err } - audit.Record(audit.OrganizationTeamMemberRemove, audit.NewAuthenticationSourceUser(), org, team, "User %s was removed from team %s/%s.", user.Name, org.Name, team.Name) + audit.Record(ctx, audit.OrganizationTeamMemberRemove, audit.NewAuthenticationSourceUser(), org, team, "User %s was removed from team %s/%s.", user.Name, org.Name, team.Name) } } } diff --git a/services/auth/sspi.go b/services/auth/sspi.go index 44c86aefbcdc3..0a979db0383bf 100644 --- a/services/auth/sspi.go +++ b/services/auth/sspi.go @@ -184,7 +184,7 @@ func (s *SSPI) newUser(ctx context.Context, username string, cfg *sspi.Source) ( return nil, err } - audit.Record(audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + audit.Record(ctx, audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) return user, nil } diff --git a/services/externalaccount/user.go b/services/externalaccount/user.go index 2e6f39d2d4973..533a025fa8b00 100644 --- a/services/externalaccount/user.go +++ b/services/externalaccount/user.go @@ -54,7 +54,7 @@ func LinkAccountToUser(ctx context.Context, user *user_model.User, gothUser goth return err } - audit.Record(audit.UserExternalLoginAdd, user, user, externalLoginUser, "Added external login %s for user %s using provider %s.", externalLoginUser.ExternalID, user.Name, gothUser.Provider) + audit.Record(ctx, audit.UserExternalLoginAdd, user, user, externalLoginUser, "Added external login %s for user %s using provider %s.", externalLoginUser.ExternalID, user.Name, gothUser.Provider) externalID := externalLoginUser.ExternalID diff --git a/services/org/org.go b/services/org/org.go index aedeb179e5998..3a74821e336ed 100644 --- a/services/org/org.go +++ b/services/org/org.go @@ -57,7 +57,7 @@ func DeleteOrganization(ctx context.Context, doer *user_model.User, org *org_mod return err } - audit.Record(audit.OrganizationDelete, doer, org, org, "Organization %s was deleted.", org.Name) + audit.Record(ctx, audit.OrganizationDelete, doer, org, org, "Organization %s was deleted.", org.Name) // FIXME: system notice // Note: There are something just cannot be roll back, diff --git a/services/org/repo.go b/services/org/repo.go index a52be589a4ea9..0f651f440a96b 100644 --- a/services/org/repo.go +++ b/services/org/repo.go @@ -30,7 +30,7 @@ func TeamAddRepository(ctx context.Context, doer *user_model.User, t *organizati return err } - audit.Record(audit.RepositoryCollaboratorTeamAdd, doer, repo, t, "Added team %s as collaborator for %s.", t.Name, repo.FullName()) + audit.Record(ctx, audit.RepositoryCollaboratorTeamAdd, doer, repo, t, "Added team %s as collaborator for %s.", t.Name, repo.FullName()) return nil } diff --git a/services/repository/delete.go b/services/repository/delete.go index c80cc94cd0369..db5ece72a4f53 100644 --- a/services/repository/delete.go +++ b/services/repository/delete.go @@ -421,7 +421,7 @@ func RemoveRepositoryFromTeam(ctx context.Context, doer *user_model.User, t *org return err } - audit.Record(audit.RepositoryCollaboratorTeamRemove, doer, repo, t, "Removed team %s as collaborator from %s.", t.Name, repo.FullName()) + audit.Record(ctx, audit.RepositoryCollaboratorTeamRemove, doer, repo, t, "Removed team %s as collaborator from %s.", t.Name, repo.FullName()) return nil } diff --git a/services/repository/fork.go b/services/repository/fork.go index 553ef992f3531..cfe82d5346dbd 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -202,7 +202,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork notify_service.ForkRepository(ctx, doer, opts.BaseRepo, repo) - audit.Record(audit.RepositoryCreateFork, doer, repo, repo, "Created fork %s of repository %s.", repo.FullName(), opts.BaseRepo.FullName()) + audit.Record(ctx, audit.RepositoryCreateFork, doer, repo, repo, "Created fork %s of repository %s.", repo.FullName(), opts.BaseRepo.FullName()) return repo, nil } @@ -238,7 +238,7 @@ func ConvertForkToNormalRepository(ctx context.Context, doer *user_model.User, r return err } - audit.Record(audit.RepositoryConvertFork, doer, repo, repo, "Converted repository %s from fork to regular repository.", repo.FullName()) + audit.Record(ctx, audit.RepositoryConvertFork, doer, repo, repo, "Converted repository %s from fork to regular repository.", repo.FullName()) return nil } diff --git a/services/repository/repository.go b/services/repository/repository.go index 71dc9239a4b1e..de81640e5d2a6 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -49,7 +49,7 @@ func CreateRepository(ctx context.Context, doer, owner *user_model.User, opts Cr notify_service.CreateRepository(ctx, doer, owner, repo) - audit.Record(audit.RepositoryCreate, doer, repo, repo, "Created repository %s.", repo.FullName()) + audit.Record(ctx, audit.RepositoryCreate, doer, repo, repo, "Created repository %s.", repo.FullName()) return repo, nil } @@ -73,7 +73,7 @@ func DeleteRepository(ctx context.Context, doer *user_model.User, repo *repo_mod return err } - audit.Record(audit.RepositoryDelete, doer, repo, repo, "Deleted repository %s.", repo.FullName()) // repo.Owner load? + audit.Record(ctx, audit.RepositoryDelete, doer, repo, repo, "Deleted repository %s.", repo.FullName()) // repo.Owner load? return nil } diff --git a/services/repository/transfer.go b/services/repository/transfer.go index 1b6b61d997a8e..6a2681deca6e8 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -49,14 +49,14 @@ func TransferOwnership(ctx context.Context, doer, newOwner *user_model.User, rep return err } - audit.Record(audit.RepositoryTransferAccept, doer, newRepo, newRepo, "Accepted repository transfer from %s to %s.", oldOwner.Name, newRepo.OwnerName) + audit.Record(ctx, audit.RepositoryTransferAccept, doer, newRepo, newRepo, "Accepted repository transfer from %s to %s.", oldOwner.Name, newRepo.OwnerName) for _, team := range teams { if err := models.AddRepository(ctx, team, newRepo); err != nil { return err } - audit.Record(audit.RepositoryCollaboratorTeamAdd, doer, newRepo, team, "Added team %s as collaborator for %s.", team.Name, newRepo.FullName()) + audit.Record(ctx, audit.RepositoryCollaboratorTeamAdd, doer, newRepo, team, "Added team %s as collaborator for %s.", team.Name, newRepo.FullName()) } notify_service.TransferRepository(ctx, doer, repo, oldOwner.Name) @@ -83,7 +83,7 @@ func ChangeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo repo.Name = newRepoName - audit.Record(audit.RepositoryName, doer, repo, repo, "Repository name changed from %s to %s.", oldRepoName, newRepoName) + audit.Record(ctx, audit.RepositoryName, doer, repo, repo, "Repository name changed from %s to %s.", oldRepoName, newRepoName) notify_service.RenameRepository(ctx, doer, repo, oldRepoName) @@ -133,7 +133,7 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use return err } - audit.Record(audit.RepositoryTransferStart, doer, repo, repo, "Started repository transfer from %s to %s.", repo.OwnerName, newOwner.Name) + audit.Record(ctx, audit.RepositoryTransferStart, doer, repo, repo, "Started repository transfer from %s to %s.", repo.OwnerName, newOwner.Name) // notify users who are able to accept / reject transfer notify_service.RepoPendingTransfer(ctx, doer, newOwner, repo) diff --git a/services/secrets/secrets.go b/services/secrets/secrets.go index e15b044bcd281..c86749957abd5 100644 --- a/services/secrets/secrets.go +++ b/services/secrets/secrets.go @@ -33,7 +33,7 @@ func CreateOrUpdateSecret(ctx context.Context, doer, owner *user_model.User, rep return nil, false, err } - audit.Record( + audit.Record(ctx, auditActionSwitch(owner, repo, audit.UserSecretAdd, audit.OrganizationSecretAdd, audit.RepositorySecretAdd), doer, auditScopeSwitch(owner, repo), @@ -51,7 +51,7 @@ func CreateOrUpdateSecret(ctx context.Context, doer, owner *user_model.User, rep return nil, false, err } - audit.Record( + audit.Record(ctx, auditActionSwitch(owner, repo, audit.UserSecretUpdate, audit.OrganizationSecretUpdate, audit.RepositorySecretUpdate), doer, auditScopeSwitch(owner, repo), @@ -104,7 +104,7 @@ func deleteSecret(ctx context.Context, doer, owner *user_model.User, repo *repo_ return err } - audit.Record( + audit.Record(ctx, auditActionSwitch(owner, repo, audit.UserSecretRemove, audit.OrganizationSecretRemove, audit.RepositorySecretRemove), doer, auditScopeSwitch(owner, repo), diff --git a/services/user/user.go b/services/user/user.go index 730a60dbc198a..2cd2dd6481e67 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -120,7 +120,7 @@ func RenameUser(ctx context.Context, doer, u *user_model.User, newUserName strin return err } - audit.Record(audit.UserName, doer, u, u, "User %s changed name to %s.", oldUserName, newUserName) + audit.Record(ctx, audit.UserName, doer, u, u, "User %s changed name to %s.", oldUserName, newUserName) return nil } @@ -280,7 +280,7 @@ func DeleteUser(ctx context.Context, doer, u *user_model.User, purge bool) error } } - audit.Record(audit.UserDelete, doer, u, u, "User %s was deleted.", u.Name) + audit.Record(ctx, audit.UserDelete, doer, u, u, "User %s was deleted.", u.Name) return nil } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 76da739994abc..63baeb0e3f4e9 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -357,7 +357,7 @@ func DeleteWiki(ctx context.Context, doer *user_model.User, repo *repo_model.Rep system_model.RemoveAllWithNotice(ctx, "Delete repository wiki", repo.WikiPath()) - audit.Record(audit.RepositoryWikiDelete, doer, repo, repo, "Deleted wiki of repository %s.", repo.FullName()) + audit.Record(ctx, audit.RepositoryWikiDelete, doer, repo, repo, "Deleted wiki of repository %s.", repo.FullName()) return nil } From d56fae11e5fdfcd4062aa8173fab87b86201c222 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Thu, 16 Nov 2023 21:14:56 +0000 Subject: [PATCH 28/44] Simplify appender. --- custom/conf/app.example.ini | 14 +--- modules/setting/audit.go | 64 ++++++--------- services/audit/appender.go | 65 --------------- services/audit/audit.go | 80 ++----------------- services/audit/audit_test.go | 15 ++++ services/audit/file.go | 44 ++++++++++ .../audit/{appender_test.go => file_test.go} | 7 +- 7 files changed, 98 insertions(+), 191 deletions(-) delete mode 100644 services/audit/appender.go create mode 100644 services/audit/file.go rename services/audit/{appender_test.go => file_test.go} (82%) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 62f099f129c9a..07a7366acd723 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -655,26 +655,20 @@ LEVEL = Info ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Enable logging of audit events ;ENABLED = false -;; Comma-separated list of destinations to output to (possible values: log, file) -;APPENDER = ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Creating specific log configuration -;; -;; You can set specific configuration for individual appenders. Currently only "file" uses additional configuration. -;; ;[audit.file] ;; Set the file name for the logger. If this is a relative path this ;; will be relative to log.ROOT_PATH ;; Defaults to log.ROOT_PATH/audit.log -;FILENAME = +;FILE_NAME = ;; This enables automated audit log rotate, default is true -;ROTATE = true +;LOG_ROTATE = true ;; Maximum file size in bytes before rotating takes place (format `1000`, `1 MB`, `1 GiB`) ;MAXIMUM_SIZE = 256 MB ;; Rotate audit log daily, default is true -;ROTATE_DAILY = true +;DAILY_ROTATE = true ;; Delete the audit log file after n days, default is 7 -;KEEP_DAYS = 7 +;MAX_DAYS = 7 ;; Compress audit logs with gzip ;COMPRESS = true ;; Compression level see godoc for compress/gzip diff --git a/modules/setting/audit.go b/modules/setting/audit.go index 7f238f1c22026..33c261a278ec4 100644 --- a/modules/setting/audit.go +++ b/modules/setting/audit.go @@ -5,69 +5,53 @@ package setting import ( "compress/gzip" + "os" "path" "path/filepath" - "strings" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" ) var Audit = struct { - Enabled bool - Appender string - AppenderOptions map[string]*AppenderOptions + Enabled bool + FileOptions *log.WriterFileOption `ini:"-"` }{ - Enabled: false, - AppenderOptions: make(map[string]*AppenderOptions), -} - -type AppenderOptions struct { - Filename string - Rotate bool - MaximumSize int64 - RotateDaily bool - KeepDays int - Compress bool - CompressionLevel int + Enabled: false, } func loadAuditFrom(rootCfg ConfigProvider) { mustMapSetting(rootCfg, "audit", &Audit) - for _, name := range strings.Split(Audit.Appender, ",") { - name = strings.TrimSpace(name) - if name == "" { - continue - } - - sec, err := rootCfg.GetSection("audit." + name) - if err != nil { - sec, _ = rootCfg.NewSection("audit." + name) - } - - opts := &AppenderOptions{ - Filename: path.Join(Log.RootPath, "audit.log"), - Rotate: true, - RotateDaily: true, - KeepDays: 7, + sec, err := rootCfg.GetSection("audit.file") + if err == nil { + opts := &log.WriterFileOption{ + FileName: path.Join(Log.RootPath, "audit.log"), + LogRotate: true, + DailyRotate: true, + MaxDays: 7, + Compress: true, CompressionLevel: gzip.DefaultCompression, } if err := sec.MapTo(opts); err != nil { - log.Error("audit.%s: %v", name, err.Error()) + log.Fatal("Failed to map audit file settings: %v", err) + } + + opts.FileName = util.FilePathJoinAbs(opts.FileName) + if !filepath.IsAbs(opts.FileName) { + opts.FileName = path.Join(Log.RootPath, opts.FileName) } - opts.Filename = util.FilePathJoinAbs(opts.Filename) - if !filepath.IsAbs(opts.Filename) { - opts.Filename = path.Join(Log.RootPath, opts.Filename) + if err := os.MkdirAll(filepath.Dir(opts.FileName), os.ModePerm); err != nil { + log.Fatal("Unable to create directory for audit log %s: %v", opts.FileName, err) } - opts.MaximumSize = mustBytes(sec, "MAXIMUM_SIZE") - if opts.MaximumSize <= 0 { - opts.MaximumSize = 1 << 28 + opts.MaxSize = mustBytes(sec, "MAXIMUM_SIZE") + if opts.MaxSize <= 0 { + opts.MaxSize = 1 << 28 } - Audit.AppenderOptions[name] = opts + Audit.FileOptions = opts } } diff --git a/services/audit/appender.go b/services/audit/appender.go deleted file mode 100644 index bb9bc85108542..0000000000000 --- a/services/audit/appender.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package audit - -import ( - "context" - "fmt" - "io" - - "code.gitea.io/gitea/modules/json" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util/rotatingfilewriter" -) - -type Appender interface { - Record(context.Context, *Event) - Close() error -} - -// LogAppender writes an info log entry for every audit event -type LogAppender struct{} - -func (a *LogAppender) Record(ctx context.Context, e *Event) { - log.Info("Audit: %s (%s %s %s)", e.Message, formatDescriptor(e.Doer), formatDescriptor(e.Scope), formatDescriptor(e.Target)) -} - -func formatDescriptor(desc TypeDescriptor) string { - if desc.FriendlyName == "" { - return fmt.Sprintf("[%s: %v]", desc.Type, desc.PrimaryKey) - } - return fmt.Sprintf("[%s: %v, %s]", desc.Type, desc.PrimaryKey, desc.FriendlyName) -} - -func (a *LogAppender) Close() error { - return nil -} - -// File writes json object for every audit event -type FileAppender struct { - rfw *rotatingfilewriter.RotatingFileWriter -} - -func NewFileAppender(filename string, opts *rotatingfilewriter.Options) (*FileAppender, error) { - rfw, err := rotatingfilewriter.Open(filename, opts) - if err != nil { - return nil, err - } - - return &FileAppender{rfw}, nil -} - -func (a *FileAppender) Record(ctx context.Context, e *Event) { - if err := WriteEventAsJSON(a.rfw, e); err != nil { - log.Error("encoding event to file failed: %v", err) - } -} - -func (a *FileAppender) Close() error { - return a.rfw.Close() -} - -func WriteEventAsJSON(w io.Writer, e *Event) error { - return json.NewEncoder(w).Encode(e) -} diff --git a/services/audit/audit.go b/services/audit/audit.go index 4206de7ed5d69..bce864f896286 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -7,8 +7,6 @@ import ( "context" "fmt" "net" - "os" - "path/filepath" "time" "code.gitea.io/gitea/models" @@ -20,11 +18,8 @@ import ( secret_model "code.gitea.io/gitea/models/secret" user_model "code.gitea.io/gitea/models/user" webhook_model "code.gitea.io/gitea/models/webhook" - "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util/rotatingfilewriter" "code.gitea.io/gitea/modules/web/middleware" ) @@ -45,77 +40,12 @@ type Event struct { IPAddress string `json:"ip_address"` } -var ( - appenders = make([]Appender, 0, 5) - auditQueue *queue.WorkerPoolQueue[*Event] -) - -func TestingOnlyAddAppender(a Appender) { - appenders = append(appenders, a) -} - -func TestingOnlyRemoveAppender(a Appender) { - for i, app := range appenders { - if app == a { - last := len(appenders) - 1 - appenders[last], appenders[i] = nil, appenders[last] - appenders = appenders[:last] - return - } - } -} - -func Init() { +func Init() error { if !setting.Audit.Enabled { - return + return nil } - for name, opts := range setting.Audit.AppenderOptions { - var a Appender - switch name { - case "log": - a = &LogAppender{} - case "file": - if err := os.MkdirAll(filepath.Dir(opts.Filename), os.ModePerm); err != nil { - panic(err.Error()) - } - - fa, err := NewFileAppender(opts.Filename, &rotatingfilewriter.Options{ - Rotate: opts.Rotate, - MaximumSize: opts.MaximumSize, - RotateDaily: opts.RotateDaily, - KeepDays: opts.KeepDays, - Compress: opts.Compress, - CompressionLevel: opts.CompressionLevel, - }) - if err != nil { - log.Error("Failed to create file appender: %v", err) - continue - } - a = fa - } - - if a != nil { - appenders = append(appenders, a) - } - } - - auditQueue = queue.CreateSimpleQueue( - graceful.GetManager().ShutdownContext(), - "audit", - func(data ...*Event) []*Event { - ctx := graceful.GetManager().ShutdownContext() - - for _, e := range data { - for _, a := range appenders { - a.Record(ctx, e) - } - } - return nil - }, - ) - - go graceful.GetManager().RunWithCancel(auditQueue) + return initAuditFile() } func Record(ctx context.Context, action Action, doer *user_model.User, scope, target any, message string, v ...any) { @@ -125,8 +55,8 @@ func Record(ctx context.Context, action Action, doer *user_model.User, scope, ta e := BuildEvent(ctx, action, doer, scope, target, message, v...) - if err := auditQueue.Push(e); err != nil { - log.Error("Error pushing audit event to queue: %v", err) + if err := writeToFile(e); err != nil { + log.Error("Error writing audit event to file: %v", err) } } diff --git a/services/audit/audit_test.go b/services/audit/audit_test.go index c95be9d758adc..3b0c82ad80717 100644 --- a/services/audit/audit_test.go +++ b/services/audit/audit_test.go @@ -4,6 +4,8 @@ package audit import ( + "context" + "net/http" "testing" "time" @@ -16,6 +18,7 @@ import ( secret_model "code.gitea.io/gitea/models/secret" user_model "code.gitea.io/gitea/models/user" webhook_model "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/web/middleware" "github.com/stretchr/testify/assert" ) @@ -28,6 +31,8 @@ func TestBuildEvent(t *testing.T) { assert.Equal(t, expected, e) } + ctx := context.Background() + u := &user_model.User{ID: 1, Name: "TestUser"} r := &repository_model.Repository{ID: 3, Name: "TestRepo", OwnerName: "TestUser"} m := &repository_model.PushMirror{ID: 4} @@ -42,6 +47,7 @@ func TestBuildEvent(t *testing.T) { Message: "Updated settings of user TestUser.", }, BuildEvent( + ctx, UserUpdate, doer, u, @@ -59,6 +65,7 @@ func TestBuildEvent(t *testing.T) { Message: "Added push mirror for repository TestUser/TestRepo.", }, BuildEvent( + ctx, RepositoryMirrorPushAdd, doer, r, @@ -67,6 +74,14 @@ func TestBuildEvent(t *testing.T) { r.FullName(), ), ) + + e := BuildEvent(ctx, UserUpdate, doer, u, u, "") + assert.Empty(t, e.IPAddress) + + ctx = middleware.WithContextRequest(ctx, &http.Request{RemoteAddr: "127.0.0.1:1234"}) + + e = BuildEvent(ctx, UserUpdate, doer, u, u, "") + assert.Equal(t, "127.0.0.1", e.IPAddress) } func TestScopeToDescription(t *testing.T) { diff --git a/services/audit/file.go b/services/audit/file.go new file mode 100644 index 0000000000000..a2026276d948d --- /dev/null +++ b/services/audit/file.go @@ -0,0 +1,44 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package audit + +import ( + "io" + + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util/rotatingfilewriter" +) + +var rfw *rotatingfilewriter.RotatingFileWriter + +func initAuditFile() error { + if setting.Audit.FileOptions == nil { + return nil + } + + opts := setting.Audit.FileOptions + + var err error + rfw, err = rotatingfilewriter.Open(opts.FileName, &rotatingfilewriter.Options{ + Rotate: opts.LogRotate, + MaximumSize: opts.MaxSize, + RotateDaily: opts.DailyRotate, + KeepDays: opts.MaxDays, + Compress: opts.Compress, + CompressionLevel: opts.CompressionLevel, + }) + return err +} + +func writeToFile(e *Event) error { + if rfw == nil { + return nil + } + return WriteEventAsJSON(rfw, e) +} + +func WriteEventAsJSON(w io.Writer, e *Event) error { + return json.NewEncoder(w).Encode(e) +} diff --git a/services/audit/appender_test.go b/services/audit/file_test.go similarity index 82% rename from services/audit/appender_test.go rename to services/audit/file_test.go index db25d94176c27..061e9de4d0ea0 100644 --- a/services/audit/appender_test.go +++ b/services/audit/file_test.go @@ -4,12 +4,14 @@ package audit import ( + "context" "strings" "testing" "time" repository_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/web/middleware" "github.com/stretchr/testify/assert" ) @@ -19,7 +21,10 @@ func TestWriteEventAsJSON(t *testing.T) { m := &repository_model.PushMirror{ID: 4} doer := &user_model.User{ID: 2, Name: "Doer"} + ctx := middleware.WithContextRequest(context.Background(), &http.Request{RemoteAddr: "127.0.0.1:1234"}) + e := BuildEvent( + ctx, RepositoryMirrorPushAdd, doer, r, @@ -33,7 +38,7 @@ func TestWriteEventAsJSON(t *testing.T) { assert.NoError(t, WriteEventAsJSON(&sb, e)) assert.Equal( t, - `{"action":"repository:mirror:push:add","doer":{"type":"user","primary_key":2,"friendly_name":"Doer"},"scope":{"type":"repository","primary_key":3,"friendly_name":"TestUser/TestRepo"},"target":{"type":"push_mirror","primary_key":4,"friendly_name":""},"message":"Added push mirror for repository TestUser/TestRepo.","time":"0001-01-01T00:00:00Z"}`+"\n", + `{"action":"repository:mirror:push:add","doer":{"type":"user","primary_key":2,"friendly_name":"Doer"},"scope":{"type":"repository","primary_key":3,"friendly_name":"TestUser/TestRepo"},"target":{"type":"push_mirror","primary_key":4,"friendly_name":""},"message":"Added push mirror for repository TestUser/TestRepo.","time":"0001-01-01T00:00:00Z","ip_address":"127.0.0.1"}`+"\n", sb.String(), ) } From 20d6fdd9298cd671088af3927a557d10cb9c1bdf Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sun, 19 Nov 2023 18:19:58 +0000 Subject: [PATCH 29/44] Add database logging. --- cmd/admin_auth.go | 5 +- cmd/admin_auth_ldap.go | 5 +- cmd/admin_user_create.go | 5 +- cmd/admin_user_generate_access_token.go | 3 +- {services => models}/audit/action.go | 0 models/audit/audit_event.go | 103 ++++++++++++++++++ models/audit/types.go | 30 +++++ models/migrations/v1_22/v283.go | 27 +++++ routers/api/v1/admin/org.go | 3 +- routers/api/v1/admin/user.go | 15 +-- routers/api/v1/api.go | 3 +- routers/api/v1/org/org.go | 7 +- routers/api/v1/org/team.go | 13 ++- routers/api/v1/repo/collaborators.go | 7 +- routers/api/v1/repo/repo.go | 5 +- routers/api/v1/repo/transfer.go | 3 +- routers/api/v1/user/app.go | 11 +- routers/install/install.go | 3 +- routers/private/hook_post_receive.go | 3 +- routers/web/admin/auths.go | 5 +- routers/web/admin/emails.go | 3 +- routers/web/admin/hooks.go | 3 +- routers/web/admin/users.go | 15 +-- routers/web/auth/2fa.go | 3 +- routers/web/auth/auth.go | 7 +- routers/web/auth/oauth.go | 11 +- routers/web/auth/openid.go | 5 +- routers/web/auth/password.go | 9 +- routers/web/org/org.go | 3 +- routers/web/org/setting.go | 9 +- routers/web/org/teams.go | 23 ++-- routers/web/repo/repo.go | 3 +- routers/web/repo/setting/collaboration.go | 9 +- routers/web/repo/setting/default_branch.go | 3 +- routers/web/repo/setting/deploy_key.go | 3 +- routers/web/repo/setting/protected_branch.go | 7 +- routers/web/repo/setting/protected_tag.go | 7 +- routers/web/repo/setting/setting.go | 21 ++-- routers/web/repo/setting/webhook.go | 9 +- routers/web/user/setting/account.go | 7 +- routers/web/user/setting/applications.go | 5 +- routers/web/user/setting/keys.go | 9 +- routers/web/user/setting/oauth2_common.go | 13 ++- routers/web/user/setting/profile.go | 5 +- routers/web/user/setting/security/2fa.go | 7 +- routers/web/user/setting/security/openid.go | 5 +- routers/web/user/setting/security/security.go | 3 +- routers/web/user/setting/security/webauthn.go | 5 +- routers/web/user/setting/webhooks.go | 3 +- services/asymkey/deploy_key.go | 3 +- services/asymkey/ssh_key.go | 5 +- services/audit/audit.go | 80 +++++++------- services/audit/audit_test.go | 69 ++++++------ services/audit/database.go | 24 ++++ services/auth/basic.go | 3 +- services/auth/reverseproxy.go | 3 +- services/auth/source.go | 3 +- .../auth/source/ldap/source_authenticate.go | 13 ++- services/auth/source/ldap/source_sync.go | 17 +-- .../auth/source/pam/source_authenticate.go | 3 +- .../auth/source/smtp/source_authenticate.go | 3 +- services/auth/source/source_group_sync.go | 5 +- services/auth/sspi.go | 3 +- services/externalaccount/user.go | 3 +- services/org/org.go | 3 +- services/org/repo.go | 3 +- services/repository/delete.go | 3 +- services/repository/fork.go | 5 +- services/repository/repository.go | 5 +- services/repository/transfer.go | 9 +- services/secrets/secrets.go | 9 +- services/user/user.go | 5 +- services/wiki/wiki.go | 3 +- 73 files changed, 505 insertions(+), 248 deletions(-) rename {services => models}/audit/action.go (100%) create mode 100644 models/audit/audit_event.go create mode 100644 models/audit/types.go create mode 100644 models/migrations/v1_22/v283.go create mode 100644 services/audit/database.go diff --git a/cmd/admin_auth.go b/cmd/admin_auth.go index 6819db4d96f05..957dda4ad653c 100644 --- a/cmd/admin_auth.go +++ b/cmd/admin_auth.go @@ -9,6 +9,7 @@ import ( "os" "text/tabwriter" + audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/services/audit" auth_service "code.gitea.io/gitea/services/auth" @@ -95,7 +96,7 @@ func createSource(ctx context.Context, source *auth_model.Source) error { return err } - audit.Record(ctx, audit.SystemAuthenticationSourceAdd, audit.NewCLIUser(), nil, source, "Created authentication source %s [%s].", source.Name, source.Type.String()) + audit.Record(ctx, audit_model.SystemAuthenticationSourceAdd, audit.NewCLIUser(), nil, source, "Created authentication source %s [%s].", source.Name, source.Type.String()) return nil } @@ -105,7 +106,7 @@ func updateSource(ctx context.Context, source *auth_model.Source) error { return err } - audit.Record(ctx, audit.SystemAuthenticationSourceUpdate, audit.NewCLIUser(), nil, source, "Updated authentication source %s.", source.Name) + audit.Record(ctx, audit_model.SystemAuthenticationSourceUpdate, audit.NewCLIUser(), nil, source, "Updated authentication source %s.", source.Name) return nil } diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index c6c84bf472a4a..8722166ae03a9 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/auth/source/ldap" @@ -357,7 +358,7 @@ func (a *authService) addLdapSource(c *cli.Context, authType auth.Type, args ... return err } - audit.Record(ctx, audit.SystemAuthenticationSourceAdd, audit.NewCLIUser(), nil, authSource, "Created authentication source %s [%s].", authSource.Name, authSource.Type.String()) + audit.Record(ctx, audit_model.SystemAuthenticationSourceAdd, audit.NewCLIUser(), nil, authSource, "Created authentication source %s [%s].", authSource.Name, authSource.Type.String()) return nil } @@ -385,7 +386,7 @@ func (a *authService) updateLdapSource(c *cli.Context, authType auth.Type) error return err } - audit.Record(ctx, audit.SystemAuthenticationSourceUpdate, audit.NewCLIUser(), nil, authSource, "Updated authentication source %s.", authSource.Name) + audit.Record(ctx, audit_model.SystemAuthenticationSourceUpdate, audit.NewCLIUser(), nil, authSource, "Updated authentication source %s.", authSource.Name) return nil } diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index ca57879ec4d44..202af7d484549 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" + audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" pwd "code.gitea.io/gitea/modules/auth/password" @@ -152,7 +153,7 @@ func runCreateUser(c *cli.Context) error { return fmt.Errorf("CreateUser: %w", err) } - audit.Record(ctx, audit.UserCreate, audit.NewCLIUser(), u, u, "Created user %s.", u.Name) + audit.Record(ctx, audit_model.UserCreate, audit.NewCLIUser(), u, u, "Created user %s.", u.Name) if c.Bool("access-token") { t := &auth_model.AccessToken{ @@ -164,7 +165,7 @@ func runCreateUser(c *cli.Context) error { return err } - audit.Record(ctx, audit.UserAccessTokenAdd, audit.NewCLIUser(), u, t, "Added access token %s for user %s with scope %s.", t.Name, u.Name, t.Scope) + audit.Record(ctx, audit_model.UserAccessTokenAdd, audit.NewCLIUser(), u, t, "Added access token %s for user %s with scope %s.", t.Name, u.Name, t.Scope) fmt.Printf("Access token was successfully created... %s\n", t.Token) } diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go index ca1de2bc6d4c1..e7451ff0415a4 100644 --- a/cmd/admin_user_generate_access_token.go +++ b/cmd/admin_user_generate_access_token.go @@ -6,6 +6,7 @@ package cmd import ( "fmt" + audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/services/audit" @@ -85,7 +86,7 @@ func runGenerateAccessToken(c *cli.Context) error { return err } - audit.Record(ctx, audit.UserAccessTokenAdd, audit.NewCLIUser(), user, t, "Added access token %s for user %s with scope %s.", t.Name, user.Name, t.Scope) + audit.Record(ctx, audit_model.UserAccessTokenAdd, audit.NewCLIUser(), user, t, "Added access token %s for user %s with scope %s.", t.Name, user.Name, t.Scope) if c.Bool("raw") { fmt.Printf("%s\n", t.Token) diff --git a/services/audit/action.go b/models/audit/action.go similarity index 100% rename from services/audit/action.go rename to models/audit/action.go diff --git a/models/audit/audit_event.go b/models/audit/audit_event.go new file mode 100644 index 0000000000000..80042c446518e --- /dev/null +++ b/models/audit/audit_event.go @@ -0,0 +1,103 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package audit + +import ( + "context" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/builder" +) + +func init() { + db.RegisterModel(new(Event)) +} + +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 + CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"` +} + +func (_ *Event) TableName() string { + return "audit_event" +} + +func InsertEvent(ctx context.Context, e *Event) (*Event, error) { + return e, db.Insert(ctx, e) +} + +type EventSort = string + +const ( + SortCreatedAsc EventSort = "created_asc" + SortCreatedDesc EventSort = "created_desc" +) + +// PackageSearchOptions are options for SearchXXX methods +// All fields optional and are not used if they have their default value (nil, "", 0) +type EventSearchOptions struct { + Action Action + ActorID int64 + ScopeType ObjectType + ScopeID int64 + Sort EventSort + db.Paginator +} + +func (opts *EventSearchOptions) ToConds() builder.Cond { + cond := builder.NewCond() + + if opts.Action != "" { + cond = cond.And(builder.Eq{"action": opts.Action}) + } + if opts.ActorID != 0 { + cond = cond.And(builder.Eq{"actor_id": opts.ActorID}) + } + if opts.ScopeID != 0 && opts.ScopeType != "" { + cond = cond.And(builder.Eq{ + "audit_event.scope_type": opts.ScopeType, + "audit_event.scope_id": opts.ScopeID, + }) + } + + return cond +} + +func (opts *EventSearchOptions) configureOrderBy(e db.Engine) { + switch opts.Sort { + case SortCreatedAsc: + e.Asc("created_unix") + default: + e.Desc("created_unix") + } + + // Sort by id for stable order with duplicates in the other field + e.Asc("id") +} + +func FindEvents(ctx context.Context, opts *EventSearchOptions) ([]*Event, int64, error) { + sess := db.GetEngine(ctx). + Where(opts.ToConds()). + Table("audit_event") + + opts.configureOrderBy(sess) + + if opts.Paginator != nil { + sess = db.SetSessionPagination(sess, opts) + } + + evs := make([]*Event, 0, 10) + count, err := sess.FindAndCount(&evs) + return evs, count, err +} diff --git a/models/audit/types.go b/models/audit/types.go new file mode 100644 index 0000000000000..2a0d165c08858 --- /dev/null +++ b/models/audit/types.go @@ -0,0 +1,30 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package audit + +type ObjectType string + +const ( + TypeSystem ObjectType = "system" + TypeRepository ObjectType = "repository" + TypeUser ObjectType = "user" + TypeOrganization ObjectType = "organization" + TypeEmailAddress ObjectType = "email_address" + TypeTeam ObjectType = "team" + TypeTwoFactor ObjectType = "twofactor" + TypeWebAuthnCredential ObjectType = "webauthn" + TypeOpenID ObjectType = "openid" + TypeAccessToken ObjectType = "access_token" + TypeOAuth2Application ObjectType = "oauth2_application" + TypeOAuth2Grant ObjectType = "oauth2_grant" + TypeAuthenticationSource ObjectType = "authentication_source" + TypePublicKey ObjectType = "public_key" + TypeGPGKey ObjectType = "gpg_key" + TypeSecret ObjectType = "secret" + TypeWebhook ObjectType = "webhook" + TypeProtectedTag ObjectType = "protected_tag" + TypeProtectedBranch ObjectType = "protected_branch" + TypePushMirror ObjectType = "push_mirror" + TypeRepoTransfer ObjectType = "repo_transfer" +) diff --git a/models/migrations/v1_22/v283.go b/models/migrations/v1_22/v283.go new file mode 100644 index 0000000000000..34a2df100b444 --- /dev/null +++ b/models/migrations/v1_22/v283.go @@ -0,0 +1,27 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import ( + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +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 + CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"` + } + + return x.Sync(&AuditEvent{}) +} diff --git a/routers/api/v1/admin/org.go b/routers/api/v1/admin/org.go index 4dd3979a4ff80..cd30a2e4c8054 100644 --- a/routers/api/v1/admin/org.go +++ b/routers/api/v1/admin/org.go @@ -7,6 +7,7 @@ package admin import ( "net/http" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" @@ -75,7 +76,7 @@ func CreateOrg(ctx *context.APIContext) { return } - audit.Record(ctx, audit.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) + audit.Record(ctx, audit_model.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) ctx.JSON(http.StatusCreated, convert.ToOrganization(ctx, org)) } diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 1205c253e0796..28cb2ea3302f9 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" @@ -144,7 +145,7 @@ func CreateUser(ctx *context.APIContext) { return } - audit.Record(ctx, audit.UserCreate, ctx.Doer, u, u, "Created user %s.", u.Name) + audit.Record(ctx, audit_model.UserCreate, ctx.Doer, u, u, "Created user %s.", u.Name) log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name) @@ -309,22 +310,22 @@ func EditUser(ctx *context.APIContext) { } if passwordChanged { - audit.Record(ctx, audit.UserPassword, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Password of user %s changed.", ctx.ContextUser.Name) + audit.Record(ctx, audit_model.UserPassword, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Password of user %s changed.", ctx.ContextUser.Name) } if auditFields.LoginSource != ctx.ContextUser.LoginSource { - audit.Record(ctx, audit.UserAuthenticationSource, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Authentication source of user %s changed.", ctx.ContextUser.Name) + audit.Record(ctx, audit_model.UserAuthenticationSource, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Authentication source of user %s changed.", ctx.ContextUser.Name) } if auditFields.Visibility != ctx.ContextUser.Visibility { - audit.Record(ctx, audit.UserVisibility, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Visibility of user %s changed from %s to %s.", ctx.ContextUser.Name, auditFields.Visibility.String(), ctx.ContextUser.Visibility.String()) + audit.Record(ctx, audit_model.UserVisibility, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Visibility of user %s changed from %s to %s.", ctx.ContextUser.Name, auditFields.Visibility.String(), ctx.ContextUser.Visibility.String()) } if auditFields.IsActive != ctx.ContextUser.IsActive { - audit.Record(ctx, audit.UserActive, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Activation status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserActiveString(ctx.ContextUser.IsActive)) + audit.Record(ctx, audit_model.UserActive, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Activation status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserActiveString(ctx.ContextUser.IsActive)) } if auditFields.IsAdmin != ctx.ContextUser.IsAdmin { - audit.Record(ctx, audit.UserAdmin, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Admin status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserAdminString(ctx.ContextUser.IsAdmin)) + audit.Record(ctx, audit_model.UserAdmin, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Admin status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserAdminString(ctx.ContextUser.IsAdmin)) } if auditFields.IsRestricted != ctx.ContextUser.IsRestricted { - audit.Record(ctx, audit.UserRestricted, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Restricted status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserRestrictedString(ctx.ContextUser.IsRestricted)) + audit.Record(ctx, audit_model.UserRestricted, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Restricted status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserRestrictedString(ctx.ContextUser.IsRestricted)) } log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, ctx.ContextUser.Name) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 6039f2f662db6..0ae3c5a49bb35 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -69,6 +69,7 @@ import ( "strings" actions_model "code.gitea.io/gitea/models/actions" + audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" @@ -122,7 +123,7 @@ func sudo() func(ctx *context.APIContext) { return } - audit.Record(ctx, audit.UserImpersonation, ctx.Doer, ctx.Doer, user, "User %s impersonating user %s.", ctx.Doer.Name, user.Name) + audit.Record(ctx, audit_model.UserImpersonation, ctx.Doer, ctx.Doer, user, "User %s impersonating user %s.", ctx.Doer.Name, user.Name) log.Trace("Sudo from (%s) to: %s", ctx.Doer.Name, user.Name) ctx.Doer = user diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go index aafbf702c7951..52bd574486bfd 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -8,6 +8,7 @@ import ( "net/http" activities_model "code.gitea.io/gitea/models/activities" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" @@ -281,7 +282,7 @@ func Create(ctx *context.APIContext) { return } - audit.Record(ctx, audit.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) + audit.Record(ctx, audit_model.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) ctx.JSON(http.StatusCreated, convert.ToOrganization(ctx, org)) } @@ -368,9 +369,9 @@ func Edit(ctx *context.APIContext) { return } - audit.Record(ctx, audit.OrganizationUpdate, ctx.Doer, org, org, "Updated settings of organization %s.", org.Name) + audit.Record(ctx, audit_model.OrganizationUpdate, ctx.Doer, org, org, "Updated settings of organization %s.", org.Name) if org.Visibility != oldVisibility { - audit.Record(ctx, audit.OrganizationVisibility, ctx.Doer, org, org, "Visibility of organization %s changed from %s to %s.", org.Name, oldVisibility.String(), org.Visibility.String()) + audit.Record(ctx, audit_model.OrganizationVisibility, ctx.Doer, org, org, "Visibility of organization %s changed from %s to %s.", org.Name, oldVisibility.String(), org.Visibility.String()) } ctx.JSON(http.StatusOK, convert.ToOrganization(ctx, org)) diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index 62abb0abed5b4..522854fbc74d0 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models" activities_model "code.gitea.io/gitea/models/activities" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" @@ -249,7 +250,7 @@ func CreateTeam(ctx *context.APIContext) { return } - audit.Record(ctx, audit.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, team, "Team %s was added to organization %s.", team.Name, ctx.Org.Organization.Name) + audit.Record(ctx, audit_model.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, team, "Team %s was added to organization %s.", team.Name, ctx.Org.Organization.Name) apiTeam, err := convert.ToTeam(ctx, team, true) if err != nil { @@ -346,9 +347,9 @@ func EditTeam(ctx *context.APIContext) { return } - audit.Record(ctx, audit.OrganizationTeamUpdate, ctx.Doer, org, team, "Updated settings of team %s/%s.", org.Name, team.Name) + audit.Record(ctx, audit_model.OrganizationTeamUpdate, ctx.Doer, org, team, "Updated settings of team %s/%s.", org.Name, team.Name) if isAuthChanged { - audit.Record(ctx, audit.OrganizationTeamPermission, ctx.Doer, org, team, "Permission of team %s/%s changed from %s to %s.", org.Name, team.Name, oldAccessMode.String(), team.AccessMode.String()) + audit.Record(ctx, audit_model.OrganizationTeamPermission, ctx.Doer, org, team, "Permission of team %s/%s changed from %s to %s.", org.Name, team.Name, oldAccessMode.String(), team.AccessMode.String()) } apiTeam, err := convert.ToTeam(ctx, team) @@ -388,7 +389,7 @@ func DeleteTeam(ctx *context.APIContext) { return } - audit.Record(ctx, audit.OrganizationTeamRemove, ctx.Doer, org, ctx.Org.Team, "Team %s was removed from organization %s.", ctx.Org.Team.Name, org.Name) + audit.Record(ctx, audit_model.OrganizationTeamRemove, ctx.Doer, org, ctx.Org.Team, "Team %s was removed from organization %s.", ctx.Org.Team.Name, org.Name) ctx.Status(http.StatusNoContent) } @@ -529,7 +530,7 @@ func AddTeamMember(ctx *context.APIContext) { return } - audit.Record(ctx, audit.OrganizationTeamMemberAdd, ctx.Doer, org, ctx.Org.Team, "User %s was added to team %s/%s.", u.Name, org.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, ctx.Doer, org, ctx.Org.Team, "User %s was added to team %s/%s.", u.Name, org.Name, ctx.Org.Team.Name) ctx.Status(http.StatusNoContent) } @@ -575,7 +576,7 @@ func RemoveTeamMember(ctx *context.APIContext) { return } - audit.Record(ctx, audit.OrganizationTeamMemberRemove, ctx.Doer, org, ctx.Org.Team, "User %s was removed from team %s/%s.", u.Name, org.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberRemove, ctx.Doer, org, ctx.Org.Team, "User %s was removed from team %s/%s.", u.Name, org.Name, ctx.Org.Team.Name) ctx.Status(http.StatusNoContent) } diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go index e167cf18a585b..d5078d9d369e0 100644 --- a/routers/api/v1/repo/collaborators.go +++ b/routers/api/v1/repo/collaborators.go @@ -8,6 +8,7 @@ import ( "errors" "net/http" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" @@ -184,7 +185,7 @@ func AddCollaborator(ctx *context.APIContext) { return } - audit.Record(ctx, audit.RepositoryCollaboratorAdd, ctx.Doer, ctx.Repo.Repository, collaborator, "Added user %s as collaborator for repository %s.", collaborator.Name, ctx.Repo.Repository.FullName()) + audit.Record(ctx, audit_model.RepositoryCollaboratorAdd, ctx.Doer, ctx.Repo.Repository, collaborator, "Added user %s as collaborator for repository %s.", collaborator.Name, ctx.Repo.Repository.FullName()) if form.Permission != nil { accessMode := perm.ParseAccessMode(*form.Permission) @@ -193,7 +194,7 @@ func AddCollaborator(ctx *context.APIContext) { return } - audit.Record(ctx, audit.RepositoryCollaboratorAccess, ctx.Doer, ctx.Repo.Repository, collaborator, "Changed access mode of collaborator %s to %s.", collaborator.Name, accessMode.String()) + audit.Record(ctx, audit_model.RepositoryCollaboratorAccess, ctx.Doer, ctx.Repo.Repository, collaborator, "Changed access mode of collaborator %s to %s.", collaborator.Name, accessMode.String()) } ctx.Status(http.StatusNoContent) @@ -245,7 +246,7 @@ func DeleteCollaborator(ctx *context.APIContext) { return } - audit.Record(ctx, audit.RepositoryCollaboratorRemove, ctx.Doer, ctx.Repo.Repository, collaborator, "Removed collaborator %s.", collaborator.Name) + audit.Record(ctx, audit_model.RepositoryCollaboratorRemove, ctx.Doer, ctx.Repo.Repository, collaborator, "Removed collaborator %s.", collaborator.Name) ctx.Status(http.StatusNoContent) } diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index bf09a7d1357b3..bd6a1e391613b 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -12,6 +12,7 @@ import ( "time" activities_model "code.gitea.io/gitea/models/activities" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" @@ -744,9 +745,9 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err return err } - audit.Record(ctx, audit.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) + audit.Record(ctx, audit_model.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) if visibilityChanged { - audit.Record(ctx, audit.RepositoryVisibility, ctx.Doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) + audit.Record(ctx, audit_model.RepositoryVisibility, ctx.Doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) } log.Trace("Repository basic settings updated: %s/%s", owner.Name, repo.Name) diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go index 8df49aa08ac3f..88e41756f4810 100644 --- a/routers/api/v1/repo/transfer.go +++ b/routers/api/v1/repo/transfer.go @@ -8,6 +8,7 @@ import ( "net/http" "code.gitea.io/gitea/models" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" @@ -235,7 +236,7 @@ func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error { return err } - audit.Record(ctx, audit.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected repository transfer.") + audit.Record(ctx, audit_model.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected repository transfer.") return nil } diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index 1a3290ed12100..26d278c942c33 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -11,6 +11,7 @@ import ( "strconv" "strings" + audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" @@ -132,7 +133,7 @@ func CreateAccessToken(ctx *context.APIContext) { return } - audit.Record(ctx, audit.UserAccessTokenAdd, ctx.Doer, ctx.Doer, t, "Added access token %s for user %s with scope %s.", t.Name, ctx.Doer.Name, t.Scope) + audit.Record(ctx, audit_model.UserAccessTokenAdd, ctx.Doer, ctx.Doer, t, "Added access token %s for user %s with scope %s.", t.Name, ctx.Doer.Name, t.Scope) ctx.JSON(http.StatusCreated, &api.AccessToken{ Name: t.Name, @@ -216,7 +217,7 @@ func DeleteAccessToken(ctx *context.APIContext) { return } - audit.Record(ctx, audit.UserAccessTokenRemove, ctx.Doer, ctx.Doer, t, "Removed access token %s from user %s.", t.Name, ctx.Doer.Name) + audit.Record(ctx, audit_model.UserAccessTokenRemove, ctx.Doer, ctx.Doer, t, "Removed access token %s from user %s.", t.Name, ctx.Doer.Name) ctx.Status(http.StatusNoContent) } @@ -259,7 +260,7 @@ func CreateOauth2Application(ctx *context.APIContext) { } app.ClientSecret = secret - audit.Record(ctx, audit.UserOAuth2ApplicationAdd, ctx.Doer, ctx.Doer, app, "Created OAuth2 application %s.", app.Name) + audit.Record(ctx, audit_model.UserOAuth2ApplicationAdd, ctx.Doer, ctx.Doer, app, "Created OAuth2 application %s.", app.Name) ctx.JSON(http.StatusCreated, convert.ToOAuth2Application(app)) } @@ -339,7 +340,7 @@ func DeleteOauth2Application(ctx *context.APIContext) { return } - audit.Record(ctx, audit.UserOAuth2ApplicationRemove, ctx.Doer, ctx.Doer, app, "Removed OAuth2 application %s.", app.Name) + audit.Record(ctx, audit_model.UserOAuth2ApplicationRemove, ctx.Doer, ctx.Doer, app, "Removed OAuth2 application %s.", app.Name) ctx.Status(http.StatusNoContent) } @@ -428,7 +429,7 @@ func UpdateOauth2Application(ctx *context.APIContext) { return } - audit.Record(ctx, audit.UserOAuth2ApplicationUpdate, ctx.Doer, ctx.Doer, app, "Updated OAuth2 application %s.", app.Name) + audit.Record(ctx, audit_model.UserOAuth2ApplicationUpdate, ctx.Doer, ctx.Doer, app, "Updated OAuth2 application %s.", app.Name) ctx.JSON(http.StatusOK, convert.ToOAuth2Application(app)) } diff --git a/routers/install/install.go b/routers/install/install.go index 1a88d7927d1a1..e268d1a8a73aa 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -14,6 +14,7 @@ import ( "strings" "time" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" db_install "code.gitea.io/gitea/models/db/install" "code.gitea.io/gitea/models/migrations" @@ -550,7 +551,7 @@ func SubmitInstall(ctx *context.Context) { u, _ = user_model.GetUserByName(ctx, u.Name) } - audit.Record(ctx, audit.UserCreate, u, u, u, "Created user %s.", u.Name) + audit.Record(ctx, audit_model.UserCreate, u, u, u, "Created user %s.", u.Name) nt, token, err := auth_service.CreateAuthTokenForUserID(ctx, u.ID) if err != nil { diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index bf75169211a80..7bcd2cb56bb80 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -8,6 +8,7 @@ import ( "net/http" "strconv" + audit_model "code.gitea.io/gitea/models/audit" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -123,7 +124,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { return } - audit.Record(ctx, audit.RepositoryVisibility, doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) + audit.Record(ctx, audit_model.RepositoryVisibility, doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) } } diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index c7d883b053429..a999fb0a39c4a 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/modules/auth/pam" "code.gitea.io/gitea/modules/base" @@ -324,7 +325,7 @@ func NewAuthSourcePost(ctx *context.Context) { return } - audit.Record(ctx, audit.SystemAuthenticationSourceAdd, ctx.Doer, nil, source, "Created authentication source %s [%s].", source.Name, source.Type.String()) + audit.Record(ctx, audit_model.SystemAuthenticationSourceAdd, ctx.Doer, nil, source, "Created authentication source %s [%s].", source.Name, source.Type.String()) log.Trace("Authentication created by admin(%s): %s", ctx.Doer.Name, form.Name) @@ -440,7 +441,7 @@ func EditAuthSourcePost(ctx *context.Context) { return } - audit.Record(ctx, audit.SystemAuthenticationSourceUpdate, ctx.Doer, nil, source, "Updated authentication source %s.", source.Name) + audit.Record(ctx, audit_model.SystemAuthenticationSourceUpdate, ctx.Doer, nil, source, "Updated authentication source %s.", source.Name) log.Trace("Authentication changed by admin(%s): %d", ctx.Doer.Name, source.ID) diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go index ebd5e38c1fa24..300a27970fefb 100644 --- a/routers/web/admin/emails.go +++ b/routers/web/admin/emails.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" @@ -134,7 +135,7 @@ func ActivateEmail(ctx *context.Context) { ctx.Flash.Error(ctx.Tr("admin.emails.not_updated", err)) } } else { - audit.Record(ctx, audit.UserEmailActivate, ctx.Doer, u, u, "Email %s of user %s activated.", email, u.Name) + audit.Record(ctx, audit_model.UserEmailActivate, ctx.Doer, u, u, "Email %s of user %s activated.", email, u.Name) log.Info("Activation for User ID: %d, email: %s, primary: %v changed to %v", uid, email, primary, activate) ctx.Flash.Info(ctx.Tr("admin.emails.updated")) diff --git a/routers/web/admin/hooks.go b/routers/web/admin/hooks.go index 977df3fc536c6..a7b76e0abb60a 100644 --- a/routers/web/admin/hooks.go +++ b/routers/web/admin/hooks.go @@ -6,6 +6,7 @@ package admin import ( "net/http" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -71,7 +72,7 @@ func DeleteDefaultOrSystemWebhook(ctx *context.Context) { if err := webhook.DeleteDefaultSystemWebhook(ctx, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteDefaultWebhook: " + err.Error()) } else { - audit.Record(ctx, audit.SystemWebhookRemove, ctx.Doer, nil, hook, "Removed webhook %s.", hook.URL) + audit.Record(ctx, audit_model.SystemWebhookRemove, ctx.Doer, nil, hook, "Removed webhook %s.", hook.URL) ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 2cc8f1f45f95f..4e7468f4aaa97 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -11,6 +11,7 @@ import ( "strings" "code.gitea.io/gitea/models" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" org_model "code.gitea.io/gitea/models/organization" @@ -207,7 +208,7 @@ func NewUserPost(ctx *context.Context) { return } - audit.Record(ctx, audit.UserCreate, ctx.Doer, u, u, "Created user %s.", u.Name) + audit.Record(ctx, audit_model.UserCreate, ctx.Doer, u, u, "Created user %s.", u.Name) log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name) @@ -499,22 +500,22 @@ func EditUserPost(ctx *context.Context) { } if passwordChanged { - audit.Record(ctx, audit.UserPassword, ctx.Doer, u, u, "Password of user %s changed.", u.Name) + audit.Record(ctx, audit_model.UserPassword, ctx.Doer, u, u, "Password of user %s changed.", u.Name) } if auditFields.LoginSource != u.LoginSource { - audit.Record(ctx, audit.UserAuthenticationSource, ctx.Doer, u, u, "Authentication source of user %s changed.", u.Name) + audit.Record(ctx, audit_model.UserAuthenticationSource, ctx.Doer, u, u, "Authentication source of user %s changed.", u.Name) } if auditFields.Visibility != u.Visibility { - audit.Record(ctx, audit.UserVisibility, ctx.Doer, u, u, "Visibility of user %s changed from %s to %s.", u.Name, auditFields.Visibility.String(), u.Visibility.String()) + audit.Record(ctx, audit_model.UserVisibility, ctx.Doer, u, u, "Visibility of user %s changed from %s to %s.", u.Name, auditFields.Visibility.String(), u.Visibility.String()) } if auditFields.IsActive != u.IsActive { - audit.Record(ctx, audit.UserActive, ctx.Doer, u, u, "Activation status of user %s changed to %s.", u.Name, audit.UserActiveString(u.IsActive)) + audit.Record(ctx, audit_model.UserActive, ctx.Doer, u, u, "Activation status of user %s changed to %s.", u.Name, audit.UserActiveString(u.IsActive)) } if auditFields.IsAdmin != u.IsAdmin { - audit.Record(ctx, audit.UserAdmin, ctx.Doer, u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) + audit.Record(ctx, audit_model.UserAdmin, ctx.Doer, u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) } if auditFields.IsRestricted != u.IsRestricted { - audit.Record(ctx, audit.UserRestricted, ctx.Doer, u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) + audit.Record(ctx, audit_model.UserRestricted, ctx.Doer, u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) } log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, u.Name) diff --git a/routers/web/auth/2fa.go b/routers/web/auth/2fa.go index 77327561b10ad..7268edbdb7ae0 100644 --- a/routers/web/auth/2fa.go +++ b/routers/web/auth/2fa.go @@ -7,6 +7,7 @@ import ( "errors" "net/http" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" @@ -94,7 +95,7 @@ func TwoFactorPost(ctx *context.Context) { return } - audit.Record(ctx, audit.UserAuthenticationFailTwoFactor, user_model.NewGhostUser(), u, twofa, "Failed two-factor authentication for user %s.", u.Name) + audit.Record(ctx, audit_model.UserAuthenticationFailTwoFactor, user_model.NewGhostUser(), u, twofa, "Failed two-factor authentication for user %s.", u.Name) ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplTwofa, forms.TwoFactorAuthForm{}) } diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 4915510174ddc..9c503658eee13 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -10,6 +10,7 @@ import ( "net/http" "strings" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" @@ -581,7 +582,7 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *us return false } - audit.Record(ctx, audit.UserCreate, audit.NewAuthenticationSourceUser(), u, u, "Created user %s.", u.Name) + audit.Record(ctx, audit_model.UserCreate, audit.NewAuthenticationSourceUser(), u, u, "Created user %s.", u.Name) log.Trace("Account created: %s", u.Name) return true @@ -744,7 +745,7 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) { return } - audit.Record(ctx, audit.UserActive, user, user, user, "Activation status of user %s changed to %s.", user.Name, audit.UserActiveString(user.IsActive)) + audit.Record(ctx, audit_model.UserActive, user, user, user, "Activation status of user %s changed to %s.", user.Name, audit.UserActiveString(user.IsActive)) log.Trace("User activated: %s", user.Name) @@ -798,7 +799,7 @@ func ActivateEmail(ctx *context.Context) { _ = ctx.Cache.Delete("MailResendLimit_" + user.LowerName) } - audit.Record(ctx, audit.UserEmailActivate, user, user, email, "Email %s of user %s activated.", email.Email, user.Name) + audit.Record(ctx, audit_model.UserEmailActivate, user, user, email, "Email %s of user %s activated.", email.Email, user.Name) } // FIXME: e-mail verification does not require the user to be logged in, diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 72ad79ad22807..c0f8ca3a4259a 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -15,6 +15,7 @@ import ( "sort" "strings" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" org_model "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" @@ -565,7 +566,7 @@ func GrantApplicationOAuth(ctx *context.Context) { return } - audit.Record(ctx, audit.UserOAuth2ApplicationGrant, ctx.Doer, owner, grant, "Granted OAuth2 access to application %s.", app.Name) + audit.Record(ctx, audit_model.UserOAuth2ApplicationGrant, ctx.Doer, owner, grant, "Granted OAuth2 access to application %s.", app.Name) if len(form.Nonce) > 0 { err := grant.SetNonce(ctx, form.Nonce) @@ -1159,10 +1160,10 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model } if changedIsAdmin { - audit.Record(ctx, audit.UserAdmin, audit.NewAuthenticationSourceUser(), u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) + audit.Record(ctx, audit_model.UserAdmin, audit.NewAuthenticationSourceUser(), u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) } if changedIsRestricted { - audit.Record(ctx, audit.UserRestricted, audit.NewAuthenticationSourceUser(), u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) + audit.Record(ctx, audit_model.UserRestricted, audit.NewAuthenticationSourceUser(), u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) } if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval { @@ -1203,10 +1204,10 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model } if changedIsAdmin { - audit.Record(ctx, audit.UserAdmin, audit.NewAuthenticationSourceUser(), u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) + audit.Record(ctx, audit_model.UserAdmin, audit.NewAuthenticationSourceUser(), u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) } if changedIsRestricted { - audit.Record(ctx, audit.UserRestricted, audit.NewAuthenticationSourceUser(), u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) + audit.Record(ctx, audit_model.UserRestricted, audit.NewAuthenticationSourceUser(), u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) } if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval { diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go index cbfce76195025..513f7f66bc4b6 100644 --- a/routers/web/auth/openid.go +++ b/routers/web/auth/openid.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" + audit_model "code.gitea.io/gitea/models/audit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/openid" "code.gitea.io/gitea/modules/base" @@ -281,7 +282,7 @@ func ConnectOpenIDPost(ctx *context.Context) { return } - audit.Record(ctx, audit.UserOpenIDAdd, u, u, userOID, "Associated OpenID %s to user %s.", userOID.URI, u.Name) + audit.Record(ctx, audit_model.UserOpenIDAdd, u, u, userOID, "Associated OpenID %s to user %s.", userOID.URI, u.Name) ctx.Flash.Success(ctx.Tr("settings.add_openid_success")) @@ -387,7 +388,7 @@ func RegisterOpenIDPost(ctx *context.Context) { return } - audit.Record(ctx, audit.UserOpenIDAdd, u, u, userOID, "Associated OpenID %s to user %s.", userOID.URI, u.Name) + audit.Record(ctx, audit_model.UserOpenIDAdd, u, u, userOID, "Associated OpenID %s to user %s.", userOID.URI, u.Name) remember, _ := ctx.Session.Get("openid_signin_remember").(bool) log.Trace("Session stored openid-remember: %t", remember) diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go index 11688c03b10a6..7468cf83afce4 100644 --- a/routers/web/auth/password.go +++ b/routers/web/auth/password.go @@ -8,6 +8,7 @@ import ( "fmt" "net/http" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/password" @@ -88,7 +89,7 @@ func ForgotPasswdPost(ctx *context.Context) { mailer.SendResetPasswordMail(u) - audit.Record(ctx, audit.UserPasswordReset, u, u, u, "User %s requested a password reset.", u.Name) + audit.Record(ctx, audit_model.UserPasswordReset, u, u, u, "User %s requested a password reset.", u.Name) if setting.CacheService.Enabled { if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { @@ -213,7 +214,7 @@ func ResetPasswdPost(ctx *context.Context) { return } if !ok || twofa.LastUsedPasscode == passcode { - audit.Record(ctx, audit.UserAuthenticationFailTwoFactor, u, u, twofa, "Failed two-factor authentication for user %s.", u.Name) + audit.Record(ctx, audit_model.UserAuthenticationFailTwoFactor, u, u, twofa, "Failed two-factor authentication for user %s.", u.Name) ctx.Data["IsResetForm"] = true ctx.Data["Err_Passcode"] = true @@ -243,7 +244,7 @@ func ResetPasswdPost(ctx *context.Context) { return } - audit.Record(ctx, audit.UserPassword, u, u, u, "Password of user %s changed.", u.Name) + audit.Record(ctx, audit_model.UserPassword, u, u, u, "Password of user %s changed.", u.Name) log.Trace("User password reset: %s", u.Name) ctx.Data["IsResetFailed"] = true @@ -344,7 +345,7 @@ func MustChangePasswordPost(ctx *context.Context) { log.Trace("User updated password: %s", u.Name) - audit.Record(ctx, audit.UserPassword, u, u, u, "Password of user %s changed.", u.Name) + audit.Record(ctx, audit_model.UserPassword, u, u, u, "Password of user %s changed.", u.Name) if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) { middleware.DeleteRedirectToCookie(ctx.Resp) diff --git a/routers/web/org/org.go b/routers/web/org/org.go index e04c582558a0f..c65e354aea51d 100644 --- a/routers/web/org/org.go +++ b/routers/web/org/org.go @@ -8,6 +8,7 @@ import ( "errors" "net/http" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" @@ -76,7 +77,7 @@ func CreatePost(ctx *context.Context) { return } - audit.Record(ctx, audit.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) + audit.Record(ctx, audit_model.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) log.Trace("Organization created: %s", org.Name) diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 71cebb7875e2b..9d5bbdd893a6c 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -10,6 +10,7 @@ import ( "strings" "code.gitea.io/gitea/models" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -143,12 +144,12 @@ func SettingsPost(ctx *context.Context) { } } - audit.Record(ctx, audit.OrganizationUpdate, ctx.Doer, org, org, "Updated settings of organization %s.", org.Name) + audit.Record(ctx, audit_model.OrganizationUpdate, ctx.Doer, org, org, "Updated settings of organization %s.", org.Name) if nameChanged { - audit.Record(ctx, audit.OrganizationName, ctx.Doer, org, org, "Organization name changed from %s to %s.", oldName, org.Name) + audit.Record(ctx, audit_model.OrganizationName, ctx.Doer, org, org, "Organization name changed from %s to %s.", oldName, org.Name) } if org.Visibility != oldVisibility { - audit.Record(ctx, audit.OrganizationVisibility, ctx.Doer, org, org, "Visibility of organization %s changed from %s to %s.", org.Name, oldVisibility.String(), org.Visibility.String()) + audit.Record(ctx, audit_model.OrganizationVisibility, ctx.Doer, org, org, "Visibility of organization %s changed from %s to %s.", org.Name, oldVisibility.String(), org.Visibility.String()) } log.Trace("Organization setting updated: %s", org.Name) @@ -253,7 +254,7 @@ func DeleteWebhook(ctx *context.Context) { if err := webhook.DeleteWebhookByOwnerID(ctx, ctx.Org.Organization.ID, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error()) } else { - audit.Record(ctx, audit.OrganizationWebhookRemove, ctx.Doer, ctx.Org.Organization, hook, "Removed webhook %s.", hook.URL) + audit.Record(ctx, audit_model.OrganizationWebhookRemove, ctx.Doer, ctx.Org.Organization, hook, "Removed webhook %s.", hook.URL) ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index f69871c23a8d5..ee1f4af89f0b1 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -13,6 +13,7 @@ import ( "strings" "code.gitea.io/gitea/models" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" org_model "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" @@ -81,7 +82,7 @@ func TeamsAction(ctx *context.Context) { } err = models.AddTeamMember(ctx, ctx.Org.Team, ctx.Doer.ID) if err == nil { - audit.Record(ctx, audit.OrganizationTeamMemberAdd, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was added to team %s/%s.", ctx.Doer.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was added to team %s/%s.", ctx.Doer.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) } case "leave": err = models.RemoveTeamMember(ctx, ctx.Org.Team, ctx.Doer.ID) @@ -97,7 +98,7 @@ func TeamsAction(ctx *context.Context) { return } } else { - audit.Record(ctx, audit.OrganizationTeamMemberRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was removed from team %s/%s.", ctx.Doer.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was removed from team %s/%s.", ctx.Doer.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) } checkIsOrgMemberAndRedirect(ctx, ctx.Org.OrgLink+"/teams/") return @@ -126,7 +127,7 @@ func TeamsAction(ctx *context.Context) { return } } else { - audit.Record(ctx, audit.OrganizationTeamMemberRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was removed from team %s/%s.", u.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was removed from team %s/%s.", u.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) } checkIsOrgMemberAndRedirect(ctx, ctx.Org.OrgLink+"/teams/"+url.PathEscape(ctx.Org.Team.LowerName)) return @@ -172,7 +173,7 @@ func TeamsAction(ctx *context.Context) { } else { err = models.AddTeamMember(ctx, ctx.Org.Team, u.ID) if err == nil { - audit.Record(ctx, audit.OrganizationTeamMemberAdd, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was added to team %s/%s.", u.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was added to team %s/%s.", u.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) } } @@ -277,7 +278,7 @@ func TeamsRepoAction(ctx *context.Context) { } for _, repo := range added { - audit.Record(ctx, audit.RepositoryCollaboratorTeamAdd, ctx.Doer, repo, ctx.Org.Team, "Added team %s as collaborator for %s.", ctx.Org.Team.Name, repo.FullName()) + audit.Record(ctx, audit_model.RepositoryCollaboratorTeamAdd, ctx.Doer, repo, ctx.Org.Team, "Added team %s as collaborator for %s.", ctx.Org.Team.Name, repo.FullName()) } case "removeall": if err := ctx.Org.Team.LoadRepositories(ctx); err != nil { @@ -291,7 +292,7 @@ func TeamsRepoAction(ctx *context.Context) { } for _, repo := range ctx.Org.Team.Repos { - audit.Record(ctx, audit.RepositoryCollaboratorTeamRemove, ctx.Doer, repo, ctx.Org.Team, "Removed team %s as collaborator from %s.", ctx.Org.Team.Name, repo.FullName()) + audit.Record(ctx, audit_model.RepositoryCollaboratorTeamRemove, ctx.Doer, repo, ctx.Org.Team, "Removed team %s as collaborator from %s.", ctx.Org.Team.Name, repo.FullName()) } } @@ -400,7 +401,7 @@ func NewTeamPost(ctx *context.Context) { return } - audit.Record(ctx, audit.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, t, "Team %s was added to organization %s.", t.Name, ctx.Org.Organization.Name) + audit.Record(ctx, audit_model.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, t, "Team %s was added to organization %s.", t.Name, ctx.Org.Organization.Name) log.Trace("Team created: %s/%s", ctx.Org.Organization.Name, t.Name) ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(t.LowerName)) @@ -582,9 +583,9 @@ func EditTeamPost(ctx *context.Context) { return } - audit.Record(ctx, audit.OrganizationTeamUpdate, ctx.Doer, ctx.Org.Organization, t, "Updated settings of team %s/%s.", ctx.Org.Organization.Name, t.Name) + audit.Record(ctx, audit_model.OrganizationTeamUpdate, ctx.Doer, ctx.Org.Organization, t, "Updated settings of team %s/%s.", ctx.Org.Organization.Name, t.Name) if isAuthChanged { - audit.Record(ctx, audit.OrganizationTeamPermission, ctx.Doer, ctx.Org.Organization, t, "Permission of team %s/%s changed from %s to %s.", ctx.Org.Organization.Name, t.Name, oldAccessMode.String(), t.AccessMode.String()) + audit.Record(ctx, audit_model.OrganizationTeamPermission, ctx.Doer, ctx.Org.Organization, t, "Permission of team %s/%s changed from %s to %s.", ctx.Org.Organization.Name, t.Name, oldAccessMode.String(), t.AccessMode.String()) } ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(t.LowerName)) @@ -595,7 +596,7 @@ func DeleteTeam(ctx *context.Context) { if err := models.DeleteTeam(ctx, ctx.Org.Team); err != nil { ctx.Flash.Error("DeleteTeam: " + err.Error()) } else { - audit.Record(ctx, audit.OrganizationTeamRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Team %s was removed from organization %s.", ctx.Org.Team.Name, ctx.Org.Organization.Name) + audit.Record(ctx, audit_model.OrganizationTeamRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Team %s was removed from organization %s.", ctx.Org.Team.Name, ctx.Org.Organization.Name) ctx.Flash.Success(ctx.Tr("org.teams.delete_team_success")) } @@ -641,7 +642,7 @@ func TeamInvitePost(ctx *context.Context) { return } - audit.Record(ctx, audit.OrganizationTeamMemberAdd, ctx.Doer, org, team, "User %s was added to team %s/%s.", ctx.Doer.Name, org.Name, team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, ctx.Doer, org, team, "User %s was added to team %s/%s.", ctx.Doer.Name, org.Name, team.Name) if err := org_model.RemoveInviteByID(ctx, invite.ID, team.ID); err != nil { log.Error("RemoveInviteByID: %v", err) diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 5dddc89c3c38e..21adab5b042ec 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -12,6 +12,7 @@ import ( "strings" "code.gitea.io/gitea/models" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/organization" @@ -364,7 +365,7 @@ func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) error { return err } - audit.Record(ctx, audit.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected repository transfer.") + audit.Record(ctx, audit_model.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected repository transfer.") ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected")) } diff --git a/routers/web/repo/setting/collaboration.go b/routers/web/repo/setting/collaboration.go index 2abd8d3608e16..86474c14931eb 100644 --- a/routers/web/repo/setting/collaboration.go +++ b/routers/web/repo/setting/collaboration.go @@ -7,6 +7,7 @@ import ( "net/http" "strings" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" @@ -111,7 +112,7 @@ func CollaborationPost(ctx *context.Context) { mailer.SendCollaboratorMail(u, ctx.Doer, ctx.Repo.Repository) } - audit.Record(ctx, audit.RepositoryCollaboratorAdd, ctx.Doer, ctx.Repo.Repository, u, "Added user %s as collaborator for repository %s.", u.Name, ctx.Repo.Repository.FullName()) + audit.Record(ctx, audit_model.RepositoryCollaboratorAdd, ctx.Doer, ctx.Repo.Repository, u, "Added user %s as collaborator for repository %s.", u.Name, ctx.Repo.Repository.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.add_collaborator_success")) ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) @@ -134,7 +135,7 @@ func ChangeCollaborationAccessMode(ctx *context.Context) { return } - audit.Record(ctx, audit.RepositoryCollaboratorAccess, ctx.Doer, ctx.Repo.Repository, u, "Changed access mode of collaborator %s to %s.", u.Name, perm.AccessMode(ctx.FormInt("mode")).String()) + audit.Record(ctx, audit_model.RepositoryCollaboratorAccess, ctx.Doer, ctx.Repo.Repository, u, "Changed access mode of collaborator %s to %s.", u.Name, perm.AccessMode(ctx.FormInt("mode")).String()) } // DeleteCollaboration delete a collaboration for a repository @@ -148,7 +149,7 @@ func DeleteCollaboration(ctx *context.Context) { if err := repo_service.DeleteCollaboration(ctx, ctx.Repo.Repository, u.ID); err != nil { ctx.Flash.Error("DeleteCollaboration: " + err.Error()) } else { - audit.Record(ctx, audit.RepositoryCollaboratorRemove, ctx.Doer, ctx.Repo.Repository, u, "Removed user %s as collaborator.", u.Name) + audit.Record(ctx, audit_model.RepositoryCollaboratorRemove, ctx.Doer, ctx.Repo.Repository, u, "Removed user %s as collaborator.", u.Name) ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success")) } @@ -221,7 +222,7 @@ func DeleteTeam(ctx *context.Context) { return } - audit.Record(ctx, audit.RepositoryCollaboratorTeamRemove, ctx.Doer, ctx.Repo.Repository, team, "Removed team %s as collaborator from %s.", team.Name, ctx.Repo.Repository.FullName()) + audit.Record(ctx, audit_model.RepositoryCollaboratorTeamRemove, ctx.Doer, ctx.Repo.Repository, team, "Removed team %s as collaborator from %s.", team.Name, ctx.Repo.Repository.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.remove_team_success")) ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/collaboration") diff --git a/routers/web/repo/setting/default_branch.go b/routers/web/repo/setting/default_branch.go index 0292d92f0aca8..e318e65ffdfcf 100644 --- a/routers/web/repo/setting/default_branch.go +++ b/routers/web/repo/setting/default_branch.go @@ -6,6 +6,7 @@ package setting import ( "net/http" + audit_model "code.gitea.io/gitea/models/audit" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" @@ -55,7 +56,7 @@ func SetDefaultBranchPost(ctx *context.Context) { notify_service.ChangeDefaultBranch(ctx, repo) - audit.Record(ctx, audit.RepositoryBranchDefault, ctx.Doer, repo, repo, "Changed default branch from %s to %s.", oldBranch, branch) + audit.Record(ctx, audit_model.RepositoryBranchDefault, ctx.Doer, repo, repo, "Changed default branch from %s to %s.", oldBranch, branch) } log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) diff --git a/routers/web/repo/setting/deploy_key.go b/routers/web/repo/setting/deploy_key.go index 26e58addfe517..80dacae9b31cb 100644 --- a/routers/web/repo/setting/deploy_key.go +++ b/routers/web/repo/setting/deploy_key.go @@ -7,6 +7,7 @@ import ( "net/http" asymkey_model "code.gitea.io/gitea/models/asymkey" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" @@ -93,7 +94,7 @@ func DeployKeysPost(ctx *context.Context) { return } - audit.Record(ctx, audit.RepositoryDeployKeyAdd, ctx.Doer, ctx.Repo.Repository, key, "Added deploy key %s.", key.Name) + audit.Record(ctx, audit_model.RepositoryDeployKeyAdd, ctx.Doer, ctx.Repo.Repository, key, "Added deploy key %s.", key.Name) log.Trace("Deploy key added: %d", ctx.Repo.Repository.ID) ctx.Flash.Success(ctx.Tr("repo.settings.add_key_success", key.Name)) diff --git a/routers/web/repo/setting/protected_branch.go b/routers/web/repo/setting/protected_branch.go index 9b6f8a7f21b78..14df0d3f4459b 100644 --- a/routers/web/repo/setting/protected_branch.go +++ b/routers/web/repo/setting/protected_branch.go @@ -10,6 +10,7 @@ import ( "strings" "time" + audit_model "code.gitea.io/gitea/models/audit" git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" @@ -250,9 +251,9 @@ func SettingsProtectedBranchPost(ctx *context.Context) { } if isNewProtectedBranch { - audit.Record(ctx, audit.RepositoryBranchProtectionAdd, ctx.Doer, ctx.Repo.Repository, protectBranch, "Added branch protection %s.", protectBranch.RuleName) + audit.Record(ctx, audit_model.RepositoryBranchProtectionAdd, ctx.Doer, ctx.Repo.Repository, protectBranch, "Added branch protection %s.", protectBranch.RuleName) } else { - audit.Record(ctx, audit.RepositoryBranchProtectionUpdate, ctx.Doer, ctx.Repo.Repository, protectBranch, "Updated branch protection %s.", protectBranch.RuleName) + audit.Record(ctx, audit_model.RepositoryBranchProtectionUpdate, ctx.Doer, ctx.Repo.Repository, protectBranch, "Updated branch protection %s.", protectBranch.RuleName) } // FIXME: since we only need to recheck files protected rules, we could improve this @@ -300,7 +301,7 @@ func DeleteProtectedBranchRulePost(ctx *context.Context) { return } - audit.Record(ctx, audit.RepositoryBranchProtectionRemove, ctx.Doer, ctx.Repo.Repository, rule, "Removed branch protection %s.", rule.RuleName) + audit.Record(ctx, audit_model.RepositoryBranchProtectionRemove, ctx.Doer, ctx.Repo.Repository, rule, "Removed branch protection %s.", rule.RuleName) ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", rule.RuleName)) ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) diff --git a/routers/web/repo/setting/protected_tag.go b/routers/web/repo/setting/protected_tag.go index 318428a93f331..01c758d406bc9 100644 --- a/routers/web/repo/setting/protected_tag.go +++ b/routers/web/repo/setting/protected_tag.go @@ -8,6 +8,7 @@ import ( "net/http" "strings" + audit_model "code.gitea.io/gitea/models/audit" git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" @@ -64,7 +65,7 @@ func NewProtectedTagPost(ctx *context.Context) { return } - audit.Record(ctx, audit.RepositoryTagProtectionAdd, ctx.Doer, repo, pt, "Added tag protection for %s.", pt.NamePattern) + audit.Record(ctx, audit_model.RepositoryTagProtectionAdd, ctx.Doer, repo, pt, "Added tag protection for %s.", pt.NamePattern) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) @@ -119,7 +120,7 @@ func EditProtectedTagPost(ctx *context.Context) { return } - audit.Record(ctx, audit.RepositoryTagProtectionUpdate, ctx.Doer, ctx.Repo.Repository, pt, "Updated tag protection for %s.", pt.NamePattern) + audit.Record(ctx, audit_model.RepositoryTagProtectionUpdate, ctx.Doer, ctx.Repo.Repository, pt, "Updated tag protection for %s.", pt.NamePattern) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/tags") @@ -137,7 +138,7 @@ func DeleteProtectedTagPost(ctx *context.Context) { return } - audit.Record(ctx, audit.RepositoryTagProtectionRemove, ctx.Doer, ctx.Repo.Repository, pt, "Removed tag protection for %s.", pt.NamePattern) + audit.Record(ctx, audit_model.RepositoryTagProtectionRemove, ctx.Doer, ctx.Repo.Repository, pt, "Removed tag protection for %s.", pt.NamePattern) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/tags") diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index bea4d427f8126..fd46852906992 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -12,6 +12,7 @@ import ( "time" "code.gitea.io/gitea/models" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" @@ -181,9 +182,9 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(ctx, audit.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) + audit.Record(ctx, audit_model.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) if visibilityChanged { - audit.Record(ctx, audit.RepositoryVisibility, ctx.Doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) + audit.Record(ctx, audit_model.RepositoryVisibility, ctx.Doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) } log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) @@ -375,7 +376,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(ctx, audit.RepositoryMirrorPushRemove, ctx.Doer, repo, m, "Removed push mirror for repository %s.", repo.FullName()) + audit.Record(ctx, audit_model.RepositoryMirrorPushRemove, ctx.Doer, repo, m, "Removed push mirror for repository %s.", repo.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(repo.Link() + "/settings") @@ -440,7 +441,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(ctx, audit.RepositoryMirrorPushAdd, ctx.Doer, repo, m, "Added push mirror for repository %s.", repo.FullName()) + audit.Record(ctx, audit_model.RepositoryMirrorPushAdd, ctx.Doer, repo, m, "Added push mirror for repository %s.", repo.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(repo.Link() + "/settings") @@ -616,7 +617,7 @@ func SettingsPost(ctx *context.Context) { } } - audit.Record(ctx, audit.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) + audit.Record(ctx, audit_model.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) @@ -637,7 +638,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(ctx, audit.RepositorySigningVerification, ctx.Doer, repo, repo, "Changed signing verification of repository %s to %s.", repo.FullName(), repo.TrustModel.String()) + audit.Record(ctx, audit_model.RepositorySigningVerification, ctx.Doer, repo, repo, "Changed signing verification of repository %s to %s.", repo.FullName(), repo.TrustModel.String()) } log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) @@ -716,7 +717,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(ctx, audit.RepositoryConvertMirror, ctx.Doer, repo, repo, "Converted repository %s from mirror to regular repository.", repo.FullName()) + audit.Record(ctx, audit_model.RepositoryConvertMirror, ctx.Doer, repo, repo, "Converted repository %s from mirror to regular repository.", repo.FullName()) log.Trace("Repository converted from mirror to regular: %s", repo.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.convert_succeed")) @@ -837,7 +838,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(ctx, audit.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected transfer of repository %s.", ctx.Repo.Repository.FullName()) + audit.Record(ctx, audit_model.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected transfer of repository %s.", ctx.Repo.Repository.FullName()) log.Trace("Repository transfer process was cancelled: %s/%s ", ctx.Repo.Owner.Name, repo.Name) ctx.Flash.Success(ctx.Tr("repo.settings.transfer_abort_success", repoTransfer.Recipient.Name)) @@ -906,7 +907,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(ctx, audit.RepositoryArchive, ctx.Doer, repo, repo, "Archived repository %s.", repo.FullName()) + audit.Record(ctx, audit_model.RepositoryArchive, ctx.Doer, repo, repo, "Archived repository %s.", repo.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.archive.success")) @@ -926,7 +927,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(ctx, audit.RepositoryUnarchive, ctx.Doer, repo, repo, "Unarchived repository %s.", repo.FullName()) + audit.Record(ctx, audit_model.RepositoryUnarchive, ctx.Doer, repo, repo, "Unarchived repository %s.", repo.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success")) diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go index d2b472cfc8ecb..bb8ffae993340 100644 --- a/routers/web/repo/setting/webhook.go +++ b/routers/web/repo/setting/webhook.go @@ -12,6 +12,7 @@ import ( "path" "strings" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" @@ -68,7 +69,7 @@ type ownerRepoCtx struct { NewTemplate base.TplName } -func (ctx *ownerRepoCtx) auditActionSwitch(user, org, repo, system audit.Action) audit.Action { +func (ctx *ownerRepoCtx) auditActionSwitch(user, org, repo, system audit_model.Action) audit_model.Action { if ctx.IsAdmin { return system } @@ -287,7 +288,7 @@ func createWebhook(ctx *context.Context, params webhookParams) { return } - audit.Record(ctx, orCtx.auditActionSwitch(audit.UserWebhookAdd, audit.OrganizationWebhookAdd, audit.RepositoryWebhookAdd, audit.SystemWebhookAdd), ctx.Doer, orCtx.auditScopeSwitch(), w, "Added webhook %s.", w.URL) + audit.Record(ctx, orCtx.auditActionSwitch(audit_model.UserWebhookAdd, audit_model.OrganizationWebhookAdd, audit_model.RepositoryWebhookAdd, audit_model.SystemWebhookAdd), ctx.Doer, orCtx.auditScopeSwitch(), w, "Added webhook %s.", w.URL) ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) ctx.Redirect(orCtx.Link) @@ -341,7 +342,7 @@ func editWebhook(ctx *context.Context, params webhookParams) { return } - audit.Record(ctx, orCtx.auditActionSwitch(audit.UserWebhookUpdate, audit.OrganizationWebhookUpdate, audit.RepositoryWebhookUpdate, audit.SystemWebhookUpdate), ctx.Doer, orCtx.auditScopeSwitch(), w, "Updated webhook %s.", w.URL) + audit.Record(ctx, orCtx.auditActionSwitch(audit_model.UserWebhookUpdate, audit_model.OrganizationWebhookUpdate, audit_model.RepositoryWebhookUpdate, audit_model.SystemWebhookUpdate), ctx.Doer, orCtx.auditScopeSwitch(), w, "Updated webhook %s.", w.URL) ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) @@ -771,7 +772,7 @@ func DeleteWebhook(ctx *context.Context) { if err := webhook.DeleteWebhookByRepoID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteWebhookByRepoID: " + err.Error()) } else { - audit.Record(ctx, audit.RepositoryWebhookRemove, ctx.Doer, ctx.Repo.Repository, hook, "Removed webhook %s.", hook.URL) + audit.Record(ctx, audit_model.RepositoryWebhookRemove, ctx.Doer, ctx.Repo.Repository, hook, "Removed webhook %s.", hook.URL) ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index 96ce98650431d..99823dfff5ed5 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -10,6 +10,7 @@ import ( "time" "code.gitea.io/gitea/models" + audit_model "code.gitea.io/gitea/models/audit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/password" "code.gitea.io/gitea/modules/base" @@ -80,7 +81,7 @@ func AccountPost(ctx *context.Context) { return } - audit.Record(ctx, audit.UserPassword, ctx.Doer, ctx.Doer, ctx.Doer, "Password of user %s changed.", ctx.Doer.Name) + audit.Record(ctx, audit_model.UserPassword, ctx.Doer, ctx.Doer, ctx.Doer, "Password of user %s changed.", ctx.Doer.Name) log.Trace("User password updated: %s", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.change_password_success")) @@ -218,7 +219,7 @@ func EmailPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.add_email_success")) } - audit.Record(ctx, audit.UserEmailAdd, ctx.Doer, ctx.Doer, email, "Email %s added to user %s.", email.Email, ctx.Doer.Name) + audit.Record(ctx, audit_model.UserEmailAdd, ctx.Doer, ctx.Doer, email, "Email %s added to user %s.", email.Email, ctx.Doer.Name) log.Trace("Email address added: %s", email.Email) ctx.Redirect(setting.AppSubURL + "/user/settings/account") @@ -237,7 +238,7 @@ func DeleteEmail(ctx *context.Context) { return } - audit.Record(ctx, audit.UserEmailRemove, ctx.Doer, ctx.Doer, email, "Email %s removed from user %s.", email.Email, ctx.Doer.Name) + audit.Record(ctx, audit_model.UserEmailRemove, ctx.Doer, ctx.Doer, email, "Email %s removed from user %s.", email.Email, ctx.Doer.Name) log.Trace("Email address deleted: %s", ctx.Doer.Name) diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index 5872ea3e742c2..987610ededf2d 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -7,6 +7,7 @@ package setting import ( "net/http" + audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -70,7 +71,7 @@ func ApplicationsPost(ctx *context.Context) { return } - audit.Record(ctx, audit.UserAccessTokenAdd, ctx.Doer, ctx.Doer, t, "Added access token %s for user %s with scope %s.", t.Name, ctx.Doer.Name, t.Scope) + audit.Record(ctx, audit_model.UserAccessTokenAdd, ctx.Doer, ctx.Doer, t, "Added access token %s for user %s with scope %s.", t.Name, ctx.Doer.Name, t.Scope) ctx.Flash.Success(ctx.Tr("settings.generate_token_success")) ctx.Flash.Info(t.Token) @@ -89,7 +90,7 @@ func DeleteApplication(ctx *context.Context) { if err := auth_model.DeleteAccessTokenByID(ctx, t.ID, ctx.Doer.ID); err != nil { ctx.Flash.Error("DeleteAccessTokenByID: " + err.Error()) } else { - audit.Record(ctx, audit.UserAccessTokenRemove, ctx.Doer, ctx.Doer, t, "Removed access token %s from user %s.", t.Name, ctx.Doer.Name) + audit.Record(ctx, audit_model.UserAccessTokenRemove, ctx.Doer, ctx.Doer, t, "Removed access token %s from user %s.", t.Name, ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.delete_token_success")) } diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index ed0ba1394034e..3538e7e4f171a 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -8,6 +8,7 @@ import ( "net/http" asymkey_model "code.gitea.io/gitea/models/asymkey" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -77,7 +78,7 @@ func KeysPost(ctx *context.Context) { return } - audit.Record(ctx, audit.UserKeyPrincipalAdd, ctx.Doer, ctx.Doer, key, "Added principal key %s.", key.Name) + audit.Record(ctx, audit_model.UserKeyPrincipalAdd, ctx.Doer, ctx.Doer, key, "Added principal key %s.", key.Name) ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") @@ -124,7 +125,7 @@ func KeysPost(ctx *context.Context) { } for _, key := range keys { - audit.Record(ctx, audit.UserKeyGPGAdd, ctx.Doer, ctx.Doer, key, "Added GPG key %s.", key.KeyID) + audit.Record(ctx, audit_model.UserKeyGPGAdd, ctx.Doer, ctx.Doer, key, "Added GPG key %s.", key.KeyID) } keyIDs := "" @@ -201,7 +202,7 @@ func KeysPost(ctx *context.Context) { return } - audit.Record(ctx, audit.UserKeySSHAdd, ctx.Doer, ctx.Doer, key, "Added SSH key %s.", key.Fingerprint) + audit.Record(ctx, audit_model.UserKeySSHAdd, ctx.Doer, ctx.Doer, key, "Added SSH key %s.", key.Fingerprint) ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") @@ -245,7 +246,7 @@ func DeleteKey(ctx *context.Context) { if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, key.ID); err != nil { ctx.Flash.Error("DeleteGPGKey: " + err.Error()) } else { - audit.Record(ctx, audit.UserKeyGPGRemove, ctx.Doer, ctx.Doer, key, "Removed GPG key %s.", key.KeyID) + audit.Record(ctx, audit_model.UserKeyGPGRemove, ctx.Doer, ctx.Doer, key, "Removed GPG key %s.", key.KeyID) ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success")) } diff --git a/routers/web/user/setting/oauth2_common.go b/routers/web/user/setting/oauth2_common.go index 3fb396f4cffb0..9fca776146d70 100644 --- a/routers/web/user/setting/oauth2_common.go +++ b/routers/web/user/setting/oauth2_common.go @@ -8,6 +8,7 @@ import ( "fmt" "net/http" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" @@ -34,7 +35,7 @@ func (oa *OAuth2CommonHandlers) ownerID() int64 { return 0 } -func (oa *OAuth2CommonHandlers) auditActionSwitch(user, org, system audit.Action) audit.Action { +func (oa *OAuth2CommonHandlers) auditActionSwitch(user, org, system audit_model.Action) audit_model.Action { if oa.Owner == nil { return system } @@ -80,7 +81,7 @@ func (oa *OAuth2CommonHandlers) AddApp(ctx *context.Context) { return } - audit.Record(ctx, oa.auditActionSwitch(audit.UserOAuth2ApplicationAdd, audit.OrganizationOAuth2ApplicationAdd, audit.SystemOAuth2ApplicationAdd), oa.Doer, oa.Owner, app, "Created OAuth2 application %s.", app.Name) + audit.Record(ctx, oa.auditActionSwitch(audit_model.UserOAuth2ApplicationAdd, audit_model.OrganizationOAuth2ApplicationAdd, audit_model.SystemOAuth2ApplicationAdd), oa.Doer, oa.Owner, app, "Created OAuth2 application %s.", app.Name) // render the edit page with secret ctx.Flash.Success(ctx.Tr("settings.create_oauth2_application_success"), true) @@ -137,7 +138,7 @@ func (oa *OAuth2CommonHandlers) EditSave(ctx *context.Context) { ctx.Data["App"] = app - audit.Record(ctx, oa.auditActionSwitch(audit.UserOAuth2ApplicationUpdate, audit.OrganizationOAuth2ApplicationUpdate, audit.SystemOAuth2ApplicationUpdate), oa.Doer, oa.Owner, app, "Updated OAuth2 application %s.", app.Name) + audit.Record(ctx, oa.auditActionSwitch(audit_model.UserOAuth2ApplicationUpdate, audit_model.OrganizationOAuth2ApplicationUpdate, audit_model.SystemOAuth2ApplicationUpdate), oa.Doer, oa.Owner, app, "Updated OAuth2 application %s.", app.Name) ctx.Flash.Success(ctx.Tr("settings.update_oauth2_application_success")) ctx.Redirect(oa.BasePathList) @@ -165,7 +166,7 @@ func (oa *OAuth2CommonHandlers) RegenerateSecret(ctx *context.Context) { return } - audit.Record(ctx, oa.auditActionSwitch(audit.UserOAuth2ApplicationSecret, audit.OrganizationOAuth2ApplicationSecret, audit.SystemOAuth2ApplicationSecret), oa.Doer, oa.Owner, app, "Regenerated secret for OAuth2 application %s.", app.Name) + audit.Record(ctx, oa.auditActionSwitch(audit_model.UserOAuth2ApplicationSecret, audit_model.OrganizationOAuth2ApplicationSecret, audit_model.SystemOAuth2ApplicationSecret), oa.Doer, oa.Owner, app, "Regenerated secret for OAuth2 application %s.", app.Name) ctx.Flash.Success(ctx.Tr("settings.update_oauth2_application_success"), true) oa.renderEditPage(ctx) @@ -188,7 +189,7 @@ func (oa *OAuth2CommonHandlers) DeleteApp(ctx *context.Context) { return } - audit.Record(ctx, oa.auditActionSwitch(audit.UserOAuth2ApplicationRemove, audit.OrganizationOAuth2ApplicationRemove, audit.SystemOAuth2ApplicationRemove), oa.Doer, oa.Owner, app, "Removed OAuth2 application %s.", app.Name) + audit.Record(ctx, oa.auditActionSwitch(audit_model.UserOAuth2ApplicationRemove, audit_model.OrganizationOAuth2ApplicationRemove, audit_model.SystemOAuth2ApplicationRemove), oa.Doer, oa.Owner, app, "Removed OAuth2 application %s.", app.Name) ctx.Flash.Success(ctx.Tr("settings.remove_oauth2_application_success")) ctx.JSONRedirect(oa.BasePathList) @@ -221,7 +222,7 @@ func (oa *OAuth2CommonHandlers) RevokeGrant(ctx *context.Context) { return } - audit.Record(ctx, audit.UserOAuth2ApplicationRevoke, oa.Doer, oa.Owner, grant, "Revoked OAuth2 grant for application %s.", app.Name) + audit.Record(ctx, audit_model.UserOAuth2ApplicationRevoke, oa.Doer, oa.Owner, grant, "Revoked OAuth2 grant for application %s.", app.Name) ctx.Flash.Success(ctx.Tr("settings.revoke_oauth2_grant_success")) ctx.JSONRedirect(oa.BasePathList) diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index f46704d540634..f6e4671f31349 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -14,6 +14,7 @@ import ( "path/filepath" "strings" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" @@ -124,10 +125,10 @@ func ProfilePost(ctx *context.Context) { log.Trace("User settings updated: %s", ctx.Doer.Name) - audit.Record(ctx, audit.UserUpdate, ctx.Doer, ctx.Doer, ctx.Doer, "Updated settings of user %s.", ctx.Doer.Name) + audit.Record(ctx, audit_model.UserUpdate, ctx.Doer, ctx.Doer, ctx.Doer, "Updated settings of user %s.", ctx.Doer.Name) if oldVisibility != ctx.Doer.Visibility { - audit.Record(ctx, audit.UserVisibility, ctx.Doer, ctx.Doer, ctx.Doer, "Visibility of user %s changed from %s to %s.", ctx.Doer.Name, oldVisibility.String(), ctx.Doer.Visibility.String()) + audit.Record(ctx, audit_model.UserVisibility, ctx.Doer, ctx.Doer, ctx.Doer, "Visibility of user %s changed from %s to %s.", ctx.Doer.Name, oldVisibility.String(), ctx.Doer.Visibility.String()) } ctx.Flash.Success(ctx.Tr("settings.update_profile_success")) diff --git a/routers/web/user/setting/security/2fa.go b/routers/web/user/setting/security/2fa.go index d85ea3ad0d9fe..a3e7fe6db5a50 100644 --- a/routers/web/user/setting/security/2fa.go +++ b/routers/web/user/setting/security/2fa.go @@ -12,6 +12,7 @@ import ( "net/http" "strings" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" @@ -50,7 +51,7 @@ func RegenerateScratchTwoFactor(ctx *context.Context) { return } - audit.Record(ctx, audit.UserTwoFactorRegenerate, ctx.Doer, ctx.Doer, t, "User %s regenerated two-factor authentication secret.", ctx.Doer.Name) + audit.Record(ctx, audit_model.UserTwoFactorRegenerate, ctx.Doer, ctx.Doer, t, "User %s regenerated two-factor authentication secret.", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.twofa_scratch_token_regenerated", token)) ctx.Redirect(setting.AppSubURL + "/user/settings/security") @@ -81,7 +82,7 @@ func DisableTwoFactor(ctx *context.Context) { return } - audit.Record(ctx, audit.UserTwoFactorDisable, ctx.Doer, ctx.Doer, t, "User %s disabled two-factor authentication.", ctx.Doer.Name) + audit.Record(ctx, audit_model.UserTwoFactorDisable, ctx.Doer, ctx.Doer, t, "User %s disabled two-factor authentication.", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.twofa_disabled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") @@ -249,7 +250,7 @@ func EnrollTwoFactorPost(ctx *context.Context) { return } - audit.Record(ctx, audit.UserTwoFactorEnable, ctx.Doer, ctx.Doer, t, "User %s enabled two-factor authentication.", ctx.Doer.Name) + audit.Record(ctx, audit_model.UserTwoFactorEnable, ctx.Doer, ctx.Doer, t, "User %s enabled two-factor authentication.", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.twofa_enrolled", token)) ctx.Redirect(setting.AppSubURL + "/user/settings/security") diff --git a/routers/web/user/setting/security/openid.go b/routers/web/user/setting/security/openid.go index 3bf22931cb7c2..3782167af8b0d 100644 --- a/routers/web/user/setting/security/openid.go +++ b/routers/web/user/setting/security/openid.go @@ -6,6 +6,7 @@ package security import ( "net/http" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/openid" @@ -100,7 +101,7 @@ func settingsOpenIDVerify(ctx *context.Context) { return } - audit.Record(ctx, audit.UserOpenIDAdd, ctx.Doer, ctx.Doer, oid, "Associated OpenID %s to user %s.", oid.URI, ctx.Doer.Name) + audit.Record(ctx, audit_model.UserOpenIDAdd, ctx.Doer, ctx.Doer, oid, "Associated OpenID %s to user %s.", oid.URI, ctx.Doer.Name) log.Trace("Associated OpenID %s to user %s", id, ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.add_openid_success")) @@ -122,7 +123,7 @@ func DeleteOpenID(ctx *context.Context) { return } - audit.Record(ctx, audit.UserOpenIDAdd, ctx.Doer, ctx.Doer, oid, "Removed OpenID %s from user %s.", oid.URI, ctx.Doer.Name) + audit.Record(ctx, audit_model.UserOpenIDAdd, ctx.Doer, ctx.Doer, oid, "Removed OpenID %s from user %s.", oid.URI, ctx.Doer.Name) log.Trace("OpenID address deleted: %s", ctx.Doer.Name) diff --git a/routers/web/user/setting/security/security.go b/routers/web/user/setting/security/security.go index e4d74ef246148..a4ffda00da4b7 100644 --- a/routers/web/user/setting/security/security.go +++ b/routers/web/user/setting/security/security.go @@ -8,6 +8,7 @@ import ( "net/http" "sort" + audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" @@ -53,7 +54,7 @@ func DeleteAccountLink(ctx *context.Context) { ctx.Flash.Error("RemoveAccountLink: " + err.Error()) return } else { - audit.Record(ctx, audit.UserExternalLoginRemove, ctx.Doer, ctx.Doer, elu, "Removed external login %s for user %s.", elu.ExternalID, ctx.Doer.Name) + audit.Record(ctx, audit_model.UserExternalLoginRemove, ctx.Doer, ctx.Doer, ctx.Doer, "Removed external login %s for user %s.", elu.ExternalID, ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success")) } diff --git a/routers/web/user/setting/security/webauthn.go b/routers/web/user/setting/security/webauthn.go index 55dcd7620ba9d..ddf8a24d500ce 100644 --- a/routers/web/user/setting/security/webauthn.go +++ b/routers/web/user/setting/security/webauthn.go @@ -9,6 +9,7 @@ import ( "strconv" "time" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" wa "code.gitea.io/gitea/modules/auth/webauthn" "code.gitea.io/gitea/modules/context" @@ -107,7 +108,7 @@ func WebauthnRegisterPost(ctx *context.Context) { } _ = ctx.Session.Delete("webauthnName") - audit.Record(ctx, audit.UserWebAuthAdd, ctx.Doer, ctx.Doer, dbCred, "User %s added WebAuthn key %s.", ctx.Doer.Name, dbCred.Name) + audit.Record(ctx, audit_model.UserWebAuthAdd, ctx.Doer, ctx.Doer, dbCred, "User %s added WebAuthn key %s.", ctx.Doer.Name, dbCred.Name) ctx.JSON(http.StatusCreated, cred) } @@ -126,7 +127,7 @@ func WebauthnDelete(ctx *context.Context) { ctx.ServerError("DeleteCredential", err) return } else if ok { - audit.Record(ctx, audit.UserWebAuthRemove, ctx.Doer, ctx.Doer, cred, "User %s removed WebAuthn key %s.", ctx.Doer.Name, cred.Name) + audit.Record(ctx, audit_model.UserWebAuthRemove, ctx.Doer, ctx.Doer, cred, "User %s removed WebAuthn key %s.", ctx.Doer.Name, cred.Name) } ctx.JSONRedirect(setting.AppSubURL + "/user/settings/security") diff --git a/routers/web/user/setting/webhooks.go b/routers/web/user/setting/webhooks.go index a18ec02488915..56f5985e2409c 100644 --- a/routers/web/user/setting/webhooks.go +++ b/routers/web/user/setting/webhooks.go @@ -6,6 +6,7 @@ package setting import ( "net/http" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -46,7 +47,7 @@ func DeleteWebhook(ctx *context.Context) { if err := webhook.DeleteWebhookByOwnerID(ctx, ctx.Doer.ID, hook.ID); err != nil { ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error()) } else { - audit.Record(ctx, audit.UserWebhookRemove, ctx.Doer, ctx.Doer, hook, "Removed webhook %s.", hook.URL) + audit.Record(ctx, audit_model.UserWebhookRemove, ctx.Doer, ctx.Doer, hook, "Removed webhook %s.", hook.URL) ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } diff --git a/services/asymkey/deploy_key.go b/services/asymkey/deploy_key.go index 62cbf1ddcaf0f..588799bfbdad1 100644 --- a/services/asymkey/deploy_key.go +++ b/services/asymkey/deploy_key.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -42,7 +43,7 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error return err } - audit.Record(ctx, audit.RepositoryDeployKeyRemove, doer, repo, key, "Removed deploy key %s.", key.Name) + audit.Record(ctx, audit_model.RepositoryDeployKeyRemove, doer, repo, key, "Removed deploy key %s.", key.Name) return asymkey_model.RewriteAllPublicKeys(ctx) } diff --git a/services/asymkey/ssh_key.go b/services/asymkey/ssh_key.go index 4fc46ca261f1c..ed75dc8cfdf11 100644 --- a/services/asymkey/ssh_key.go +++ b/services/asymkey/ssh_key.go @@ -7,6 +7,7 @@ import ( "context" asymkey_model "code.gitea.io/gitea/models/asymkey" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/services/audit" @@ -49,12 +50,12 @@ func DeletePublicKey(ctx context.Context, doer *user_model.User, id int64) (err committer.Close() if key.Type == asymkey_model.KeyTypePrincipal { - audit.Record(ctx, audit.UserKeyPrincipalRemove, doer, owner, key, "Removed principal key %s.", key.Name) + audit.Record(ctx, audit_model.UserKeyPrincipalRemove, doer, owner, key, "Removed principal key %s.", key.Name) return asymkey_model.RewriteAllPrincipalKeys(ctx) } - audit.Record(ctx, audit.UserKeySSHRemove, doer, owner, key, "Removed SSH key %s.", key.Fingerprint) + audit.Record(ctx, audit_model.UserKeySSHRemove, doer, owner, key, "Removed SSH key %s.", key.Fingerprint) return asymkey_model.RewriteAllPublicKeys(ctx) } diff --git a/services/audit/audit.go b/services/audit/audit.go index bce864f896286..c349553ab7728 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" + audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" git_model "code.gitea.io/gitea/models/git" organization_model "code.gitea.io/gitea/models/organization" @@ -24,20 +25,20 @@ import ( ) type TypeDescriptor struct { - Type string `json:"type"` - PrimaryKey any `json:"primary_key"` - FriendlyName string `json:"friendly_name"` - Target any `json:"-"` + Type audit_model.ObjectType `json:"type"` + ID int64 `json:"id"` + DisplayName string `json:"display_name"` + Object any `json:"-"` } type Event struct { - Action Action `json:"action"` - Doer TypeDescriptor `json:"doer"` - 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"` + Message string `json:"message"` + Time time.Time `json:"time"` + IPAddress string `json:"ip_address"` } func Init() error { @@ -48,22 +49,25 @@ func Init() error { return initAuditFile() } -func Record(ctx context.Context, action Action, doer *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, message string, v ...any) { if !setting.Audit.Enabled { return } - e := BuildEvent(ctx, action, doer, scope, target, message, v...) + e := BuildEvent(ctx, action, actor, scope, target, message, v...) if err := writeToFile(e); err != nil { log.Error("Error writing audit event to file: %v", err) } + if err := writeToDatabase(ctx, e); err != nil { + log.Error("Error writing audit event to database: %v", err) + } } -func BuildEvent(ctx context.Context, action Action, doer *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, message string, v ...any) *Event { return &Event{ Action: action, - Doer: typeToDescription(doer), + Actor: typeToDescription(actor), Scope: scopeToDescription(scope), Target: typeToDescription(target), Message: fmt.Sprintf(message, v...), @@ -74,7 +78,7 @@ func BuildEvent(ctx context.Context, action Action, doer *user_model.User, scope func scopeToDescription(scope any) TypeDescriptor { if scope == nil { - return TypeDescriptor{"system", 0, "System", nil} + return TypeDescriptor{audit_model.TypeSystem, 0, "System", nil} } switch s := scope.(type) { @@ -88,50 +92,48 @@ func scopeToDescription(scope any) TypeDescriptor { func typeToDescription(val any) TypeDescriptor { switch t := val.(type) { case *repository_model.Repository: - return TypeDescriptor{"repository", t.ID, t.FullName(), val} + return TypeDescriptor{audit_model.TypeRepository, t.ID, t.FullName(), val} case *user_model.User: if t.IsOrganization() { - return TypeDescriptor{"organization", t.ID, t.Name, val} + return TypeDescriptor{audit_model.TypeOrganization, t.ID, t.Name, val} } - return TypeDescriptor{"user", t.ID, t.Name, val} + return TypeDescriptor{audit_model.TypeUser, t.ID, t.Name, val} case *organization_model.Organization: - return TypeDescriptor{"organization", t.ID, t.Name, val} + return TypeDescriptor{audit_model.TypeOrganization, t.ID, t.Name, val} case *user_model.EmailAddress: - return TypeDescriptor{"email_address", t.ID, t.Email, val} + return TypeDescriptor{audit_model.TypeEmailAddress, t.ID, t.Email, val} case *organization_model.Team: - return TypeDescriptor{"team", t.ID, t.Name, val} + return TypeDescriptor{audit_model.TypeTeam, t.ID, t.Name, val} case *auth_model.TwoFactor: - return TypeDescriptor{"twofactor", t.ID, "", val} + return TypeDescriptor{audit_model.TypeTwoFactor, t.ID, "", val} case *auth_model.WebAuthnCredential: - return TypeDescriptor{"webauthn", t.ID, t.Name, val} + return TypeDescriptor{audit_model.TypeWebAuthnCredential, t.ID, t.Name, val} case *user_model.UserOpenID: - return TypeDescriptor{"openid", t.ID, t.URI, val} + return TypeDescriptor{audit_model.TypeOpenID, t.ID, t.URI, val} case *auth_model.AccessToken: - return TypeDescriptor{"access_token", t.ID, t.Name, val} + return TypeDescriptor{audit_model.TypeAccessToken, t.ID, t.Name, val} case *auth_model.OAuth2Application: - return TypeDescriptor{"oauth2_application", t.ID, t.Name, val} + return TypeDescriptor{audit_model.TypeOAuth2Application, t.ID, t.Name, val} case *auth_model.OAuth2Grant: - return TypeDescriptor{"oauth2_grant", t.ID, "", val} + return TypeDescriptor{audit_model.TypeOAuth2Grant, t.ID, "", val} case *auth_model.Source: - return TypeDescriptor{"authentication_source", t.ID, t.Name, val} - case *user_model.ExternalLoginUser: - return TypeDescriptor{"external_account", t.ExternalID, t.ExternalID, val} + return TypeDescriptor{audit_model.TypeAuthenticationSource, t.ID, t.Name, val} case *asymkey_model.PublicKey: - return TypeDescriptor{"public_key", t.ID, t.Fingerprint, val} + return TypeDescriptor{audit_model.TypePublicKey, t.ID, t.Fingerprint, val} case *asymkey_model.GPGKey: - return TypeDescriptor{"gpg_key", t.ID, t.KeyID, val} + return TypeDescriptor{audit_model.TypeGPGKey, t.ID, t.KeyID, val} case *secret_model.Secret: - return TypeDescriptor{"secret", t.ID, t.Name, val} + return TypeDescriptor{audit_model.TypeSecret, t.ID, t.Name, val} case *webhook_model.Webhook: - return TypeDescriptor{"webhook", t.ID, t.URL, val} + return TypeDescriptor{audit_model.TypeWebhook, t.ID, t.URL, val} case *git_model.ProtectedTag: - return TypeDescriptor{"protected_tag", t.ID, t.NamePattern, val} + return TypeDescriptor{audit_model.TypeProtectedTag, t.ID, t.NamePattern, val} case *git_model.ProtectedBranch: - return TypeDescriptor{"protected_branch", t.ID, t.RuleName, val} + return TypeDescriptor{audit_model.TypeProtectedBranch, t.ID, t.RuleName, val} case *repository_model.PushMirror: - return TypeDescriptor{"push_mirror", t.ID, t.RemoteAddress, val} + return TypeDescriptor{audit_model.TypePushMirror, t.ID, t.RemoteAddress, val} case *models.RepoTransfer: - return TypeDescriptor{"repo_transfer", t.ID, "", val} + return TypeDescriptor{audit_model.TypeRepoTransfer, t.ID, "", val} default: panic(fmt.Sprintf("unsupported type: %T", t)) } diff --git a/services/audit/audit_test.go b/services/audit/audit_test.go index 3b0c82ad80717..d38387dae8a9a 100644 --- a/services/audit/audit_test.go +++ b/services/audit/audit_test.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" + audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" git_model "code.gitea.io/gitea/models/git" organization_model "code.gitea.io/gitea/models/organization" @@ -40,7 +41,7 @@ func TestBuildEvent(t *testing.T) { equal( &Event{ - Action: UserUpdate, + Action: audit_model.UserUpdate, Doer: TypeDescriptor{Type: "user", PrimaryKey: int64(2), FriendlyName: "Doer", Target: doer}, Scope: TypeDescriptor{Type: "user", PrimaryKey: int64(1), FriendlyName: "TestUser", Target: u}, Target: TypeDescriptor{Type: "user", PrimaryKey: int64(1), FriendlyName: "TestUser", Target: u}, @@ -48,7 +49,7 @@ func TestBuildEvent(t *testing.T) { }, BuildEvent( ctx, - UserUpdate, + audit_model.UserUpdate, doer, u, u, @@ -58,7 +59,7 @@ func TestBuildEvent(t *testing.T) { ) equal( &Event{ - Action: RepositoryMirrorPushAdd, + Action: audit_model.RepositoryMirrorPushAdd, Doer: TypeDescriptor{Type: "user", PrimaryKey: int64(2), FriendlyName: "Doer", Target: doer}, Scope: TypeDescriptor{Type: "repository", PrimaryKey: int64(3), FriendlyName: "TestUser/TestRepo", Target: r}, Target: TypeDescriptor{Type: "push_mirror", PrimaryKey: int64(4), FriendlyName: "", Target: m}, @@ -66,7 +67,7 @@ func TestBuildEvent(t *testing.T) { }, BuildEvent( ctx, - RepositoryMirrorPushAdd, + audit_model.RepositoryMirrorPushAdd, doer, r, m, @@ -75,12 +76,12 @@ func TestBuildEvent(t *testing.T) { ), ) - e := BuildEvent(ctx, UserUpdate, doer, u, u, "") + e := BuildEvent(ctx, audit_model.UserUpdate, doer, u, u, "") assert.Empty(t, e.IPAddress) ctx = middleware.WithContextRequest(ctx, &http.Request{RemoteAddr: "127.0.0.1:1234"}) - e = BuildEvent(ctx, UserUpdate, doer, u, u, "") + e = BuildEvent(ctx, audit_model.UserUpdate, doer, u, u, "") assert.Equal(t, "127.0.0.1", e.IPAddress) } @@ -92,19 +93,19 @@ func TestScopeToDescription(t *testing.T) { }{ { Scope: nil, - Expected: TypeDescriptor{Type: "system", PrimaryKey: 0, FriendlyName: "System"}, + Expected: TypeDescriptor{Type: audit_model.TypeSystem, PrimaryKey: 0, FriendlyName: "System"}, }, { Scope: &user_model.User{ID: 1, Name: "TestUser"}, - Expected: TypeDescriptor{Type: "user", PrimaryKey: int64(1), FriendlyName: "TestUser"}, + Expected: TypeDescriptor{Type: audit_model.TypeUser, PrimaryKey: int64(1), FriendlyName: "TestUser"}, }, { Scope: &organization_model.Organization{ID: 2, Name: "TestOrg"}, - Expected: TypeDescriptor{Type: "organization", PrimaryKey: int64(2), FriendlyName: "TestOrg"}, + Expected: TypeDescriptor{Type: audit_model.TypeOrganization, PrimaryKey: int64(2), FriendlyName: "TestOrg"}, }, { Scope: &repository_model.Repository{ID: 3, Name: "TestRepo", OwnerName: "TestUser"}, - Expected: TypeDescriptor{Type: "repository", PrimaryKey: int64(3), FriendlyName: "TestUser/TestRepo"}, + Expected: TypeDescriptor{Type: audit_model.TypeRepository, PrimaryKey: int64(3), FriendlyName: "TestUser/TestRepo"}, }, { ShouldPanic: true, @@ -140,83 +141,87 @@ func TestTypeToDescription(t *testing.T) { }, { Type: &user_model.User{ID: 1, Name: "TestUser"}, - Expected: TypeDescriptor{Type: "user", PrimaryKey: int64(1), FriendlyName: "TestUser"}, + Expected: TypeDescriptor{Type: audit_model.TypeUser, PrimaryKey: int64(1), FriendlyName: "TestUser"}, }, { Type: &organization_model.Organization{ID: 2, Name: "TestOrg"}, - Expected: TypeDescriptor{Type: "organization", PrimaryKey: int64(2), FriendlyName: "TestOrg"}, + Expected: TypeDescriptor{Type: audit_model.TypeOrganization, PrimaryKey: int64(2), FriendlyName: "TestOrg"}, + }, + { + Type: &user_model.EmailAddress{ID: 3, Email: "user@gitea.com"}, + Expected: TypeDescriptor{Type: audit_model.TypeEmailAddress, PrimaryKey: int64(3), FriendlyName: "user@gitea.com"}, }, { Type: &repository_model.Repository{ID: 3, Name: "TestRepo", OwnerName: "TestUser"}, - Expected: TypeDescriptor{Type: "repository", PrimaryKey: int64(3), FriendlyName: "TestUser/TestRepo"}, + Expected: TypeDescriptor{Type: audit_model.TypeRepository, PrimaryKey: int64(3), FriendlyName: "TestUser/TestRepo"}, }, { Type: &organization_model.Team{ID: 4, Name: "TestTeam"}, - Expected: TypeDescriptor{Type: "team", PrimaryKey: int64(4), FriendlyName: "TestTeam"}, + Expected: TypeDescriptor{Type: audit_model.TypeTeam, PrimaryKey: int64(4), FriendlyName: "TestTeam"}, }, { Type: &auth_model.TwoFactor{ID: 5}, - Expected: TypeDescriptor{Type: "twofactor", PrimaryKey: int64(5), FriendlyName: ""}, + Expected: TypeDescriptor{Type: audit_model.TypeTwoFactor, PrimaryKey: int64(5), FriendlyName: ""}, }, { Type: &auth_model.WebAuthnCredential{ID: 6, Name: "TestCredential"}, - Expected: TypeDescriptor{Type: "webauthn", PrimaryKey: int64(6), FriendlyName: "TestCredential"}, + Expected: TypeDescriptor{Type: audit_model.TypeWebAuthnCredential, PrimaryKey: int64(6), FriendlyName: "TestCredential"}, }, { Type: &user_model.UserOpenID{ID: 7, URI: "test://uri"}, - Expected: TypeDescriptor{Type: "openid", PrimaryKey: int64(7), FriendlyName: "test://uri"}, + Expected: TypeDescriptor{Type: audit_model.TypeOpenID, PrimaryKey: int64(7), FriendlyName: "test://uri"}, }, { Type: &auth_model.AccessToken{ID: 8, Name: "TestToken"}, - Expected: TypeDescriptor{Type: "access_token", PrimaryKey: int64(8), FriendlyName: "TestToken"}, + Expected: TypeDescriptor{Type: audit_model.TypeAccessToken, PrimaryKey: int64(8), FriendlyName: "TestToken"}, }, { Type: &auth_model.OAuth2Application{ID: 9, Name: "TestOAuth2Application"}, - Expected: TypeDescriptor{Type: "oauth2_application", PrimaryKey: int64(9), FriendlyName: "TestOAuth2Application"}, + Expected: TypeDescriptor{Type: audit_model.TypeOAuth2Application, PrimaryKey: int64(9), FriendlyName: "TestOAuth2Application"}, }, { Type: &auth_model.OAuth2Grant{ID: 10}, - Expected: TypeDescriptor{Type: "oauth2_grant", PrimaryKey: int64(10), FriendlyName: ""}, + Expected: TypeDescriptor{Type: audit_model.TypeOAuth2Grant, PrimaryKey: int64(10), FriendlyName: ""}, }, { Type: &auth_model.Source{ID: 11, Name: "TestSource"}, - Expected: TypeDescriptor{Type: "authentication_source", PrimaryKey: int64(11), FriendlyName: "TestSource"}, + Expected: TypeDescriptor{Type: audit_model.TypeAuthenticationSource, PrimaryKey: int64(11), FriendlyName: "TestSource"}, }, - { + /*{ Type: &user_model.ExternalLoginUser{ExternalID: "12"}, - Expected: TypeDescriptor{Type: "external_account", PrimaryKey: "12", FriendlyName: "12"}, - }, + Expected: TypeDescriptor{Type: audit_model.TypeExternalLoginUser, PrimaryKey: "12", FriendlyName: "12"}, + },*/ { Type: &asymkey_model.PublicKey{ID: 13, Fingerprint: "TestPublicKey"}, - Expected: TypeDescriptor{Type: "public_key", PrimaryKey: int64(13), FriendlyName: "TestPublicKey"}, + Expected: TypeDescriptor{Type: audit_model.TypePublicKey, PrimaryKey: int64(13), FriendlyName: "TestPublicKey"}, }, { Type: &asymkey_model.GPGKey{ID: 14, KeyID: "TestGPGKey"}, - Expected: TypeDescriptor{Type: "gpg_key", PrimaryKey: int64(14), FriendlyName: "TestGPGKey"}, + Expected: TypeDescriptor{Type: audit_model.TypeGPGKey, PrimaryKey: int64(14), FriendlyName: "TestGPGKey"}, }, { Type: &secret_model.Secret{ID: 15, Name: "TestSecret"}, - Expected: TypeDescriptor{Type: "secret", PrimaryKey: int64(15), FriendlyName: "TestSecret"}, + Expected: TypeDescriptor{Type: audit_model.TypeSecret, PrimaryKey: int64(15), FriendlyName: "TestSecret"}, }, { Type: &webhook_model.Webhook{ID: 16, URL: "test://webhook"}, - Expected: TypeDescriptor{Type: "webhook", PrimaryKey: int64(16), FriendlyName: "test://webhook"}, + Expected: TypeDescriptor{Type: audit_model.TypeWebhook, PrimaryKey: int64(16), FriendlyName: "test://webhook"}, }, { Type: &git_model.ProtectedTag{ID: 17, NamePattern: "TestProtectedTag"}, - Expected: TypeDescriptor{Type: "protected_tag", PrimaryKey: int64(17), FriendlyName: "TestProtectedTag"}, + Expected: TypeDescriptor{Type: audit_model.TypeProtectedTag, PrimaryKey: int64(17), FriendlyName: "TestProtectedTag"}, }, { Type: &git_model.ProtectedBranch{ID: 18, RuleName: "TestProtectedBranch"}, - Expected: TypeDescriptor{Type: "protected_branch", PrimaryKey: int64(18), FriendlyName: "TestProtectedBranch"}, + Expected: TypeDescriptor{Type: audit_model.TypeProtectedBranch, PrimaryKey: int64(18), FriendlyName: "TestProtectedBranch"}, }, { Type: &repository_model.PushMirror{ID: 19}, - Expected: TypeDescriptor{Type: "push_mirror", PrimaryKey: int64(19), FriendlyName: ""}, + Expected: TypeDescriptor{Type: audit_model.TypePushMirror, PrimaryKey: int64(19), FriendlyName: ""}, }, { Type: &models.RepoTransfer{ID: 20}, - Expected: TypeDescriptor{Type: "repo_transfer", PrimaryKey: int64(20), FriendlyName: ""}, + Expected: TypeDescriptor{Type: audit_model.TypeRepoTransfer, PrimaryKey: int64(20), FriendlyName: ""}, }, { ShouldPanic: true, diff --git a/services/audit/database.go b/services/audit/database.go new file mode 100644 index 0000000000000..a0fb9954a9542 --- /dev/null +++ b/services/audit/database.go @@ -0,0 +1,24 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package audit + +import ( + "context" + + audit_model "code.gitea.io/gitea/models/audit" +) + +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, + }) + return err +} diff --git a/services/auth/basic.go b/services/auth/basic.go index 7878e6c647bb0..4c7fca6323f28 100644 --- a/services/auth/basic.go +++ b/services/auth/basic.go @@ -9,6 +9,7 @@ import ( "strings" actions_model "code.gitea.io/gitea/models/actions" + audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" @@ -156,7 +157,7 @@ func validateTOTP(req *http.Request, u *user_model.User) error { if ok, err := twofa.ValidateTOTP(req.Header.Get("X-Gitea-OTP")); err != nil { return err } else if !ok { - audit.Record(req.Context(), audit.UserAuthenticationFailTwoFactor, u, u, twofa, "Failed two-factor authentication for user %s.", u.Name) + audit.Record(req.Context(), audit_model.UserAuthenticationFailTwoFactor, u, u, twofa, "Failed two-factor authentication for user %s.", u.Name) return util.NewInvalidArgumentErrorf("invalid provided OTP") } diff --git a/services/auth/reverseproxy.go b/services/auth/reverseproxy.go index 873332013b5c1..dbce4f7ecec5d 100644 --- a/services/auth/reverseproxy.go +++ b/services/auth/reverseproxy.go @@ -8,6 +8,7 @@ import ( "net/http" "strings" + audit_model "code.gitea.io/gitea/models/audit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -171,7 +172,7 @@ func (r *ReverseProxy) newUser(req *http.Request) *user_model.User { return nil } - audit.Record(req.Context(), audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + audit.Record(req.Context(), audit_model.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) return user } diff --git a/services/auth/source.go b/services/auth/source.go index d8af497619804..d60e99b9511bb 100644 --- a/services/auth/source.go +++ b/services/auth/source.go @@ -6,6 +6,7 @@ package auth import ( "context" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" @@ -41,7 +42,7 @@ func DeleteSource(ctx context.Context, doer *user_model.User, source *auth.Sourc _, err = db.GetEngine(ctx).ID(source.ID).Delete(new(auth.Source)) if err == nil { - audit.Record(ctx, audit.SystemAuthenticationSourceRemove, doer, nil, source, "Removed authentication source %s.", source.Name) + audit.Record(ctx, audit_model.SystemAuthenticationSourceRemove, doer, nil, source, "Removed authentication source %s.", source.Name) } return err diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index fef2bb0f1bd4f..0f5619761ba1c 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -9,6 +9,7 @@ import ( "strings" asymkey_model "code.gitea.io/gitea/models/asymkey" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" auth_module "code.gitea.io/gitea/modules/auth" @@ -78,19 +79,19 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u if user != nil { if isAdminChanged { - audit.Record(ctx, audit.UserAdmin, audit.NewAuthenticationSourceUser(), user, user, "Admin status of user %s changed to %s.", user.Name, audit.UserAdminString(user.IsAdmin)) + audit.Record(ctx, audit_model.UserAdmin, audit.NewAuthenticationSourceUser(), user, user, "Admin status of user %s changed to %s.", user.Name, audit.UserAdminString(user.IsAdmin)) } if isRestrictedChanged { - audit.Record(ctx, audit.UserRestricted, audit.NewAuthenticationSourceUser(), user, user, "Restricted status of user %s changed to %s.", user.Name, audit.UserRestrictedString(user.IsRestricted)) + audit.Record(ctx, audit_model.UserRestricted, audit.NewAuthenticationSourceUser(), user, user, "Restricted status of user %s changed to %s.", user.Name, audit.UserRestrictedString(user.IsRestricted)) } if isAttributeSSHPublicKeySet { if addedKeys, deletedKeys := asymkey_model.SynchronizePublicKeys(ctx, user, source.authSource, sr.SSHPublicKey); len(addedKeys) > 0 || len(deletedKeys) > 0 { for _, key := range addedKeys { - audit.Record(ctx, audit.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), user, user, "Added SSH key %s.", key.Fingerprint) + audit.Record(ctx, audit_model.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), user, user, "Added SSH key %s.", key.Fingerprint) } for _, key := range deletedKeys { - audit.Record(ctx, audit.UserKeySSHRemove, audit.NewAuthenticationSourceUser(), user, user, "Removed SSH key %s.", key.Fingerprint) + audit.Record(ctx, audit_model.UserKeySSHRemove, audit.NewAuthenticationSourceUser(), user, user, "Removed SSH key %s.", key.Fingerprint) } if err := asymkey_model.RewriteAllPublicKeys(ctx); err != nil { @@ -119,12 +120,12 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u return user, err } - audit.Record(ctx, audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + audit.Record(ctx, audit_model.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) if isAttributeSSHPublicKeySet { if addedKeys := asymkey_model.AddPublicKeysBySource(ctx, user, source.authSource, sr.SSHPublicKey); len(addedKeys) > 0 { for _, key := range addedKeys { - audit.Record(ctx, audit.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), user, user, "Added SSH key %s.", key.Fingerprint) + audit.Record(ctx, audit_model.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), user, user, "Added SSH key %s.", key.Fingerprint) } if err := asymkey_model.RewriteAllPublicKeys(ctx); err != nil { diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index 26da9f0ed1de8..4ad4d13d67695 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -9,6 +9,7 @@ import ( "strings" asymkey_model "code.gitea.io/gitea/models/asymkey" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" @@ -133,7 +134,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { if err != nil { log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.authSource.Name, su.Username, err) } else { - audit.Record(ctx, audit.UserCreate, audit.NewAuthenticationSourceUser(), usr, usr, "Created user %s.", usr.Name) + audit.Record(ctx, audit_model.UserCreate, audit.NewAuthenticationSourceUser(), usr, usr, "Created user %s.", usr.Name) if isAttributeSSHPublicKeySet { log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", source.authSource.Name, usr.Name) @@ -141,7 +142,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { sshKeysNeedUpdate = true for _, key := range addedKeys { - audit.Record(ctx, audit.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), usr, usr, "Added SSH key %s.", key.Fingerprint) + audit.Record(ctx, audit_model.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), usr, usr, "Added SSH key %s.", key.Fingerprint) } } } @@ -157,10 +158,10 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { sshKeysNeedUpdate = true for _, key := range addedKeys { - audit.Record(ctx, audit.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), usr, usr, "Added SSH key %s.", key.Fingerprint) + audit.Record(ctx, audit_model.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), usr, usr, "Added SSH key %s.", key.Fingerprint) } for _, key := range deletedKeys { - audit.Record(ctx, audit.UserKeySSHRemove, audit.NewAuthenticationSourceUser(), usr, usr, "Removed SSH key %s.", key.Fingerprint) + audit.Record(ctx, audit_model.UserKeySSHRemove, audit.NewAuthenticationSourceUser(), usr, usr, "Removed SSH key %s.", key.Fingerprint) } } } @@ -198,13 +199,13 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { } if isActiveChanged { - audit.Record(ctx, audit.UserActive, audit.NewAuthenticationSourceUser(), usr, usr, "Activation status of user %s changed to %s.", usr.Name, audit.UserActiveString(usr.IsActive)) + audit.Record(ctx, audit_model.UserActive, audit.NewAuthenticationSourceUser(), usr, usr, "Activation status of user %s changed to %s.", usr.Name, audit.UserActiveString(usr.IsActive)) } if isAdminChanged { - audit.Record(ctx, audit.UserAdmin, audit.NewAuthenticationSourceUser(), usr, usr, "Admin status of user %s changed to %s.", usr.Name, audit.UserAdminString(usr.IsAdmin)) + audit.Record(ctx, audit_model.UserAdmin, audit.NewAuthenticationSourceUser(), usr, usr, "Admin status of user %s changed to %s.", usr.Name, audit.UserAdminString(usr.IsAdmin)) } if isRestrictedChanged { - audit.Record(ctx, audit.UserRestricted, audit.NewAuthenticationSourceUser(), usr, usr, "Restricted status of user %s changed to %s.", usr.Name, audit.UserRestrictedString(usr.IsRestricted)) + audit.Record(ctx, audit_model.UserRestricted, audit.NewAuthenticationSourceUser(), usr, usr, "Restricted status of user %s changed to %s.", usr.Name, audit.UserRestrictedString(usr.IsRestricted)) } } @@ -251,7 +252,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { if err := user_model.UpdateUserCols(ctx, usr, "is_active"); err != nil { log.Error("SyncExternalUsers[%s]: Error deactivating user %s: %v", source.authSource.Name, usr.Name, err) } else { - audit.Record(ctx, audit.UserActive, audit.NewAuthenticationSourceUser(), usr, usr, "Activation status of user %s changed to %s.", usr.Name, audit.UserActiveString(usr.IsActive)) + audit.Record(ctx, audit_model.UserActive, audit.NewAuthenticationSourceUser(), usr, usr, "Activation status of user %s changed to %s.", usr.Name, audit.UserActiveString(usr.IsActive)) } } } diff --git a/services/auth/source/pam/source_authenticate.go b/services/auth/source/pam/source_authenticate.go index 1bbf76c5e9e40..23debe57e37d1 100644 --- a/services/auth/source/pam/source_authenticate.go +++ b/services/auth/source/pam/source_authenticate.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/pam" @@ -68,7 +69,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u return user, err } - audit.Record(ctx, audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + audit.Record(ctx, audit_model.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) return user, nil } diff --git a/services/auth/source/smtp/source_authenticate.go b/services/auth/source/smtp/source_authenticate.go index f179529ef04d8..6dafd81790793 100644 --- a/services/auth/source/smtp/source_authenticate.go +++ b/services/auth/source/smtp/source_authenticate.go @@ -10,6 +10,7 @@ import ( "net/textproto" "strings" + audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/util" @@ -83,7 +84,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u return user, err } - audit.Record(ctx, audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + audit.Record(ctx, audit_model.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) return user, nil } diff --git a/services/auth/source/source_group_sync.go b/services/auth/source/source_group_sync.go index ed66f6c5f07ab..72ca131e44afd 100644 --- a/services/auth/source/source_group_sync.go +++ b/services/auth/source/source_group_sync.go @@ -8,6 +8,7 @@ import ( "fmt" "code.gitea.io/gitea/models" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" @@ -106,14 +107,14 @@ func syncGroupsToTeamsCached(ctx context.Context, user *user_model.User, orgTeam return err } - audit.Record(ctx, audit.OrganizationTeamMemberAdd, audit.NewAuthenticationSourceUser(), org, team, "User %s was added to team %s/%s.", user.Name, org.Name, team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, audit.NewAuthenticationSourceUser(), org, team, "User %s was added to team %s/%s.", user.Name, org.Name, team.Name) } else if action == syncRemove && isMember { if err := models.RemoveTeamMember(ctx, team, user.ID); err != nil { log.Error("group sync: Could not remove user from team: %v", err) return err } - audit.Record(ctx, audit.OrganizationTeamMemberRemove, audit.NewAuthenticationSourceUser(), org, team, "User %s was removed from team %s/%s.", user.Name, org.Name, team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberRemove, audit.NewAuthenticationSourceUser(), org, team, "User %s was removed from team %s/%s.", user.Name, org.Name, team.Name) } } } diff --git a/services/auth/sspi.go b/services/auth/sspi.go index 0a979db0383bf..ae93f4de3a315 100644 --- a/services/auth/sspi.go +++ b/services/auth/sspi.go @@ -10,6 +10,7 @@ import ( "strings" "sync" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/avatars" user_model "code.gitea.io/gitea/models/user" @@ -184,7 +185,7 @@ func (s *SSPI) newUser(ctx context.Context, username string, cfg *sspi.Source) ( return nil, err } - audit.Record(ctx, audit.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + audit.Record(ctx, audit_model.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) return user, nil } diff --git a/services/externalaccount/user.go b/services/externalaccount/user.go index 533a025fa8b00..e28d248b965dd 100644 --- a/services/externalaccount/user.go +++ b/services/externalaccount/user.go @@ -7,6 +7,7 @@ import ( "context" "strings" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" @@ -54,7 +55,7 @@ func LinkAccountToUser(ctx context.Context, user *user_model.User, gothUser goth return err } - audit.Record(ctx, audit.UserExternalLoginAdd, user, user, externalLoginUser, "Added external login %s for user %s using provider %s.", externalLoginUser.ExternalID, user.Name, gothUser.Provider) + audit.Record(ctx, audit_model.UserExternalLoginAdd, user, user, user, "Added external login %s for user %s using provider %s.", externalLoginUser.ExternalID, user.Name, gothUser.Provider) externalID := externalLoginUser.ExternalID diff --git a/services/org/org.go b/services/org/org.go index 3a74821e336ed..4fe90afdac9be 100644 --- a/services/org/org.go +++ b/services/org/org.go @@ -8,6 +8,7 @@ import ( "fmt" "code.gitea.io/gitea/models" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" org_model "code.gitea.io/gitea/models/organization" packages_model "code.gitea.io/gitea/models/packages" @@ -57,7 +58,7 @@ func DeleteOrganization(ctx context.Context, doer *user_model.User, org *org_mod return err } - audit.Record(ctx, audit.OrganizationDelete, doer, org, org, "Organization %s was deleted.", org.Name) + audit.Record(ctx, audit_model.OrganizationDelete, doer, org, org, "Organization %s was deleted.", org.Name) // FIXME: system notice // Note: There are something just cannot be roll back, diff --git a/services/org/repo.go b/services/org/repo.go index 0f651f440a96b..b545167ddcc84 100644 --- a/services/org/repo.go +++ b/services/org/repo.go @@ -8,6 +8,7 @@ import ( "errors" "code.gitea.io/gitea/models" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" @@ -30,7 +31,7 @@ func TeamAddRepository(ctx context.Context, doer *user_model.User, t *organizati return err } - audit.Record(ctx, audit.RepositoryCollaboratorTeamAdd, doer, repo, t, "Added team %s as collaborator for %s.", t.Name, repo.FullName()) + audit.Record(ctx, audit_model.RepositoryCollaboratorTeamAdd, doer, repo, t, "Added team %s as collaborator for %s.", t.Name, repo.FullName()) return nil } diff --git a/services/repository/delete.go b/services/repository/delete.go index db5ece72a4f53..9a038c3405c94 100644 --- a/services/repository/delete.go +++ b/services/repository/delete.go @@ -12,6 +12,7 @@ import ( activities_model "code.gitea.io/gitea/models/activities" admin_model "code.gitea.io/gitea/models/admin" asymkey_model "code.gitea.io/gitea/models/asymkey" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -421,7 +422,7 @@ func RemoveRepositoryFromTeam(ctx context.Context, doer *user_model.User, t *org return err } - audit.Record(ctx, audit.RepositoryCollaboratorTeamRemove, doer, repo, t, "Removed team %s as collaborator from %s.", t.Name, repo.FullName()) + audit.Record(ctx, audit_model.RepositoryCollaboratorTeamRemove, doer, repo, t, "Removed team %s as collaborator from %s.", t.Name, repo.FullName()) return nil } diff --git a/services/repository/fork.go b/services/repository/fork.go index cfe82d5346dbd..dc561e8e43700 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -9,6 +9,7 @@ import ( "strings" "time" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" @@ -202,7 +203,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork notify_service.ForkRepository(ctx, doer, opts.BaseRepo, repo) - audit.Record(ctx, audit.RepositoryCreateFork, doer, repo, repo, "Created fork %s of repository %s.", repo.FullName(), opts.BaseRepo.FullName()) + audit.Record(ctx, audit_model.RepositoryCreateFork, doer, repo, repo, "Created fork %s of repository %s.", repo.FullName(), opts.BaseRepo.FullName()) return repo, nil } @@ -238,7 +239,7 @@ func ConvertForkToNormalRepository(ctx context.Context, doer *user_model.User, r return err } - audit.Record(ctx, audit.RepositoryConvertFork, doer, repo, repo, "Converted repository %s from fork to regular repository.", repo.FullName()) + audit.Record(ctx, audit_model.RepositoryConvertFork, doer, repo, repo, "Converted repository %s from fork to regular repository.", repo.FullName()) return nil } diff --git a/services/repository/repository.go b/services/repository/repository.go index de81640e5d2a6..99e56aeac9a1f 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -7,6 +7,7 @@ import ( "context" "fmt" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -49,7 +50,7 @@ func CreateRepository(ctx context.Context, doer, owner *user_model.User, opts Cr notify_service.CreateRepository(ctx, doer, owner, repo) - audit.Record(ctx, audit.RepositoryCreate, doer, repo, repo, "Created repository %s.", repo.FullName()) + audit.Record(ctx, audit_model.RepositoryCreate, doer, repo, repo, "Created repository %s.", repo.FullName()) return repo, nil } @@ -73,7 +74,7 @@ func DeleteRepository(ctx context.Context, doer *user_model.User, repo *repo_mod return err } - audit.Record(ctx, audit.RepositoryDelete, doer, repo, repo, "Deleted repository %s.", repo.FullName()) // repo.Owner load? + audit.Record(ctx, audit_model.RepositoryDelete, doer, repo, repo, "Deleted repository %s.", repo.FullName()) // repo.Owner load? return nil } diff --git a/services/repository/transfer.go b/services/repository/transfer.go index 6a2681deca6e8..8d6bdd969dba2 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -8,6 +8,7 @@ import ( "fmt" "code.gitea.io/gitea/models" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" @@ -49,14 +50,14 @@ func TransferOwnership(ctx context.Context, doer, newOwner *user_model.User, rep return err } - audit.Record(ctx, audit.RepositoryTransferAccept, doer, newRepo, newRepo, "Accepted repository transfer from %s to %s.", oldOwner.Name, newRepo.OwnerName) + audit.Record(ctx, audit_model.RepositoryTransferAccept, doer, newRepo, newRepo, "Accepted repository transfer from %s to %s.", oldOwner.Name, newRepo.OwnerName) for _, team := range teams { if err := models.AddRepository(ctx, team, newRepo); err != nil { return err } - audit.Record(ctx, audit.RepositoryCollaboratorTeamAdd, doer, newRepo, team, "Added team %s as collaborator for %s.", team.Name, newRepo.FullName()) + audit.Record(ctx, audit_model.RepositoryCollaboratorTeamAdd, doer, newRepo, team, "Added team %s as collaborator for %s.", team.Name, newRepo.FullName()) } notify_service.TransferRepository(ctx, doer, repo, oldOwner.Name) @@ -83,7 +84,7 @@ func ChangeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo repo.Name = newRepoName - audit.Record(ctx, audit.RepositoryName, doer, repo, repo, "Repository name changed from %s to %s.", oldRepoName, newRepoName) + audit.Record(ctx, audit_model.RepositoryName, doer, repo, repo, "Repository name changed from %s to %s.", oldRepoName, newRepoName) notify_service.RenameRepository(ctx, doer, repo, oldRepoName) @@ -133,7 +134,7 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use return err } - audit.Record(ctx, audit.RepositoryTransferStart, doer, repo, repo, "Started repository transfer from %s to %s.", repo.OwnerName, newOwner.Name) + audit.Record(ctx, audit_model.RepositoryTransferStart, doer, repo, repo, "Started repository transfer from %s to %s.", repo.OwnerName, newOwner.Name) // notify users who are able to accept / reject transfer notify_service.RepoPendingTransfer(ctx, doer, newOwner, repo) diff --git a/services/secrets/secrets.go b/services/secrets/secrets.go index c86749957abd5..7044fa9964f9b 100644 --- a/services/secrets/secrets.go +++ b/services/secrets/secrets.go @@ -6,6 +6,7 @@ package secrets import ( "context" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" secret_model "code.gitea.io/gitea/models/secret" @@ -34,7 +35,7 @@ func CreateOrUpdateSecret(ctx context.Context, doer, owner *user_model.User, rep } audit.Record(ctx, - auditActionSwitch(owner, repo, audit.UserSecretAdd, audit.OrganizationSecretAdd, audit.RepositorySecretAdd), + auditActionSwitch(owner, repo, audit_model.UserSecretAdd, audit_model.OrganizationSecretAdd, audit_model.RepositorySecretAdd), doer, auditScopeSwitch(owner, repo), s, @@ -52,7 +53,7 @@ func CreateOrUpdateSecret(ctx context.Context, doer, owner *user_model.User, rep } audit.Record(ctx, - auditActionSwitch(owner, repo, audit.UserSecretUpdate, audit.OrganizationSecretUpdate, audit.RepositorySecretUpdate), + auditActionSwitch(owner, repo, audit_model.UserSecretUpdate, audit_model.OrganizationSecretUpdate, audit_model.RepositorySecretUpdate), doer, auditScopeSwitch(owner, repo), s, @@ -105,7 +106,7 @@ func deleteSecret(ctx context.Context, doer, owner *user_model.User, repo *repo_ } audit.Record(ctx, - auditActionSwitch(owner, repo, audit.UserSecretRemove, audit.OrganizationSecretRemove, audit.RepositorySecretRemove), + auditActionSwitch(owner, repo, audit_model.UserSecretRemove, audit_model.OrganizationSecretRemove, audit_model.RepositorySecretRemove), doer, auditScopeSwitch(owner, repo), s, @@ -130,7 +131,7 @@ func tryGetRepositoryID(repo *repo_model.Repository) int64 { return repo.ID } -func auditActionSwitch(owner *user_model.User, repo *repo_model.Repository, userAction, orgAction, repoAction audit.Action) audit.Action { +func auditActionSwitch(owner *user_model.User, repo *repo_model.Repository, userAction, orgAction, repoAction audit_model.Action) audit_model.Action { if owner == nil { return repoAction } diff --git a/services/user/user.go b/services/user/user.go index 2cd2dd6481e67..129beb85f306c 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" + audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" packages_model "code.gitea.io/gitea/models/packages" @@ -120,7 +121,7 @@ func RenameUser(ctx context.Context, doer, u *user_model.User, newUserName strin return err } - audit.Record(ctx, audit.UserName, doer, u, u, "User %s changed name to %s.", oldUserName, newUserName) + audit.Record(ctx, audit_model.UserName, doer, u, u, "User %s changed name to %s.", oldUserName, newUserName) return nil } @@ -280,7 +281,7 @@ func DeleteUser(ctx context.Context, doer, u *user_model.User, purge bool) error } } - audit.Record(ctx, audit.UserDelete, doer, u, u, "User %s was deleted.", u.Name) + audit.Record(ctx, audit_model.UserDelete, doer, u, u, "User %s was deleted.", u.Name) return nil } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 63baeb0e3f4e9..d77dc6917b799 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -10,6 +10,7 @@ import ( "os" "strings" + audit_model "code.gitea.io/gitea/models/audit" repo_model "code.gitea.io/gitea/models/repo" system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unit" @@ -357,7 +358,7 @@ func DeleteWiki(ctx context.Context, doer *user_model.User, repo *repo_model.Rep system_model.RemoveAllWithNotice(ctx, "Delete repository wiki", repo.WikiPath()) - audit.Record(ctx, audit.RepositoryWikiDelete, doer, repo, repo, "Deleted wiki of repository %s.", repo.FullName()) + audit.Record(ctx, audit_model.RepositoryWikiDelete, doer, repo, repo, "Deleted wiki of repository %s.", repo.FullName()) return nil } From 3b945d9392153cce268ccce5423b5a1f013b269f Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Tue, 21 Nov 2023 19:58:05 +0000 Subject: [PATCH 30/44] Display audit logs in admin/org/repo settings. --- .../administration/audit-logging.en-us.md | 52 ++--- models/audit/audit_event.go | 32 ++- models/db/context.go | 2 +- models/migrations/v1_22/v283.go | 20 +- modules/context/context.go | 1 + options/locale/locale_en-US.ini | 11 ++ routers/api/v1/admin/org.go | 2 +- routers/api/v1/admin/user.go | 12 +- routers/api/v1/org/org.go | 4 +- routers/api/v1/org/team.go | 10 +- routers/web/admin/audit.go | 53 +++++ routers/web/admin/emails.go | 2 +- routers/web/admin/users.go | 12 +- routers/web/auth/2fa.go | 2 +- routers/web/auth/auth.go | 4 +- routers/web/auth/oauth.go | 10 +- routers/web/auth/password.go | 8 +- routers/web/org/org.go | 2 +- routers/web/org/setting.go | 4 +- routers/web/org/setting/audit.go | 56 ++++++ routers/web/org/teams.go | 16 +- routers/web/repo/setting/audit.go | 55 ++++++ routers/web/user/setting/account.go | 6 +- routers/web/user/setting/profile.go | 2 +- routers/web/user/setting/security/2fa.go | 6 +- routers/web/user/setting/security/openid.go | 2 +- routers/web/user/setting/security/webauthn.go | 4 +- routers/web/web.go | 4 + services/audit/audit.go | 109 +++++++--- services/audit/audit_test.go | 186 +++++++++++------- services/audit/database.go | 18 +- services/audit/display.go | 117 +++++++++++ services/audit/file.go | 15 ++ services/audit/file_test.go | 6 +- services/auth/basic.go | 2 +- .../auth/source/ldap/source_authenticate.go | 4 +- services/auth/source/ldap/source_sync.go | 8 +- services/auth/source/source_group_sync.go | 4 +- services/org/org.go | 2 +- services/repository/repository.go | 2 +- services/repository/transfer.go | 2 +- services/user/user.go | 4 +- templates/admin/audit/list.tmpl | 6 + templates/admin/navbar.tmpl | 7 +- templates/org/settings/audit_logs.tmpl | 6 + templates/org/settings/navbar.tmpl | 5 + templates/repo/settings/audit_logs.tmpl | 6 + templates/repo/settings/navbar.tmpl | 5 + templates/shared/audit/list.tmpl | 57 ++++++ tests/integration/audit_test.go | 184 +++++++++-------- 50 files changed, 843 insertions(+), 306 deletions(-) create mode 100644 routers/web/admin/audit.go create mode 100644 routers/web/org/setting/audit.go create mode 100644 routers/web/repo/setting/audit.go create mode 100644 services/audit/display.go create mode 100644 templates/admin/audit/list.tmpl create mode 100644 templates/org/settings/audit_logs.tmpl create mode 100644 templates/repo/settings/audit_logs.tmpl create mode 100644 templates/shared/audit/list.tmpl diff --git a/docs/content/administration/audit-logging.en-us.md b/docs/content/administration/audit-logging.en-us.md index 1cd4d41ee8a79..faaacf0a82a38 100644 --- a/docs/content/administration/audit-logging.en-us.md +++ b/docs/content/administration/audit-logging.en-us.md @@ -39,21 +39,21 @@ Audit events are grouped by `user`, `organization`, `repository` and `system`. | Event | Description | | - | - | | `user:impersonation` | Admin impersonating user | -| `user:create` | User was created | +| `user:create` | Created user | | `user:update` | Updated settings of user | -| `user:delete` | User was deleted | +| `user:delete` | Deleted user | | `user:authentication:fail:twofactor` | Failed two-factor authentication for user | -| `user:authentication:source` | Authentication source of user changed | -| `user:active` | Activation status of user changed | -| `user:restricted` | Restriction status of user changed | -| `user:admin` | Admin status of user changed | -| `user:name` | User changed name | -| `user:password` | Password of user changed | -| `user:password:reset` | User requested a password reset | -| `user:visibility` | Visibility of user changed | -| `user:email:add` | Email added to user | -| `user:email:activate` | Email of user activated | -| `user:email:remove` | Email removed from user | +| `user:authentication:source` | Changed authentication source of user | +| `user:active` | Changed activation status of user | +| `user:restricted` | Changed restriction status of user | +| `user:admin` | Changed admin status of user | +| `user:name` | Changed user name | +| `user:password` | Changed password of user | +| `user:password:reset` | Requested a password reset | +| `user:visibility` | Changed visibility of user | +| `user:email:add` | Added email to user | +| `user:email:activate` | Activated email of user | +| `user:email:remove` | Removed email from user | | `user:twofactor:enable` | User enabled two-factor authentication | | `user:twofactor:regenerate` | User regenerated two-factor authentication secret | | `user:twofactor:disable` | User disabled two-factor authentication | @@ -88,17 +88,17 @@ Audit events are grouped by `user`, `organization`, `repository` and `system`. | Event | Description | | - | - | -| `organization:create` | Organization was created | +| `organization:create` | Created organization | | `organization:update` | Updated settings of organization | -| `organization:delete` | Organization was deleted | -| `organization:name` | Organization name changed | -| `organization:visibility` | Visibility of organization changed | -| `organization:team:add` | Team was added to organization | +| `organization:delete` | Deleted organization | +| `organization:name` | Changed organization name | +| `organization:visibility` | Changed visibility of organization | +| `organization:team:add` | Added team to organization | | `organization:team:update` | Updated settings of team | -| `organization:team:remove` | Team was removed from organization | -| `organization:team:permission` | Permission of team changed | -| `organization:team:member:add` | User was added to team | -| `organization:team:member:remove` | User was removed from team | +| `organization:team:remove` | Removed team from organization | +| `organization:team:permission` | Changed permission of team | +| `organization:team:member:add` | Added user to team | +| `organization:team:member:remove` | Removed User from team | | `organization:oauth2application:add` | Created OAuth2 application | | `organization:oauth2application:update` | Updated OAuth2 application | | `organization:oauth2application:secret` | Regenerated secret for OAuth2 application | @@ -114,13 +114,13 @@ Audit events are grouped by `user`, `organization`, `repository` and `system`. | Event | Description | | - | - | -| `repository:create` | Repository was created | -| `repository:create:fork` | Fork of repository was created | +| `repository:create` | Crated repository | +| `repository:create:fork` | Created fork of repository | | `repository:update` | Updated settings of repository | | `repository:archive` | Archived repository | | `repository:unarchive` | Unarchived repository | -| `repository:delete` | Repository was deleted | -| `repository:name` | Repository name changed | +| `repository:delete` | Deleted repository | +| `repository:name` | Changed repository name | | `repository:visibility` | Changed visibility of repository | | `repository:convert:fork` | Converted repository from fork to regular repository | | `repository:convert:mirror` | Converted repository from mirror to regular repository | diff --git a/models/audit/audit_event.go b/models/audit/audit_event.go index 80042c446518e..75aa2ab1c15e0 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 - CreatedUnix timeutil.TimeStamp `xorm:"created 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"` + Message string + IPAddress string + TimestampUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL"` } func (_ *Event) TableName() string { @@ -40,12 +40,10 @@ func InsertEvent(ctx context.Context, e *Event) (*Event, error) { type EventSort = string const ( - SortCreatedAsc EventSort = "created_asc" - SortCreatedDesc EventSort = "created_desc" + SortTimestampAsc EventSort = "timestamp_asc" + SortTimestampDesc EventSort = "timestamp_desc" ) -// PackageSearchOptions are options for SearchXXX methods -// All fields optional and are not used if they have their default value (nil, "", 0) type EventSearchOptions struct { Action Action ActorID int64 @@ -76,10 +74,10 @@ func (opts *EventSearchOptions) ToConds() builder.Cond { func (opts *EventSearchOptions) configureOrderBy(e db.Engine) { switch opts.Sort { - case SortCreatedAsc: - e.Asc("created_unix") + case SortTimestampAsc: + e.Asc("timestamp_unix") default: - e.Desc("created_unix") + e.Desc("timestamp_unix") } // Sort by id for stable order with duplicates in the other field diff --git a/models/db/context.go b/models/db/context.go index 568ae54ae14a5..48791fc620064 100644 --- a/models/db/context.go +++ b/models/db/context.go @@ -179,7 +179,7 @@ func GetByBean(ctx context.Context, bean any) (bool, error) { } // GetByID retrieves the bean with the given ID (given that all non-empty fields match) -func GetByID(ctx context.Context, id, bean interface{}) (bool, error) { +func GetByID(ctx context.Context, id, bean any) (bool, error) { return GetEngine(ctx).ID(id).Get(bean) } diff --git a/models/migrations/v1_22/v283.go b/models/migrations/v1_22/v283.go index 34a2df100b444..cd7217c1f5e5d 100644 --- a/models/migrations/v1_22/v283.go +++ b/models/migrations/v1_22/v283.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 - CreatedUnix timeutil.TimeStamp `xorm:"created 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"` + Message string + IPAddress string + Timestamp timeutil.TimeStamp `xorm:"INDEX NOT NULL"` } return x.Sync(&AuditEvent{}) diff --git a/modules/context/context.go b/modules/context/context.go index 8a94e958b5169..a2db4874dd512 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -208,6 +208,7 @@ func Contexter() func(next http.Handler) http.Handler { ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations ctx.Data["DisableStars"] = setting.Repository.DisableStars ctx.Data["EnableActions"] = setting.Actions.Enabled + ctx.Data["EnableAuditLogs"] = setting.Audit.Enabled ctx.Data["ManifestData"] = setting.ManifestData diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index a7a7a4f4c50f9..1dca468f37526 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3157,6 +3157,17 @@ config.xorm_log_sql = Log SQL config.set_setting_failed = Set setting %s failed +monitor.audit.title = Audit Logs +monitor.audit.actor = Actor +monitor.audit.scope = Scope +monitor.audit.target = Target +monitor.audit.action = Action +monitor.audit.ip_address = IP Address +monitor.audit.timestamp = Timestamp +monitor.audit.no_events = There are no audit events matching the filter. +monitor.audit.deleted.actor = (removed) +monitor.audit.deleted.type = (removed %s [%v]) + monitor.stats = Stats monitor.cron = Cron Tasks diff --git a/routers/api/v1/admin/org.go b/routers/api/v1/admin/org.go index cd30a2e4c8054..412019057dcf6 100644 --- a/routers/api/v1/admin/org.go +++ b/routers/api/v1/admin/org.go @@ -76,7 +76,7 @@ func CreateOrg(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) + audit.Record(ctx, audit_model.OrganizationCreate, ctx.Doer, org, org, "Created organization %s.", org.Name) ctx.JSON(http.StatusCreated, convert.ToOrganization(ctx, org)) } diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 28cb2ea3302f9..78e4c9f6a1b12 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -310,22 +310,22 @@ func EditUser(ctx *context.APIContext) { } if passwordChanged { - audit.Record(ctx, audit_model.UserPassword, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Password of user %s changed.", ctx.ContextUser.Name) + audit.Record(ctx, audit_model.UserPassword, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Changed password of user %s.", ctx.ContextUser.Name) } if auditFields.LoginSource != ctx.ContextUser.LoginSource { - audit.Record(ctx, audit_model.UserAuthenticationSource, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Authentication source of user %s changed.", ctx.ContextUser.Name) + audit.Record(ctx, audit_model.UserAuthenticationSource, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Changed authentication source of user %s.", ctx.ContextUser.Name) } if auditFields.Visibility != ctx.ContextUser.Visibility { - audit.Record(ctx, audit_model.UserVisibility, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Visibility of user %s changed from %s to %s.", ctx.ContextUser.Name, auditFields.Visibility.String(), ctx.ContextUser.Visibility.String()) + audit.Record(ctx, audit_model.UserVisibility, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Changed visibility of user %s from %s to %s.", ctx.ContextUser.Name, auditFields.Visibility.String(), ctx.ContextUser.Visibility.String()) } if auditFields.IsActive != ctx.ContextUser.IsActive { - audit.Record(ctx, audit_model.UserActive, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Activation status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserActiveString(ctx.ContextUser.IsActive)) + audit.Record(ctx, audit_model.UserActive, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Changed activation status of user %s to %s.", ctx.ContextUser.Name, audit.UserActiveString(ctx.ContextUser.IsActive)) } if auditFields.IsAdmin != ctx.ContextUser.IsAdmin { - audit.Record(ctx, audit_model.UserAdmin, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Admin status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserAdminString(ctx.ContextUser.IsAdmin)) + audit.Record(ctx, audit_model.UserAdmin, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Changed admin status of user %s to %s.", ctx.ContextUser.Name, audit.UserAdminString(ctx.ContextUser.IsAdmin)) } if auditFields.IsRestricted != ctx.ContextUser.IsRestricted { - audit.Record(ctx, audit_model.UserRestricted, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Restricted status of user %s changed to %s.", ctx.ContextUser.Name, audit.UserRestrictedString(ctx.ContextUser.IsRestricted)) + audit.Record(ctx, audit_model.UserRestricted, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Changed restricted status of user %s to %s.", ctx.ContextUser.Name, audit.UserRestrictedString(ctx.ContextUser.IsRestricted)) } log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, ctx.ContextUser.Name) diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go index 52bd574486bfd..b2b7075391f0b 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -282,7 +282,7 @@ func Create(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) + audit.Record(ctx, audit_model.OrganizationCreate, ctx.Doer, org, org, "Created organization %s.", org.Name) ctx.JSON(http.StatusCreated, convert.ToOrganization(ctx, org)) } @@ -371,7 +371,7 @@ func Edit(ctx *context.APIContext) { audit.Record(ctx, audit_model.OrganizationUpdate, ctx.Doer, org, org, "Updated settings of organization %s.", org.Name) if org.Visibility != oldVisibility { - audit.Record(ctx, audit_model.OrganizationVisibility, ctx.Doer, org, org, "Visibility of organization %s changed from %s to %s.", org.Name, oldVisibility.String(), org.Visibility.String()) + audit.Record(ctx, audit_model.OrganizationVisibility, ctx.Doer, org, org, "Changed visibility of organization %s from %s to %s.", org.Name, oldVisibility.String(), org.Visibility.String()) } ctx.JSON(http.StatusOK, convert.ToOrganization(ctx, org)) diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index 522854fbc74d0..738a7b3db645c 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -250,7 +250,7 @@ func CreateTeam(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, team, "Team %s was added to organization %s.", team.Name, ctx.Org.Organization.Name) + audit.Record(ctx, audit_model.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, team, "Added team %s to organization %s.", team.Name, ctx.Org.Organization.Name) apiTeam, err := convert.ToTeam(ctx, team, true) if err != nil { @@ -349,7 +349,7 @@ func EditTeam(ctx *context.APIContext) { audit.Record(ctx, audit_model.OrganizationTeamUpdate, ctx.Doer, org, team, "Updated settings of team %s/%s.", org.Name, team.Name) if isAuthChanged { - audit.Record(ctx, audit_model.OrganizationTeamPermission, ctx.Doer, org, team, "Permission of team %s/%s changed from %s to %s.", org.Name, team.Name, oldAccessMode.String(), team.AccessMode.String()) + audit.Record(ctx, audit_model.OrganizationTeamPermission, ctx.Doer, org, team, "Changed permission of team %s/%s from %s to %s.", org.Name, team.Name, oldAccessMode.String(), team.AccessMode.String()) } apiTeam, err := convert.ToTeam(ctx, team) @@ -389,7 +389,7 @@ func DeleteTeam(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.OrganizationTeamRemove, ctx.Doer, org, ctx.Org.Team, "Team %s was removed from organization %s.", ctx.Org.Team.Name, org.Name) + audit.Record(ctx, audit_model.OrganizationTeamRemove, ctx.Doer, org, ctx.Org.Team, "Removed team %s from organization %s.", ctx.Org.Team.Name, org.Name) ctx.Status(http.StatusNoContent) } @@ -530,7 +530,7 @@ func AddTeamMember(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, ctx.Doer, org, ctx.Org.Team, "User %s was added to team %s/%s.", u.Name, org.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, ctx.Doer, org, ctx.Org.Team, "Added user %s to team %s/%s.", u.Name, org.Name, ctx.Org.Team.Name) ctx.Status(http.StatusNoContent) } @@ -576,7 +576,7 @@ func RemoveTeamMember(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.OrganizationTeamMemberRemove, ctx.Doer, org, ctx.Org.Team, "User %s was removed from team %s/%s.", u.Name, org.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberRemove, ctx.Doer, org, ctx.Org.Team, "Removed user %s from team %s/%s.", u.Name, org.Name, ctx.Org.Team.Name) ctx.Status(http.StatusNoContent) } diff --git a/routers/web/admin/audit.go b/routers/web/admin/audit.go new file mode 100644 index 0000000000000..6a536b9581205 --- /dev/null +++ b/routers/web/admin/audit.go @@ -0,0 +1,53 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package admin + +import ( + "net/http" + + audit_model "code.gitea.io/gitea/models/audit" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/audit" +) + +const ( + tplAuditLogs base.TplName = "admin/audit/list" +) + +func ViewAuditLogs(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("admin.monitor.audit.title") + ctx.Data["PageIsAdminMonitorAudit"] = true + + page := ctx.FormInt("page") + if page < 1 { + page = 1 + } + + opts := &audit_model.EventSearchOptions{ + Sort: ctx.FormString("sort"), + Paginator: &db.ListOptions{ + Page: page, + PageSize: setting.UI.Admin.NoticePagingNum, + }, + } + + ctx.Data["AuditSort"] = opts.Sort + + evs, total, err := audit.FindEvents(ctx, opts) + if err != nil { + ctx.ServerError("", err) + return + } + + ctx.Data["AuditEvents"] = evs + + pager := context.NewPagination(int(total), setting.UI.Admin.NoticePagingNum, page, 5) + pager.AddParamString("sort", opts.Sort) + ctx.Data["Page"] = pager + + ctx.HTML(http.StatusOK, tplAuditLogs) +} diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go index 300a27970fefb..ef9cd0bf6bc63 100644 --- a/routers/web/admin/emails.go +++ b/routers/web/admin/emails.go @@ -135,7 +135,7 @@ func ActivateEmail(ctx *context.Context) { ctx.Flash.Error(ctx.Tr("admin.emails.not_updated", err)) } } else { - audit.Record(ctx, audit_model.UserEmailActivate, ctx.Doer, u, u, "Email %s of user %s activated.", email, u.Name) + audit.Record(ctx, audit_model.UserEmailActivate, ctx.Doer, u, u, "Activated email %s of user %s.", email, u.Name) log.Info("Activation for User ID: %d, email: %s, primary: %v changed to %v", uid, email, primary, activate) ctx.Flash.Info(ctx.Tr("admin.emails.updated")) diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 4e7468f4aaa97..40f33cbe9ddd0 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -500,22 +500,22 @@ func EditUserPost(ctx *context.Context) { } if passwordChanged { - audit.Record(ctx, audit_model.UserPassword, ctx.Doer, u, u, "Password of user %s changed.", u.Name) + audit.Record(ctx, audit_model.UserPassword, ctx.Doer, u, u, "Changed password of user %s.", u.Name) } if auditFields.LoginSource != u.LoginSource { - audit.Record(ctx, audit_model.UserAuthenticationSource, ctx.Doer, u, u, "Authentication source of user %s changed.", u.Name) + audit.Record(ctx, audit_model.UserAuthenticationSource, ctx.Doer, u, u, "Changed authentication source of user %s.", u.Name) } if auditFields.Visibility != u.Visibility { - audit.Record(ctx, audit_model.UserVisibility, ctx.Doer, u, u, "Visibility of user %s changed from %s to %s.", u.Name, auditFields.Visibility.String(), u.Visibility.String()) + audit.Record(ctx, audit_model.UserVisibility, ctx.Doer, u, u, "Changed visibility of user %s from %s to %s.", u.Name, auditFields.Visibility.String(), u.Visibility.String()) } if auditFields.IsActive != u.IsActive { - audit.Record(ctx, audit_model.UserActive, ctx.Doer, u, u, "Activation status of user %s changed to %s.", u.Name, audit.UserActiveString(u.IsActive)) + audit.Record(ctx, audit_model.UserActive, ctx.Doer, u, u, "Changed activation status of user %s to %s.", u.Name, audit.UserActiveString(u.IsActive)) } if auditFields.IsAdmin != u.IsAdmin { - audit.Record(ctx, audit_model.UserAdmin, ctx.Doer, u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) + audit.Record(ctx, audit_model.UserAdmin, ctx.Doer, u, u, "Changed admin status of user %s to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) } if auditFields.IsRestricted != u.IsRestricted { - audit.Record(ctx, audit_model.UserRestricted, ctx.Doer, u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) + audit.Record(ctx, audit_model.UserRestricted, ctx.Doer, u, u, "Changed restricted status of user %s to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) } log.Trace("Account profile updated by admin (%s): %s", ctx.Doer.Name, u.Name) diff --git a/routers/web/auth/2fa.go b/routers/web/auth/2fa.go index 7268edbdb7ae0..8d4d217e27b1a 100644 --- a/routers/web/auth/2fa.go +++ b/routers/web/auth/2fa.go @@ -95,7 +95,7 @@ func TwoFactorPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserAuthenticationFailTwoFactor, user_model.NewGhostUser(), u, twofa, "Failed two-factor authentication for user %s.", u.Name) + audit.Record(ctx, audit_model.UserAuthenticationFailTwoFactor, u, u, u, "Failed two-factor authentication for user %s.", u.Name) ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplTwofa, forms.TwoFactorAuthForm{}) } diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 9c503658eee13..34b58b43470c9 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -745,7 +745,7 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) { return } - audit.Record(ctx, audit_model.UserActive, user, user, user, "Activation status of user %s changed to %s.", user.Name, audit.UserActiveString(user.IsActive)) + audit.Record(ctx, audit_model.UserActive, user, user, user, "Changed activation status of user %s to %s.", user.Name, audit.UserActiveString(user.IsActive)) log.Trace("User activated: %s", user.Name) @@ -799,7 +799,7 @@ func ActivateEmail(ctx *context.Context) { _ = ctx.Cache.Delete("MailResendLimit_" + user.LowerName) } - audit.Record(ctx, audit_model.UserEmailActivate, user, user, email, "Email %s of user %s activated.", email.Email, user.Name) + audit.Record(ctx, audit_model.UserEmailActivate, user, user, email, "Activated email %s of user %s.", email.Email, user.Name) } // FIXME: e-mail verification does not require the user to be logged in, diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index c0f8ca3a4259a..7c65b3258110e 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -566,7 +566,7 @@ func GrantApplicationOAuth(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserOAuth2ApplicationGrant, ctx.Doer, owner, grant, "Granted OAuth2 access to application %s.", app.Name) + audit.Record(ctx, audit_model.UserOAuth2ApplicationGrant, ctx.Doer, owner, app, "Granted OAuth2 access to application %s.", app.Name) if len(form.Nonce) > 0 { err := grant.SetNonce(ctx, form.Nonce) @@ -1160,10 +1160,10 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model } if changedIsAdmin { - audit.Record(ctx, audit_model.UserAdmin, audit.NewAuthenticationSourceUser(), u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) + audit.Record(ctx, audit_model.UserAdmin, audit.NewAuthenticationSourceUser(), u, u, "Changed admin status of user %s to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) } if changedIsRestricted { - audit.Record(ctx, audit_model.UserRestricted, audit.NewAuthenticationSourceUser(), u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) + audit.Record(ctx, audit_model.UserRestricted, audit.NewAuthenticationSourceUser(), u, u, "Changed restricted status of user %s to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) } if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval { @@ -1204,10 +1204,10 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model } if changedIsAdmin { - audit.Record(ctx, audit_model.UserAdmin, audit.NewAuthenticationSourceUser(), u, u, "Admin status of user %s changed to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) + audit.Record(ctx, audit_model.UserAdmin, audit.NewAuthenticationSourceUser(), u, u, "Changed admin status of user %s to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) } if changedIsRestricted { - audit.Record(ctx, audit_model.UserRestricted, audit.NewAuthenticationSourceUser(), u, u, "Restricted status of user %s changed to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) + audit.Record(ctx, audit_model.UserRestricted, audit.NewAuthenticationSourceUser(), u, u, "Changed restricted status of user %s to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) } if oauth2Source.GroupTeamMap != "" || oauth2Source.GroupTeamMapRemoval { diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go index 7468cf83afce4..babce0349a00b 100644 --- a/routers/web/auth/password.go +++ b/routers/web/auth/password.go @@ -89,7 +89,7 @@ func ForgotPasswdPost(ctx *context.Context) { mailer.SendResetPasswordMail(u) - audit.Record(ctx, audit_model.UserPasswordReset, u, u, u, "User %s requested a password reset.", u.Name) + audit.Record(ctx, audit_model.UserPasswordReset, u, u, u, "Requested passwort reset for user %s.", u.Name) if setting.CacheService.Enabled { if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { @@ -214,7 +214,7 @@ func ResetPasswdPost(ctx *context.Context) { return } if !ok || twofa.LastUsedPasscode == passcode { - audit.Record(ctx, audit_model.UserAuthenticationFailTwoFactor, u, u, twofa, "Failed two-factor authentication for user %s.", u.Name) + audit.Record(ctx, audit_model.UserAuthenticationFailTwoFactor, u, u, u, "Failed two-factor authentication for user %s.", u.Name) ctx.Data["IsResetForm"] = true ctx.Data["Err_Passcode"] = true @@ -244,7 +244,7 @@ func ResetPasswdPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserPassword, u, u, u, "Password of user %s changed.", u.Name) + audit.Record(ctx, audit_model.UserPassword, u, u, u, "Changed password of user %s.", u.Name) log.Trace("User password reset: %s", u.Name) ctx.Data["IsResetFailed"] = true @@ -345,7 +345,7 @@ func MustChangePasswordPost(ctx *context.Context) { log.Trace("User updated password: %s", u.Name) - audit.Record(ctx, audit_model.UserPassword, u, u, u, "Password of user %s changed.", u.Name) + audit.Record(ctx, audit_model.UserPassword, u, u, u, "Changed password of user %s.", u.Name) if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 && !utils.IsExternalURL(redirectTo) { middleware.DeleteRedirectToCookie(ctx.Resp) diff --git a/routers/web/org/org.go b/routers/web/org/org.go index c65e354aea51d..ba3aae996563a 100644 --- a/routers/web/org/org.go +++ b/routers/web/org/org.go @@ -77,7 +77,7 @@ func CreatePost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.OrganizationCreate, ctx.Doer, org, org, "Organization %s was created.", org.Name) + audit.Record(ctx, audit_model.OrganizationCreate, ctx.Doer, org, org, "Created organization %s.", org.Name) log.Trace("Organization created: %s", org.Name) diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 9d5bbdd893a6c..bdef0c387d0dc 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -146,10 +146,10 @@ func SettingsPost(ctx *context.Context) { audit.Record(ctx, audit_model.OrganizationUpdate, ctx.Doer, org, org, "Updated settings of organization %s.", org.Name) if nameChanged { - audit.Record(ctx, audit_model.OrganizationName, ctx.Doer, org, org, "Organization name changed from %s to %s.", oldName, org.Name) + audit.Record(ctx, audit_model.OrganizationName, ctx.Doer, org, org, "Changed organization name from %s to %s.", oldName, org.Name) } if org.Visibility != oldVisibility { - audit.Record(ctx, audit_model.OrganizationVisibility, ctx.Doer, org, org, "Visibility of organization %s changed from %s to %s.", org.Name, oldVisibility.String(), org.Visibility.String()) + audit.Record(ctx, audit_model.OrganizationVisibility, ctx.Doer, org, org, "Changed visibility of organization %s from %s to %s.", org.Name, oldVisibility.String(), org.Visibility.String()) } log.Trace("Organization setting updated: %s", org.Name) diff --git a/routers/web/org/setting/audit.go b/routers/web/org/setting/audit.go new file mode 100644 index 0000000000000..c1ecf813b4e3d --- /dev/null +++ b/routers/web/org/setting/audit.go @@ -0,0 +1,56 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "net/http" + + audit_model "code.gitea.io/gitea/models/audit" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/audit" +) + +const ( + tplAuditLogs base.TplName = "org/settings/audit_logs" +) + +func ViewAuditLogs(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("admin.monitor.audit.title") + ctx.Data["PageIsOrgSettings"] = true + ctx.Data["PageIsSettingsAudit"] = true + + page := ctx.FormInt("page") + if page < 1 { + page = 1 + } + + opts := &audit_model.EventSearchOptions{ + Sort: ctx.FormString("sort"), + ScopeType: audit_model.TypeOrganization, + ScopeID: ctx.ContextUser.ID, + Paginator: &db.ListOptions{ + Page: page, + PageSize: setting.UI.Admin.NoticePagingNum, + }, + } + + ctx.Data["AuditSort"] = opts.Sort + + evs, total, err := audit.FindEvents(ctx, opts) + if err != nil { + ctx.ServerError("", err) + return + } + + ctx.Data["AuditEvents"] = evs + + pager := context.NewPagination(int(total), setting.UI.Admin.NoticePagingNum, page, 5) + pager.AddParamString("sort", opts.Sort) + ctx.Data["Page"] = pager + + ctx.HTML(http.StatusOK, tplAuditLogs) +} diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index ee1f4af89f0b1..87ec832e081e2 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -82,7 +82,7 @@ func TeamsAction(ctx *context.Context) { } err = models.AddTeamMember(ctx, ctx.Org.Team, ctx.Doer.ID) if err == nil { - audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was added to team %s/%s.", ctx.Doer.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Added user %s to team %s/%s.", ctx.Doer.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) } case "leave": err = models.RemoveTeamMember(ctx, ctx.Org.Team, ctx.Doer.ID) @@ -98,7 +98,7 @@ func TeamsAction(ctx *context.Context) { return } } else { - audit.Record(ctx, audit_model.OrganizationTeamMemberRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was removed from team %s/%s.", ctx.Doer.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Removed user %s from team %s/%s.", ctx.Doer.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) } checkIsOrgMemberAndRedirect(ctx, ctx.Org.OrgLink+"/teams/") return @@ -127,7 +127,7 @@ func TeamsAction(ctx *context.Context) { return } } else { - audit.Record(ctx, audit_model.OrganizationTeamMemberRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was removed from team %s/%s.", u.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Removed user %s from team %s/%s.", u.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) } checkIsOrgMemberAndRedirect(ctx, ctx.Org.OrgLink+"/teams/"+url.PathEscape(ctx.Org.Team.LowerName)) return @@ -173,7 +173,7 @@ func TeamsAction(ctx *context.Context) { } else { err = models.AddTeamMember(ctx, ctx.Org.Team, u.ID) if err == nil { - audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "User %s was added to team %s/%s.", u.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Added user %s to team %s/%s.", u.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) } } @@ -401,7 +401,7 @@ func NewTeamPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, t, "Team %s was added to organization %s.", t.Name, ctx.Org.Organization.Name) + audit.Record(ctx, audit_model.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, t, "Added team %s to organization %s.", t.Name, ctx.Org.Organization.Name) log.Trace("Team created: %s/%s", ctx.Org.Organization.Name, t.Name) ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(t.LowerName)) @@ -585,7 +585,7 @@ func EditTeamPost(ctx *context.Context) { audit.Record(ctx, audit_model.OrganizationTeamUpdate, ctx.Doer, ctx.Org.Organization, t, "Updated settings of team %s/%s.", ctx.Org.Organization.Name, t.Name) if isAuthChanged { - audit.Record(ctx, audit_model.OrganizationTeamPermission, ctx.Doer, ctx.Org.Organization, t, "Permission of team %s/%s changed from %s to %s.", ctx.Org.Organization.Name, t.Name, oldAccessMode.String(), t.AccessMode.String()) + audit.Record(ctx, audit_model.OrganizationTeamPermission, ctx.Doer, ctx.Org.Organization, t, "Changed permission of team %s/%s from %s to %s.", ctx.Org.Organization.Name, t.Name, oldAccessMode.String(), t.AccessMode.String()) } ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(t.LowerName)) @@ -596,7 +596,7 @@ func DeleteTeam(ctx *context.Context) { if err := models.DeleteTeam(ctx, ctx.Org.Team); err != nil { ctx.Flash.Error("DeleteTeam: " + err.Error()) } else { - audit.Record(ctx, audit_model.OrganizationTeamRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Team %s was removed from organization %s.", ctx.Org.Team.Name, ctx.Org.Organization.Name) + audit.Record(ctx, audit_model.OrganizationTeamRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Removed team %s from organization %s.", ctx.Org.Team.Name, ctx.Org.Organization.Name) ctx.Flash.Success(ctx.Tr("org.teams.delete_team_success")) } @@ -642,7 +642,7 @@ func TeamInvitePost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, ctx.Doer, org, team, "User %s was added to team %s/%s.", ctx.Doer.Name, org.Name, team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, ctx.Doer, org, team, "Added user %s to team %s/%s.", ctx.Doer.Name, org.Name, team.Name) if err := org_model.RemoveInviteByID(ctx, invite.ID, team.ID); err != nil { log.Error("RemoveInviteByID: %v", err) diff --git a/routers/web/repo/setting/audit.go b/routers/web/repo/setting/audit.go new file mode 100644 index 0000000000000..4cc2ebba874e6 --- /dev/null +++ b/routers/web/repo/setting/audit.go @@ -0,0 +1,55 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "net/http" + + audit_model "code.gitea.io/gitea/models/audit" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/audit" +) + +const ( + tplAuditLogs base.TplName = "repo/settings/audit_logs" +) + +func ViewAuditLogs(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("admin.monitor.audit.title") + ctx.Data["PageIsSettingsAudit"] = true + + page := ctx.FormInt("page") + if page < 1 { + page = 1 + } + + opts := &audit_model.EventSearchOptions{ + ScopeType: audit_model.TypeRepository, + ScopeID: ctx.Repo.Repository.ID, + Sort: ctx.FormString("sort"), + Paginator: &db.ListOptions{ + Page: page, + PageSize: setting.UI.Admin.NoticePagingNum, + }, + } + + ctx.Data["AuditSort"] = opts.Sort + + evs, total, err := audit.FindEvents(ctx, opts) + if err != nil { + ctx.ServerError("", err) + return + } + + ctx.Data["AuditEvents"] = evs + + pager := context.NewPagination(int(total), setting.UI.Admin.NoticePagingNum, page, 5) + pager.AddParamString("sort", opts.Sort) + ctx.Data["Page"] = pager + + ctx.HTML(http.StatusOK, tplAuditLogs) +} diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index 99823dfff5ed5..2fefe08c7cf1f 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -81,7 +81,7 @@ func AccountPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserPassword, ctx.Doer, ctx.Doer, ctx.Doer, "Password of user %s changed.", ctx.Doer.Name) + audit.Record(ctx, audit_model.UserPassword, ctx.Doer, ctx.Doer, ctx.Doer, "Changed password of user %s.", ctx.Doer.Name) log.Trace("User password updated: %s", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.change_password_success")) @@ -219,7 +219,7 @@ func EmailPost(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("settings.add_email_success")) } - audit.Record(ctx, audit_model.UserEmailAdd, ctx.Doer, ctx.Doer, email, "Email %s added to user %s.", email.Email, ctx.Doer.Name) + audit.Record(ctx, audit_model.UserEmailAdd, ctx.Doer, ctx.Doer, email, "Added email %s to user %s.", email.Email, ctx.Doer.Name) log.Trace("Email address added: %s", email.Email) ctx.Redirect(setting.AppSubURL + "/user/settings/account") @@ -238,7 +238,7 @@ func DeleteEmail(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserEmailRemove, ctx.Doer, ctx.Doer, email, "Email %s removed from user %s.", email.Email, ctx.Doer.Name) + audit.Record(ctx, audit_model.UserEmailRemove, ctx.Doer, ctx.Doer, email, "Removed email %s from user %s.", email.Email, ctx.Doer.Name) log.Trace("Email address deleted: %s", ctx.Doer.Name) diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index f6e4671f31349..a70e70f9ec19b 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -128,7 +128,7 @@ func ProfilePost(ctx *context.Context) { audit.Record(ctx, audit_model.UserUpdate, ctx.Doer, ctx.Doer, ctx.Doer, "Updated settings of user %s.", ctx.Doer.Name) if oldVisibility != ctx.Doer.Visibility { - audit.Record(ctx, audit_model.UserVisibility, ctx.Doer, ctx.Doer, ctx.Doer, "Visibility of user %s changed from %s to %s.", ctx.Doer.Name, oldVisibility.String(), ctx.Doer.Visibility.String()) + audit.Record(ctx, audit_model.UserVisibility, ctx.Doer, ctx.Doer, ctx.Doer, "Changed visibility of user %s from %s to %s.", ctx.Doer.Name, oldVisibility.String(), ctx.Doer.Visibility.String()) } ctx.Flash.Success(ctx.Tr("settings.update_profile_success")) diff --git a/routers/web/user/setting/security/2fa.go b/routers/web/user/setting/security/2fa.go index a3e7fe6db5a50..3fa056c5fce45 100644 --- a/routers/web/user/setting/security/2fa.go +++ b/routers/web/user/setting/security/2fa.go @@ -51,7 +51,7 @@ func RegenerateScratchTwoFactor(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserTwoFactorRegenerate, ctx.Doer, ctx.Doer, t, "User %s regenerated two-factor authentication secret.", ctx.Doer.Name) + audit.Record(ctx, audit_model.UserTwoFactorRegenerate, ctx.Doer, ctx.Doer, t, "Regenerated two-factor authentication secret for user %s.", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.twofa_scratch_token_regenerated", token)) ctx.Redirect(setting.AppSubURL + "/user/settings/security") @@ -82,7 +82,7 @@ func DisableTwoFactor(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserTwoFactorDisable, ctx.Doer, ctx.Doer, t, "User %s disabled two-factor authentication.", ctx.Doer.Name) + audit.Record(ctx, audit_model.UserTwoFactorDisable, ctx.Doer, ctx.Doer, t, "Disabled two-factor authentication for user %s.", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.twofa_disabled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") @@ -250,7 +250,7 @@ func EnrollTwoFactorPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserTwoFactorEnable, ctx.Doer, ctx.Doer, t, "User %s enabled two-factor authentication.", ctx.Doer.Name) + audit.Record(ctx, audit_model.UserTwoFactorEnable, ctx.Doer, ctx.Doer, ctx.Doer, "Enabled two-factor authentication for user %s.", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.twofa_enrolled", token)) ctx.Redirect(setting.AppSubURL + "/user/settings/security") diff --git a/routers/web/user/setting/security/openid.go b/routers/web/user/setting/security/openid.go index 3782167af8b0d..7ab01fc1431c0 100644 --- a/routers/web/user/setting/security/openid.go +++ b/routers/web/user/setting/security/openid.go @@ -123,7 +123,7 @@ func DeleteOpenID(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserOpenIDAdd, ctx.Doer, ctx.Doer, oid, "Removed OpenID %s from user %s.", oid.URI, ctx.Doer.Name) + audit.Record(ctx, audit_model.UserOpenIDRemove, ctx.Doer, ctx.Doer, oid, "Removed OpenID %s from user %s.", oid.URI, ctx.Doer.Name) log.Trace("OpenID address deleted: %s", ctx.Doer.Name) diff --git a/routers/web/user/setting/security/webauthn.go b/routers/web/user/setting/security/webauthn.go index ddf8a24d500ce..d5a0f628dc323 100644 --- a/routers/web/user/setting/security/webauthn.go +++ b/routers/web/user/setting/security/webauthn.go @@ -108,7 +108,7 @@ func WebauthnRegisterPost(ctx *context.Context) { } _ = ctx.Session.Delete("webauthnName") - audit.Record(ctx, audit_model.UserWebAuthAdd, ctx.Doer, ctx.Doer, dbCred, "User %s added WebAuthn key %s.", ctx.Doer.Name, dbCred.Name) + audit.Record(ctx, audit_model.UserWebAuthAdd, ctx.Doer, ctx.Doer, dbCred, "Added WebAuthn key %s for user %s.", dbCred.Name, ctx.Doer.Name) ctx.JSON(http.StatusCreated, cred) } @@ -127,7 +127,7 @@ func WebauthnDelete(ctx *context.Context) { ctx.ServerError("DeleteCredential", err) return } else if ok { - audit.Record(ctx, audit_model.UserWebAuthRemove, ctx.Doer, ctx.Doer, cred, "User %s removed WebAuthn key %s.", ctx.Doer.Name, cred.Name) + audit.Record(ctx, audit_model.UserWebAuthRemove, ctx.Doer, ctx.Doer, cred, "Removed WebAuthn key %s from user %s.", cred.Name, ctx.Doer.Name) } ctx.JSONRedirect(setting.AppSubURL + "/user/settings/security") diff --git a/routers/web/web.go b/routers/web/web.go index f8b745fb10b55..193552a9f1ded 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -669,6 +669,7 @@ func registerRoutes(m *web.Route) { }) m.Group("/monitor", func() { + m.Get("/audit_logs", admin.ViewAuditLogs) m.Get("/stats", admin.MonitorStats) m.Get("/cron", admin.CronTasks) m.Get("/stacktrace", admin.Stacktrace) @@ -886,6 +887,8 @@ func registerRoutes(m *web.Route) { addSettingVariablesRoutes() }, actions.MustEnableActions) + m.Get("/audit_logs", org_setting.ViewAuditLogs) + m.Methods("GET,POST", "/delete", org.SettingsDelete) m.Group("/packages", func() { @@ -1064,6 +1067,7 @@ func registerRoutes(m *web.Route) { addSettingsSecretsRoutes() addSettingVariablesRoutes() }, actions.MustEnableActions) + m.Get("/audit_logs", repo_setting.ViewAuditLogs) // the follow handler must be under "settings", otherwise this incomplete repo can't be accessed m.Group("/migrate", func() { m.Post("/retry", repo.MigrateRetryPost) diff --git a/services/audit/audit.go b/services/audit/audit.go index c349553ab7728..84db3ed598b25 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -9,7 +9,6 @@ import ( "net" "time" - "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" @@ -25,10 +24,66 @@ import ( ) type TypeDescriptor struct { - Type audit_model.ObjectType `json:"type"` - ID int64 `json:"id"` - DisplayName string `json:"display_name"` - Object any `json:"-"` + Type audit_model.ObjectType `json:"type"` + ID int64 `json:"id"` + Object any `json:"-"` +} + +func (d TypeDescriptor) DisplayName() string { + switch t := d.Object.(type) { + case *repository_model.Repository: + return t.FullName() + case *user_model.User: + return t.Name + case *organization_model.Organization: + return t.Name + case *user_model.EmailAddress: + return t.Email + case *organization_model.Team: + return t.Name + case *auth_model.WebAuthnCredential: + return t.Name + case *user_model.UserOpenID: + return t.URI + case *auth_model.AccessToken: + return t.Name + case *auth_model.OAuth2Application: + return t.Name + case *auth_model.Source: + return t.Name + case *asymkey_model.PublicKey: + return t.Fingerprint + case *asymkey_model.GPGKey: + return t.KeyID + case *secret_model.Secret: + return t.Name + case *webhook_model.Webhook: + return t.URL + case *git_model.ProtectedTag: + return t.NamePattern + case *git_model.ProtectedBranch: + return t.RuleName + case *repository_model.PushMirror: + return t.RemoteAddress + } + + if d.Type == audit_model.TypeSystem { + return "System" + } + + return "" +} + +func (d TypeDescriptor) HTMLURL() string { + switch t := d.Object.(type) { + case *repository_model.Repository: + return t.HTMLURL() + case *user_model.User: + return t.HTMLURL() + case *organization_model.Organization: + return t.HTMLURL() + } + return "" } type Event struct { @@ -78,7 +133,7 @@ func BuildEvent(ctx context.Context, action audit_model.Action, actor *user_mode func scopeToDescription(scope any) TypeDescriptor { if scope == nil { - return TypeDescriptor{audit_model.TypeSystem, 0, "System", nil} + return TypeDescriptor{audit_model.TypeSystem, 0, nil} } switch s := scope.(type) { @@ -92,48 +147,42 @@ func scopeToDescription(scope any) TypeDescriptor { func typeToDescription(val any) TypeDescriptor { switch t := val.(type) { case *repository_model.Repository: - return TypeDescriptor{audit_model.TypeRepository, t.ID, t.FullName(), val} + return TypeDescriptor{audit_model.TypeRepository, t.ID, val} case *user_model.User: if t.IsOrganization() { - return TypeDescriptor{audit_model.TypeOrganization, t.ID, t.Name, val} + return TypeDescriptor{audit_model.TypeOrganization, t.ID, val} } - return TypeDescriptor{audit_model.TypeUser, t.ID, t.Name, val} + return TypeDescriptor{audit_model.TypeUser, t.ID, val} case *organization_model.Organization: - return TypeDescriptor{audit_model.TypeOrganization, t.ID, t.Name, val} + return TypeDescriptor{audit_model.TypeOrganization, t.ID, val} case *user_model.EmailAddress: - return TypeDescriptor{audit_model.TypeEmailAddress, t.ID, t.Email, val} + return TypeDescriptor{audit_model.TypeEmailAddress, t.ID, val} case *organization_model.Team: - return TypeDescriptor{audit_model.TypeTeam, t.ID, t.Name, val} - case *auth_model.TwoFactor: - return TypeDescriptor{audit_model.TypeTwoFactor, t.ID, "", val} + return TypeDescriptor{audit_model.TypeTeam, t.ID, val} case *auth_model.WebAuthnCredential: - return TypeDescriptor{audit_model.TypeWebAuthnCredential, t.ID, t.Name, val} + return TypeDescriptor{audit_model.TypeWebAuthnCredential, t.ID, val} case *user_model.UserOpenID: - return TypeDescriptor{audit_model.TypeOpenID, t.ID, t.URI, val} + return TypeDescriptor{audit_model.TypeOpenID, t.ID, val} case *auth_model.AccessToken: - return TypeDescriptor{audit_model.TypeAccessToken, t.ID, t.Name, val} + return TypeDescriptor{audit_model.TypeAccessToken, t.ID, val} case *auth_model.OAuth2Application: - return TypeDescriptor{audit_model.TypeOAuth2Application, t.ID, t.Name, val} - case *auth_model.OAuth2Grant: - return TypeDescriptor{audit_model.TypeOAuth2Grant, t.ID, "", val} + return TypeDescriptor{audit_model.TypeOAuth2Application, t.ID, val} case *auth_model.Source: - return TypeDescriptor{audit_model.TypeAuthenticationSource, t.ID, t.Name, val} + return TypeDescriptor{audit_model.TypeAuthenticationSource, t.ID, val} case *asymkey_model.PublicKey: - return TypeDescriptor{audit_model.TypePublicKey, t.ID, t.Fingerprint, val} + return TypeDescriptor{audit_model.TypePublicKey, t.ID, val} case *asymkey_model.GPGKey: - return TypeDescriptor{audit_model.TypeGPGKey, t.ID, t.KeyID, val} + return TypeDescriptor{audit_model.TypeGPGKey, t.ID, val} case *secret_model.Secret: - return TypeDescriptor{audit_model.TypeSecret, t.ID, t.Name, val} + return TypeDescriptor{audit_model.TypeSecret, t.ID, val} case *webhook_model.Webhook: - return TypeDescriptor{audit_model.TypeWebhook, t.ID, t.URL, val} + return TypeDescriptor{audit_model.TypeWebhook, t.ID, val} case *git_model.ProtectedTag: - return TypeDescriptor{audit_model.TypeProtectedTag, t.ID, t.NamePattern, val} + return TypeDescriptor{audit_model.TypeProtectedTag, t.ID, val} case *git_model.ProtectedBranch: - return TypeDescriptor{audit_model.TypeProtectedBranch, t.ID, t.RuleName, val} + return TypeDescriptor{audit_model.TypeProtectedBranch, t.ID, val} case *repository_model.PushMirror: - return TypeDescriptor{audit_model.TypePushMirror, t.ID, t.RemoteAddress, val} - case *models.RepoTransfer: - return TypeDescriptor{audit_model.TypeRepoTransfer, t.ID, "", val} + return TypeDescriptor{audit_model.TypePushMirror, t.ID, val} default: panic(fmt.Sprintf("unsupported type: %T", t)) } diff --git a/services/audit/audit_test.go b/services/audit/audit_test.go index d38387dae8a9a..a42fcfc08d55e 100644 --- a/services/audit/audit_test.go +++ b/services/audit/audit_test.go @@ -9,7 +9,6 @@ import ( "testing" "time" - "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" @@ -19,6 +18,7 @@ import ( secret_model "code.gitea.io/gitea/models/secret" user_model "code.gitea.io/gitea/models/user" webhook_model "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web/middleware" "github.com/stretchr/testify/assert" @@ -42,9 +42,9 @@ func TestBuildEvent(t *testing.T) { equal( &Event{ Action: audit_model.UserUpdate, - Doer: TypeDescriptor{Type: "user", PrimaryKey: int64(2), FriendlyName: "Doer", Target: doer}, - Scope: TypeDescriptor{Type: "user", PrimaryKey: int64(1), FriendlyName: "TestUser", Target: u}, - Target: TypeDescriptor{Type: "user", PrimaryKey: int64(1), FriendlyName: "TestUser", Target: u}, + 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: "Updated settings of user TestUser.", }, BuildEvent( @@ -60,9 +60,9 @@ func TestBuildEvent(t *testing.T) { equal( &Event{ Action: audit_model.RepositoryMirrorPushAdd, - Doer: TypeDescriptor{Type: "user", PrimaryKey: int64(2), FriendlyName: "Doer", Target: doer}, - Scope: TypeDescriptor{Type: "repository", PrimaryKey: int64(3), FriendlyName: "TestUser/TestRepo", Target: r}, - Target: TypeDescriptor{Type: "push_mirror", PrimaryKey: int64(4), FriendlyName: "", Target: m}, + 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.", }, BuildEvent( @@ -93,23 +93,23 @@ func TestScopeToDescription(t *testing.T) { }{ { Scope: nil, - Expected: TypeDescriptor{Type: audit_model.TypeSystem, PrimaryKey: 0, FriendlyName: "System"}, + Expected: TypeDescriptor{Type: audit_model.TypeSystem, ID: 0}, }, { Scope: &user_model.User{ID: 1, Name: "TestUser"}, - Expected: TypeDescriptor{Type: audit_model.TypeUser, PrimaryKey: int64(1), FriendlyName: "TestUser"}, + Expected: TypeDescriptor{Type: audit_model.TypeUser, ID: 1}, }, { Scope: &organization_model.Organization{ID: 2, Name: "TestOrg"}, - Expected: TypeDescriptor{Type: audit_model.TypeOrganization, PrimaryKey: int64(2), FriendlyName: "TestOrg"}, + Expected: TypeDescriptor{Type: audit_model.TypeOrganization, ID: 2}, }, { Scope: &repository_model.Repository{ID: 3, Name: "TestRepo", OwnerName: "TestUser"}, - Expected: TypeDescriptor{Type: audit_model.TypeRepository, PrimaryKey: int64(3), FriendlyName: "TestUser/TestRepo"}, + Expected: TypeDescriptor{Type: audit_model.TypeRepository, ID: 3}, }, { ShouldPanic: true, - Scope: &organization_model.Team{ID: 345, Name: "Repo345"}, + Scope: &organization_model.Team{ID: 345, Name: "Team"}, }, { ShouldPanic: true, @@ -117,7 +117,7 @@ func TestScopeToDescription(t *testing.T) { }, } for _, c := range cases { - c.Expected.Target = c.Scope + c.Expected.Object = c.Scope if c.ShouldPanic { assert.Panics(t, func() { @@ -127,101 +127,151 @@ func TestScopeToDescription(t *testing.T) { assert.Equal(t, c.Expected, scopeToDescription(c.Scope), "Unexpected descriptor for scope: %T", c.Scope) } } + + systemScope := scopeToDescription(nil) + assert.Equal(t, "System", systemScope.DisplayName()) + assert.Empty(t, systemScope.HTMLURL()) } func TestTypeToDescription(t *testing.T) { + setting.AppURL = "http://localhost:3000/" + + type Expected struct { + TypeDescriptor TypeDescriptor + DisplayName string + HTMLURL string + } + cases := []struct { ShouldPanic bool Type any - Expected TypeDescriptor + Expected Expected }{ { ShouldPanic: true, Type: nil, }, { - Type: &user_model.User{ID: 1, Name: "TestUser"}, - Expected: TypeDescriptor{Type: audit_model.TypeUser, PrimaryKey: int64(1), FriendlyName: "TestUser"}, - }, - { - Type: &organization_model.Organization{ID: 2, Name: "TestOrg"}, - Expected: TypeDescriptor{Type: audit_model.TypeOrganization, PrimaryKey: int64(2), FriendlyName: "TestOrg"}, - }, - { - Type: &user_model.EmailAddress{ID: 3, Email: "user@gitea.com"}, - Expected: TypeDescriptor{Type: audit_model.TypeEmailAddress, PrimaryKey: int64(3), FriendlyName: "user@gitea.com"}, + Type: &user_model.User{ID: 1, Name: "TestUser"}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypeUser, ID: 1}, + DisplayName: "TestUser", + HTMLURL: "http://localhost:3000/TestUser", + }, }, { - Type: &repository_model.Repository{ID: 3, Name: "TestRepo", OwnerName: "TestUser"}, - Expected: TypeDescriptor{Type: audit_model.TypeRepository, PrimaryKey: int64(3), FriendlyName: "TestUser/TestRepo"}, + Type: &organization_model.Organization{ID: 2, Name: "TestOrg"}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypeOrganization, ID: 2}, + DisplayName: "TestOrg", + HTMLURL: "http://localhost:3000/TestOrg", + }, }, { - Type: &organization_model.Team{ID: 4, Name: "TestTeam"}, - Expected: TypeDescriptor{Type: audit_model.TypeTeam, PrimaryKey: int64(4), FriendlyName: "TestTeam"}, + Type: &user_model.EmailAddress{ID: 3, Email: "user@gitea.com"}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypeEmailAddress, ID: 3}, + DisplayName: "user@gitea.com", + }, }, { - Type: &auth_model.TwoFactor{ID: 5}, - Expected: TypeDescriptor{Type: audit_model.TypeTwoFactor, PrimaryKey: int64(5), FriendlyName: ""}, + Type: &repository_model.Repository{ID: 3, Name: "TestRepo", OwnerName: "TestUser"}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypeRepository, ID: 3}, + DisplayName: "TestUser/TestRepo", + HTMLURL: "http://localhost:3000/TestUser/TestRepo", + }, }, { - Type: &auth_model.WebAuthnCredential{ID: 6, Name: "TestCredential"}, - Expected: TypeDescriptor{Type: audit_model.TypeWebAuthnCredential, PrimaryKey: int64(6), FriendlyName: "TestCredential"}, + Type: &organization_model.Team{ID: 4, Name: "TestTeam"}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypeTeam, ID: 4}, + DisplayName: "TestTeam", + }, }, { - Type: &user_model.UserOpenID{ID: 7, URI: "test://uri"}, - Expected: TypeDescriptor{Type: audit_model.TypeOpenID, PrimaryKey: int64(7), FriendlyName: "test://uri"}, + Type: &auth_model.WebAuthnCredential{ID: 6, Name: "TestCredential"}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypeWebAuthnCredential, ID: 6}, + DisplayName: "TestCredential", + }, }, { - Type: &auth_model.AccessToken{ID: 8, Name: "TestToken"}, - Expected: TypeDescriptor{Type: audit_model.TypeAccessToken, PrimaryKey: int64(8), FriendlyName: "TestToken"}, + Type: &user_model.UserOpenID{ID: 7, URI: "test://uri"}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypeOpenID, ID: 7}, + DisplayName: "test://uri", + }, }, { - Type: &auth_model.OAuth2Application{ID: 9, Name: "TestOAuth2Application"}, - Expected: TypeDescriptor{Type: audit_model.TypeOAuth2Application, PrimaryKey: int64(9), FriendlyName: "TestOAuth2Application"}, + Type: &auth_model.AccessToken{ID: 8, Name: "TestToken"}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypeAccessToken, ID: 8}, + DisplayName: "TestToken", + }, }, { - Type: &auth_model.OAuth2Grant{ID: 10}, - Expected: TypeDescriptor{Type: audit_model.TypeOAuth2Grant, PrimaryKey: int64(10), FriendlyName: ""}, + Type: &auth_model.OAuth2Application{ID: 9, Name: "TestOAuth2Application"}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypeOAuth2Application, ID: 9}, + DisplayName: "TestOAuth2Application", + }, }, { - Type: &auth_model.Source{ID: 11, Name: "TestSource"}, - Expected: TypeDescriptor{Type: audit_model.TypeAuthenticationSource, PrimaryKey: int64(11), FriendlyName: "TestSource"}, + Type: &auth_model.Source{ID: 11, Name: "TestSource"}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypeAuthenticationSource, ID: 11}, + DisplayName: "TestSource", + }, }, - /*{ - Type: &user_model.ExternalLoginUser{ExternalID: "12"}, - Expected: TypeDescriptor{Type: audit_model.TypeExternalLoginUser, PrimaryKey: "12", FriendlyName: "12"}, - },*/ { - Type: &asymkey_model.PublicKey{ID: 13, Fingerprint: "TestPublicKey"}, - Expected: TypeDescriptor{Type: audit_model.TypePublicKey, PrimaryKey: int64(13), FriendlyName: "TestPublicKey"}, + Type: &asymkey_model.PublicKey{ID: 13, Fingerprint: "TestPublicKey"}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypePublicKey, ID: 13}, + DisplayName: "TestPublicKey", + }, }, { - Type: &asymkey_model.GPGKey{ID: 14, KeyID: "TestGPGKey"}, - Expected: TypeDescriptor{Type: audit_model.TypeGPGKey, PrimaryKey: int64(14), FriendlyName: "TestGPGKey"}, + Type: &asymkey_model.GPGKey{ID: 14, KeyID: "TestGPGKey"}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypeGPGKey, ID: 14}, + DisplayName: "TestGPGKey", + }, }, { - Type: &secret_model.Secret{ID: 15, Name: "TestSecret"}, - Expected: TypeDescriptor{Type: audit_model.TypeSecret, PrimaryKey: int64(15), FriendlyName: "TestSecret"}, + Type: &secret_model.Secret{ID: 15, Name: "TestSecret"}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypeSecret, ID: 15}, + DisplayName: "TestSecret", + }, }, { - Type: &webhook_model.Webhook{ID: 16, URL: "test://webhook"}, - Expected: TypeDescriptor{Type: audit_model.TypeWebhook, PrimaryKey: int64(16), FriendlyName: "test://webhook"}, + Type: &webhook_model.Webhook{ID: 16, URL: "test://webhook"}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypeWebhook, ID: 16}, + DisplayName: "test://webhook", + }, }, { - Type: &git_model.ProtectedTag{ID: 17, NamePattern: "TestProtectedTag"}, - Expected: TypeDescriptor{Type: audit_model.TypeProtectedTag, PrimaryKey: int64(17), FriendlyName: "TestProtectedTag"}, + Type: &git_model.ProtectedTag{ID: 17, NamePattern: "TestProtectedTag"}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypeProtectedTag, ID: 17}, + DisplayName: "TestProtectedTag", + }, }, { - Type: &git_model.ProtectedBranch{ID: 18, RuleName: "TestProtectedBranch"}, - Expected: TypeDescriptor{Type: audit_model.TypeProtectedBranch, PrimaryKey: int64(18), FriendlyName: "TestProtectedBranch"}, + Type: &git_model.ProtectedBranch{ID: 18, RuleName: "TestProtectedBranch"}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypeProtectedBranch, ID: 18}, + DisplayName: "TestProtectedBranch", + }, }, { - Type: &repository_model.PushMirror{ID: 19}, - Expected: TypeDescriptor{Type: audit_model.TypePushMirror, PrimaryKey: int64(19), FriendlyName: ""}, - }, - { - Type: &models.RepoTransfer{ID: 20}, - Expected: TypeDescriptor{Type: audit_model.TypeRepoTransfer, PrimaryKey: int64(20), FriendlyName: ""}, + Type: &repository_model.PushMirror{ID: 19}, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypePushMirror, ID: 19}, + DisplayName: "", + }, }, { ShouldPanic: true, @@ -229,14 +279,18 @@ func TestTypeToDescription(t *testing.T) { }, } for _, c := range cases { - c.Expected.Target = c.Type + c.Expected.TypeDescriptor.Object = c.Type if c.ShouldPanic { assert.Panics(t, func() { _ = typeToDescription(c.Type) }) } else { - assert.Equal(t, c.Expected, typeToDescription(c.Type), "Unexpected descriptor for type: %T", c.Type) + d := typeToDescription(c.Type) + + assert.Equal(t, c.Expected.TypeDescriptor, d, "Unexpected descriptor for type: %T", c.Type) + assert.Equal(t, c.Expected.DisplayName, d.DisplayName(), "Unexpected display name for type: %T", c.Type) + assert.Equal(t, c.Expected.HTMLURL, d.HTMLURL(), "Unexpected url for type: %T", c.Type) } } } diff --git a/services/audit/database.go b/services/audit/database.go index a0fb9954a9542..7a62adce9cf8a 100644 --- a/services/audit/database.go +++ b/services/audit/database.go @@ -7,18 +7,20 @@ import ( "context" audit_model "code.gitea.io/gitea/models/audit" + "code.gitea.io/gitea/modules/timeutil" ) 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, + 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()), }) return err } diff --git a/services/audit/display.go b/services/audit/display.go new file mode 100644 index 0000000000000..be5f5016e2cf8 --- /dev/null +++ b/services/audit/display.go @@ -0,0 +1,117 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package audit + +import ( + "context" + "fmt" + + asymkey_model "code.gitea.io/gitea/models/asymkey" + audit_model "code.gitea.io/gitea/models/audit" + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" + git_model "code.gitea.io/gitea/models/git" + organization_model "code.gitea.io/gitea/models/organization" + repository_model "code.gitea.io/gitea/models/repo" + secret_model "code.gitea.io/gitea/models/secret" + user_model "code.gitea.io/gitea/models/user" + webhook_model "code.gitea.io/gitea/models/webhook" +) + +type cache = map[audit_model.ObjectType]map[int64]TypeDescriptor + +func FindEvents(ctx context.Context, opts *audit_model.EventSearchOptions) ([]*Event, int64, error) { + events, total, err := audit_model.FindEvents(ctx, opts) + if err != nil { + return nil, 0, err + } + + return fromDatabaseEvents(ctx, events), total, nil +} + +func fromDatabaseEvents(ctx context.Context, evs []*audit_model.Event) []*Event { + c := cache{} + + events := make([]*Event, 0, len(evs)) + for _, e := range evs { + events = append(events, fromDatabaseEvent(ctx, e, c)) + } + return events +} + +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, + Time: e.TimestampUnix.AsTime(), + IPAddress: e.IPAddress, + } +} + +func resolveType(ctx context.Context, t audit_model.ObjectType, id int64, c cache) TypeDescriptor { + oc, has := c[t] + if !has { + oc = make(map[int64]TypeDescriptor) + c[t] = oc + } + + td, has := oc[id] + if has { + return td + } + + var bean any + + switch t { + case audit_model.TypeRepository: + bean = &repository_model.Repository{} + case audit_model.TypeUser: + bean = &user_model.User{} + case audit_model.TypeOrganization: + bean = &organization_model.Organization{} + case audit_model.TypeEmailAddress: + bean = &user_model.EmailAddress{} + case audit_model.TypeTeam: + bean = &organization_model.Team{} + case audit_model.TypeWebAuthnCredential: + bean = &auth_model.WebAuthnCredential{} + case audit_model.TypeOpenID: + bean = &user_model.UserOpenID{} + case audit_model.TypeAccessToken: + bean = &auth_model.AccessToken{} + case audit_model.TypeOAuth2Application: + bean = &auth_model.OAuth2Application{} + case audit_model.TypeAuthenticationSource: + bean = &auth_model.Source{} + case audit_model.TypePublicKey: + bean = &asymkey_model.PublicKey{} + case audit_model.TypeGPGKey: + bean = &asymkey_model.GPGKey{} + case audit_model.TypeSecret: + bean = &secret_model.Secret{} + case audit_model.TypeWebhook: + bean = &webhook_model.Webhook{} + case audit_model.TypeProtectedTag: + bean = &git_model.ProtectedTag{} + case audit_model.TypeProtectedBranch: + bean = &git_model.ProtectedBranch{} + case audit_model.TypePushMirror: + bean = &repository_model.PushMirror{} + default: + panic(fmt.Sprintf("unsupported type: %v", t)) + } + + if has, _ = db.GetByID(ctx, id, bean); !has { + td = TypeDescriptor{t, id, nil} + } else { + td = typeToDescription(bean) + } + + oc[id] = td + + return td +} diff --git a/services/audit/file.go b/services/audit/file.go index a2026276d948d..01e31411a32ff 100644 --- a/services/audit/file.go +++ b/services/audit/file.go @@ -6,6 +6,7 @@ package audit import ( "io" + 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/util/rotatingfilewriter" @@ -39,6 +40,20 @@ func writeToFile(e *Event) error { return WriteEventAsJSON(rfw, e) } +func (d TypeDescriptor) MarshalJSON() ([]byte, error) { + type out struct { + Type audit_model.ObjectType `json:"type"` + ID int64 `json:"id"` + DisplayName string `json:"display_name"` + } + + return json.Marshal(out{ + Type: d.Type, + ID: d.ID, + DisplayName: d.DisplayName(), + }) +} + 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 061e9de4d0ea0..daff408692ffb 100644 --- a/services/audit/file_test.go +++ b/services/audit/file_test.go @@ -5,10 +5,12 @@ package audit import ( "context" + "net/http" "strings" "testing" "time" + 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/web/middleware" @@ -25,7 +27,7 @@ func TestWriteEventAsJSON(t *testing.T) { e := BuildEvent( ctx, - RepositoryMirrorPushAdd, + audit_model.RepositoryMirrorPushAdd, doer, r, m, @@ -38,7 +40,7 @@ func TestWriteEventAsJSON(t *testing.T) { assert.NoError(t, WriteEventAsJSON(&sb, e)) assert.Equal( t, - `{"action":"repository:mirror:push:add","doer":{"type":"user","primary_key":2,"friendly_name":"Doer"},"scope":{"type":"repository","primary_key":3,"friendly_name":"TestUser/TestRepo"},"target":{"type":"push_mirror","primary_key":4,"friendly_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":""},"message":"Added push mirror for repository TestUser/TestRepo.","time":"0001-01-01T00:00:00Z","ip_address":"127.0.0.1"}`+"\n", sb.String(), ) } diff --git a/services/auth/basic.go b/services/auth/basic.go index 4c7fca6323f28..73bfe7fe9131d 100644 --- a/services/auth/basic.go +++ b/services/auth/basic.go @@ -157,7 +157,7 @@ func validateTOTP(req *http.Request, u *user_model.User) error { if ok, err := twofa.ValidateTOTP(req.Header.Get("X-Gitea-OTP")); err != nil { return err } else if !ok { - audit.Record(req.Context(), audit_model.UserAuthenticationFailTwoFactor, u, u, twofa, "Failed two-factor authentication for user %s.", u.Name) + audit.Record(req.Context(), audit_model.UserAuthenticationFailTwoFactor, u, u, u, "Failed two-factor authentication for user %s.", u.Name) return util.NewInvalidArgumentErrorf("invalid provided OTP") } diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index 0f5619761ba1c..a8f7baac89793 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -79,10 +79,10 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u if user != nil { if isAdminChanged { - audit.Record(ctx, audit_model.UserAdmin, audit.NewAuthenticationSourceUser(), user, user, "Admin status of user %s changed to %s.", user.Name, audit.UserAdminString(user.IsAdmin)) + audit.Record(ctx, audit_model.UserAdmin, audit.NewAuthenticationSourceUser(), user, user, "Changed admin status of user %s to %s.", user.Name, audit.UserAdminString(user.IsAdmin)) } if isRestrictedChanged { - audit.Record(ctx, audit_model.UserRestricted, audit.NewAuthenticationSourceUser(), user, user, "Restricted status of user %s changed to %s.", user.Name, audit.UserRestrictedString(user.IsRestricted)) + audit.Record(ctx, audit_model.UserRestricted, audit.NewAuthenticationSourceUser(), user, user, "Changed restricted status of user %s to %s.", user.Name, audit.UserRestrictedString(user.IsRestricted)) } if isAttributeSSHPublicKeySet { diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index 4ad4d13d67695..9679f6e207023 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -199,13 +199,13 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { } if isActiveChanged { - audit.Record(ctx, audit_model.UserActive, audit.NewAuthenticationSourceUser(), usr, usr, "Activation status of user %s changed to %s.", usr.Name, audit.UserActiveString(usr.IsActive)) + audit.Record(ctx, audit_model.UserActive, audit.NewAuthenticationSourceUser(), usr, usr, "Changed activation status of user %s to %s.", usr.Name, audit.UserActiveString(usr.IsActive)) } if isAdminChanged { - audit.Record(ctx, audit_model.UserAdmin, audit.NewAuthenticationSourceUser(), usr, usr, "Admin status of user %s changed to %s.", usr.Name, audit.UserAdminString(usr.IsAdmin)) + audit.Record(ctx, audit_model.UserAdmin, audit.NewAuthenticationSourceUser(), usr, usr, "Changed admin status of user %s to %s.", usr.Name, audit.UserAdminString(usr.IsAdmin)) } if isRestrictedChanged { - audit.Record(ctx, audit_model.UserRestricted, audit.NewAuthenticationSourceUser(), usr, usr, "Restricted status of user %s changed to %s.", usr.Name, audit.UserRestrictedString(usr.IsRestricted)) + audit.Record(ctx, audit_model.UserRestricted, audit.NewAuthenticationSourceUser(), usr, usr, "Changed restricted status of user %s to %s.", usr.Name, audit.UserRestrictedString(usr.IsRestricted)) } } @@ -252,7 +252,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { if err := user_model.UpdateUserCols(ctx, usr, "is_active"); err != nil { log.Error("SyncExternalUsers[%s]: Error deactivating user %s: %v", source.authSource.Name, usr.Name, err) } else { - audit.Record(ctx, audit_model.UserActive, audit.NewAuthenticationSourceUser(), usr, usr, "Activation status of user %s changed to %s.", usr.Name, audit.UserActiveString(usr.IsActive)) + audit.Record(ctx, audit_model.UserActive, audit.NewAuthenticationSourceUser(), usr, usr, "Changed activation status of user %s to %s.", usr.Name, audit.UserActiveString(usr.IsActive)) } } } diff --git a/services/auth/source/source_group_sync.go b/services/auth/source/source_group_sync.go index 72ca131e44afd..1331bae109e7a 100644 --- a/services/auth/source/source_group_sync.go +++ b/services/auth/source/source_group_sync.go @@ -107,14 +107,14 @@ func syncGroupsToTeamsCached(ctx context.Context, user *user_model.User, orgTeam return err } - audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, audit.NewAuthenticationSourceUser(), org, team, "User %s was added to team %s/%s.", user.Name, org.Name, team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, audit.NewAuthenticationSourceUser(), org, team, "Added user %s to team %s/%s.", user.Name, org.Name, team.Name) } else if action == syncRemove && isMember { if err := models.RemoveTeamMember(ctx, team, user.ID); err != nil { log.Error("group sync: Could not remove user from team: %v", err) return err } - audit.Record(ctx, audit_model.OrganizationTeamMemberRemove, audit.NewAuthenticationSourceUser(), org, team, "User %s was removed from team %s/%s.", user.Name, org.Name, team.Name) + audit.Record(ctx, audit_model.OrganizationTeamMemberRemove, audit.NewAuthenticationSourceUser(), org, team, "Removed user %s from team %s/%s.", user.Name, org.Name, team.Name) } } } diff --git a/services/org/org.go b/services/org/org.go index 4fe90afdac9be..baf1e094b4a70 100644 --- a/services/org/org.go +++ b/services/org/org.go @@ -58,7 +58,7 @@ func DeleteOrganization(ctx context.Context, doer *user_model.User, org *org_mod return err } - audit.Record(ctx, audit_model.OrganizationDelete, doer, org, org, "Organization %s was deleted.", org.Name) + audit.Record(ctx, audit_model.OrganizationDelete, doer, org, org, "Deleted organization %s.", org.Name) // FIXME: system notice // Note: There are something just cannot be roll back, diff --git a/services/repository/repository.go b/services/repository/repository.go index 99e56aeac9a1f..22f5a5e1944c5 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -74,7 +74,7 @@ func DeleteRepository(ctx context.Context, doer *user_model.User, repo *repo_mod return err } - audit.Record(ctx, audit_model.RepositoryDelete, doer, repo, repo, "Deleted repository %s.", repo.FullName()) // repo.Owner load? + audit.Record(ctx, audit_model.RepositoryDelete, doer, repo, repo, "Deleted repository %s.", repo.FullName()) return nil } diff --git a/services/repository/transfer.go b/services/repository/transfer.go index 8d6bdd969dba2..8b9e94ad7343d 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -84,7 +84,7 @@ func ChangeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo repo.Name = newRepoName - audit.Record(ctx, audit_model.RepositoryName, doer, repo, repo, "Repository name changed from %s to %s.", oldRepoName, newRepoName) + audit.Record(ctx, audit_model.RepositoryName, doer, repo, repo, "Changed repository name from %s to %s.", oldRepoName, newRepoName) notify_service.RenameRepository(ctx, doer, repo, oldRepoName) diff --git a/services/user/user.go b/services/user/user.go index 129beb85f306c..1d338d91b856e 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -121,7 +121,7 @@ func RenameUser(ctx context.Context, doer, u *user_model.User, newUserName strin return err } - audit.Record(ctx, audit_model.UserName, doer, u, u, "User %s changed name to %s.", oldUserName, newUserName) + audit.Record(ctx, audit_model.UserName, doer, u, u, "Changed user name from %s to %s.", oldUserName, newUserName) return nil } @@ -281,7 +281,7 @@ func DeleteUser(ctx context.Context, doer, u *user_model.User, purge bool) error } } - audit.Record(ctx, audit_model.UserDelete, doer, u, u, "User %s was deleted.", u.Name) + audit.Record(ctx, audit_model.UserDelete, doer, u, u, "Deleted user %s.", u.Name) return nil } diff --git a/templates/admin/audit/list.tmpl b/templates/admin/audit/list.tmpl new file mode 100644 index 0000000000000..e965ecaeb513b --- /dev/null +++ b/templates/admin/audit/list.tmpl @@ -0,0 +1,6 @@ +{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin monitor")}} +
+

{{ctx.Locale.Tr "admin.monitor.audit.title"}}

+ {{template "shared/audit/list" .}} +
+{{template "admin/layout_footer" .}} diff --git a/templates/admin/navbar.tmpl b/templates/admin/navbar.tmpl index 8ece95239c1ff..0b79f29a68cc0 100644 --- a/templates/admin/navbar.tmpl +++ b/templates/admin/navbar.tmpl @@ -75,9 +75,14 @@ {{ctx.Locale.Tr "admin.notices"}} -
+
{{ctx.Locale.Tr "admin.monitor"}}
{{end}} + {{if .EnableAuditLogs}} + + {{ctx.Locale.Tr "admin.monitor.audit.title"}} + + {{end}} {{ctx.Locale.Tr "org.settings.delete"}} diff --git a/templates/repo/settings/audit_logs.tmpl b/templates/repo/settings/audit_logs.tmpl new file mode 100644 index 0000000000000..1169e31431518 --- /dev/null +++ b/templates/repo/settings/audit_logs.tmpl @@ -0,0 +1,6 @@ +{{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings")}} +
+

{{ctx.Locale.Tr "admin.monitor.audit.title"}}

+ {{template "shared/audit/list" .}} +
+{{template "repo/settings/layout_footer" .}} diff --git a/templates/repo/settings/navbar.tmpl b/templates/repo/settings/navbar.tmpl index b16eb6076fad5..e7b5fdf9dc23b 100644 --- a/templates/repo/settings/navbar.tmpl +++ b/templates/repo/settings/navbar.tmpl @@ -49,5 +49,10 @@
{{end}} + {{if .EnableAuditLogs}} + + {{ctx.Locale.Tr "admin.monitor.audit.title"}} + + {{end}} diff --git a/templates/shared/audit/list.tmpl b/templates/shared/audit/list.tmpl new file mode 100644 index 0000000000000..f8eecd127a13f --- /dev/null +++ b/templates/shared/audit/list.tmpl @@ -0,0 +1,57 @@ +
+ + + + + + + + + + + + + {{range .AuditEvents}} + + + + + + + + + {{else}} + + {{end}} + +
{{ctx.Locale.Tr "admin.monitor.audit.actor"}}{{ctx.Locale.Tr "admin.monitor.audit.scope"}}{{ctx.Locale.Tr "admin.monitor.audit.action"}}{{ctx.Locale.Tr "admin.monitor.audit.target"}}{{ctx.Locale.Tr "admin.monitor.audit.ip_address"}}{{ctx.Locale.Tr "admin.monitor.audit.timestamp"}}{{SortArrow "timestamp_asc" "timestamp_desc" $.AuditSort true}}
+ {{if .Actor.Object}} + {{.Actor.DisplayName}} + {{else}} + {{ctx.Locale.Tr "admin.monitor.audit.deleted.actor"}} + {{end}} + + {{if or .Scope.Object (eq .Scope.Type "system")}} + {{$url := .Scope.HTMLURL}} + {{if $url}} + {{.Scope.DisplayName}} + {{else}} + {{.Scope.DisplayName}} + {{end}} + {{else}} + {{ctx.Locale.Tr "admin.monitor.audit.deleted.type" .Scope.Type .Scope.ID}} + {{end}} + {{.Message}} + {{if .Target.Object}} + {{$url := .Target.HTMLURL}} + {{if $url}} + {{.Target.DisplayName}} + {{else}} + {{.Target.DisplayName}} + {{end}} + {{else}} + {{ctx.Locale.Tr "admin.monitor.audit.deleted.type" .Target.Type .Target.ID}} + {{end}} + {{.IPAddress}}{{DateTime "full" .Time}}
{{ctx.Locale.Tr "admin.monitor.audit.no_events"}}
+
+{{template "base/paginate" .}} \ No newline at end of file diff --git a/tests/integration/audit_test.go b/tests/integration/audit_test.go index 4e8a100b6dbae..72d87a44bdce7 100644 --- a/tests/integration/audit_test.go +++ b/tests/integration/audit_test.go @@ -4,12 +4,17 @@ package integration import ( - "context" + "fmt" "net/http" "net/url" + "sync/atomic" "testing" + audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/audit" @@ -17,37 +22,19 @@ import ( "github.com/stretchr/testify/assert" ) -type testAppender struct { - Events []*audit.Event -} - -func (a *testAppender) Record(ctx context.Context, e *audit.Event) { - a.Events = append(a.Events, e) -} - -func (a *testAppender) Close() error { - return nil -} - -func (a *testAppender) ReleaseReopen() error { - a.Events = nil - return nil -} - func TestAuditLogging(t *testing.T) { - a := &testAppender{} - audit.TestingOnlyAddAppender(a) - defer audit.TestingOnlyRemoveAppender(a) - onGiteaRun(t, func(*testing.T, *url.URL) { - token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + assert.NoError(t, db.TruncateBeans(db.DefaultContext, &audit_model.Event{})) + + actor := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + token := getUserToken(t, actor.Name, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) req := NewRequestWithJSON(t, "POST", "/api/v1/orgs?token="+token, &api.CreateOrgOption{ UserName: "user1_audit_org", FullName: "User1's organization", Description: "This organization created by user1", Website: "https://try.gitea.io", - Location: "Shanghai", + Location: "Universe", Visibility: "limited", }) MakeRequest(t, req, http.StatusCreated) @@ -55,14 +42,11 @@ func TestAuditLogging(t *testing.T) { req = NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/user1_audit_org?token="+token, &api.EditOrgOption{ Description: "A new description", Website: "https://try.gitea.io/new", - Location: "Beijing", + Location: "Earth", Visibility: "private", }) MakeRequest(t, req, http.StatusOK) - req = NewRequest(t, "DELETE", "/api/v1/orgs/user1_audit_org?token="+token) - MakeRequest(t, req, http.StatusNoContent) - req = NewRequestWithJSON(t, "POST", "/api/v1/user/repos?token="+token, &api.CreateRepoOption{ Name: "audit_repo", }) @@ -79,88 +63,134 @@ func TestAuditLogging(t *testing.T) { }) MakeRequest(t, req, http.StatusCreated) - req = NewRequest(t, "DELETE", "/api/v1/repos/user1/audit_repo?token="+token) - MakeRequest(t, req, http.StatusNoContent) + type TestTypeDescriptor struct { + Type audit_model.ObjectType + DisplayName string + HTMLURL string + } cases := []struct { - Action audit.Action - Scope audit.TypeDescriptor - Target audit.TypeDescriptor + Action audit_model.Action + Scope TestTypeDescriptor + Target TestTypeDescriptor }{ { - Action: audit.UserAccessTokenAdd, - Scope: audit.TypeDescriptor{Type: "user", FriendlyName: "user1"}, - Target: audit.TypeDescriptor{Type: "access_token"}, // can't test name because it depends on other tests + Action: audit_model.UserAccessTokenAdd, + Scope: TestTypeDescriptor{Type: audit_model.TypeUser, DisplayName: "user1", HTMLURL: "http://localhost:3003/user1"}, + Target: TestTypeDescriptor{Type: audit_model.TypeAccessToken, DisplayName: fmt.Sprintf("api-testing-token-%d", atomic.LoadInt64(&tokenCounter))}, }, { - Action: audit.OrganizationCreate, - Scope: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, - Target: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, + Action: audit_model.OrganizationCreate, + Scope: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: "http://localhost:3003/user1_audit_org"}, + Target: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: "http://localhost:3003/user1_audit_org"}, }, { - Action: audit.OrganizationUpdate, - Scope: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, - Target: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, + Action: audit_model.OrganizationUpdate, + Scope: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: "http://localhost:3003/user1_audit_org"}, + Target: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: "http://localhost:3003/user1_audit_org"}, }, { - Action: audit.OrganizationVisibility, - Scope: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, - Target: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, + Action: audit_model.OrganizationVisibility, + Scope: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: "http://localhost:3003/user1_audit_org"}, + Target: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: "http://localhost:3003/user1_audit_org"}, }, { - Action: audit.OrganizationDelete, - Scope: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, - Target: audit.TypeDescriptor{Type: "organization", FriendlyName: "user1_audit_org"}, + Action: audit_model.RepositoryCreate, + Scope: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: "http://localhost:3003/user1/audit_repo"}, + Target: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: "http://localhost:3003/user1/audit_repo"}, }, { - Action: audit.RepositoryCreate, - Scope: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, - Target: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, + Action: audit_model.RepositoryUpdate, + Scope: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: "http://localhost:3003/user1/audit_repo"}, + Target: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: "http://localhost:3003/user1/audit_repo"}, }, { - Action: audit.RepositoryUpdate, - Scope: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, - Target: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, + Action: audit_model.RepositoryVisibility, + Scope: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: "http://localhost:3003/user1/audit_repo"}, + Target: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: "http://localhost:3003/user1/audit_repo"}, }, { - Action: audit.RepositoryVisibility, - Scope: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, - Target: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, + Action: audit_model.UserSecretAdd, + Scope: TestTypeDescriptor{Type: audit_model.TypeUser, DisplayName: "user1", HTMLURL: "http://localhost:3003/user1"}, + Target: TestTypeDescriptor{Type: audit_model.TypeSecret, DisplayName: "AUDIT_SECRET"}, }, + } + + events, total, err := audit.FindEvents(db.DefaultContext, &audit_model.EventSearchOptions{Sort: audit_model.SortTimestampAsc}) + assert.NoError(t, err) + assert.EqualValues(t, len(cases), total) + assert.Len(t, events, int(total)) + + for i, c := range cases { + e := events[i] + + assert.Equal(t, c.Action, e.Action) + + assert.Equal(t, audit_model.TypeUser, e.Actor.Type) + assert.NotNil(t, e.Actor.Object) + assert.Equal(t, actor.ID, e.Actor.ID) + + assert.Equal(t, c.Scope.Type, e.Scope.Type) + assert.NotNil(t, e.Scope.Object) + assert.Equal(t, c.Scope.DisplayName, e.Scope.DisplayName()) + assert.Equal(t, c.Scope.HTMLURL, e.Scope.HTMLURL()) + + assert.Equal(t, c.Target.Type, e.Target.Type) + assert.NotNil(t, e.Target.Object) + assert.Equal(t, c.Target.DisplayName, e.Target.DisplayName()) + assert.Equal(t, c.Target.HTMLURL, e.Target.HTMLURL()) + } + + // Deleted objects don't have display names anymore + + assert.NoError(t, db.TruncateBeans(db.DefaultContext, &audit_model.Event{})) + + req = NewRequest(t, "DELETE", "/api/v1/orgs/user1_audit_org?token="+token) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequest(t, "DELETE", "/api/v1/repos/user1/audit_repo?token="+token) + MakeRequest(t, req, http.StatusNoContent) + + cases = []struct { + Action audit_model.Action + Scope TestTypeDescriptor + Target TestTypeDescriptor + }{ { - Action: audit.UserSecretAdd, - Scope: audit.TypeDescriptor{Type: "user", FriendlyName: "user1"}, - Target: audit.TypeDescriptor{Type: "secret", FriendlyName: "AUDIT_SECRET"}, + Action: audit_model.OrganizationDelete, + Scope: TestTypeDescriptor{Type: audit_model.TypeOrganization}, + Target: TestTypeDescriptor{Type: audit_model.TypeOrganization}, }, { - Action: audit.RepositoryDelete, - Scope: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, - Target: audit.TypeDescriptor{Type: "repository", FriendlyName: "user1/audit_repo"}, + Action: audit_model.RepositoryDelete, + Scope: TestTypeDescriptor{Type: audit_model.TypeRepository}, + Target: TestTypeDescriptor{Type: audit_model.TypeRepository}, }, } - assert.Len(t, a.Events, len(cases)) + events, total, err = audit.FindEvents(db.DefaultContext, &audit_model.EventSearchOptions{Sort: audit_model.SortTimestampAsc}) + assert.NoError(t, err) + assert.EqualValues(t, len(cases), total) + assert.Len(t, events, int(total)) + for i, c := range cases { - e := a.Events[i] + e := events[i] assert.Equal(t, c.Action, e.Action) - assert.Equal(t, "user", e.Doer.Type) - assert.EqualValues(t, int64(1), e.Doer.PrimaryKey) - - // Can't test PrimaryKey because it depends on other tests + assert.Equal(t, audit_model.TypeUser, e.Actor.Type) + assert.NotNil(t, e.Actor.Object) + assert.Equal(t, actor.ID, e.Actor.ID) assert.Equal(t, c.Scope.Type, e.Scope.Type) - if c.Scope.FriendlyName != "" { - assert.Equal(t, c.Scope.FriendlyName, e.Scope.FriendlyName) - } + assert.Nil(t, e.Scope.Object) + assert.Empty(t, e.Scope.DisplayName()) + assert.Empty(t, e.Scope.HTMLURL()) assert.Equal(t, c.Target.Type, e.Target.Type) - if c.Target.FriendlyName != "" { - assert.Equal(t, c.Target.FriendlyName, e.Target.FriendlyName) - } + assert.Nil(t, e.Target.Object) + assert.Empty(t, e.Target.DisplayName()) + assert.Empty(t, e.Target.HTMLURL()) } }) - - audit.ReleaseReopen() } From af5b9b6fed9b553cac54c7e722755c3669313f16 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Tue, 21 Nov 2023 20:12:26 +0000 Subject: [PATCH 31/44] fix lint --- cmd/admin_auth_ldap.go | 8 ++++++-- cmd/admin_user_create.go | 4 +++- cmd/admin_user_delete.go | 4 +++- cmd/admin_user_generate_access_token.go | 4 +++- models/audit/audit_event.go | 2 +- routers/init.go | 2 +- routers/web/user/setting/security/security.go | 10 +++++----- templates/shared/audit/list.tmpl | 2 +- 8 files changed, 23 insertions(+), 13 deletions(-) diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index 8722166ae03a9..a5d0d8045b06e 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -339,7 +339,9 @@ func (a *authService) addLdapSource(c *cli.Context, authType auth.Type, args ... if err := a.initDB(ctx); err != nil { return err } - audit.Init() + if err := audit.Init(); err != nil { + return err + } authSource := &auth.Source{ Type: authType, @@ -370,7 +372,9 @@ func (a *authService) updateLdapSource(c *cli.Context, authType auth.Type) error if err := a.initDB(ctx); err != nil { return err } - audit.Init() + if err := audit.Init(); err != nil { + return err + } authSource, err := a.getAuthSource(ctx, c, authType) if err != nil { diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 202af7d484549..650a4d63556f7 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -97,7 +97,9 @@ func runCreateUser(c *cli.Context) error { if err := initDB(ctx); err != nil { return err } - audit.Init() + if err := audit.Init(); err != nil { + return err + } var password string if c.IsSet("password") { diff --git a/cmd/admin_user_delete.go b/cmd/admin_user_delete.go index e988dd7f2d616..4bbe291c49c82 100644 --- a/cmd/admin_user_delete.go +++ b/cmd/admin_user_delete.go @@ -52,7 +52,9 @@ func runDeleteUser(c *cli.Context) error { if err := initDB(ctx); err != nil { return err } - audit.Init() + if err := audit.Init(); err != nil { + return err + } if err := storage.Init(); err != nil { return err } diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go index e7451ff0415a4..0ad2e6631a870 100644 --- a/cmd/admin_user_generate_access_token.go +++ b/cmd/admin_user_generate_access_token.go @@ -53,7 +53,9 @@ func runGenerateAccessToken(c *cli.Context) error { if err := initDB(ctx); err != nil { return err } - audit.Init() + if err := audit.Init(); err != nil { + return err + } user, err := user_model.GetUserByName(ctx, c.String("username")) if err != nil { diff --git a/models/audit/audit_event.go b/models/audit/audit_event.go index 75aa2ab1c15e0..18278b3f86bd3 100644 --- a/models/audit/audit_event.go +++ b/models/audit/audit_event.go @@ -29,7 +29,7 @@ type Event struct { TimestampUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL"` } -func (_ *Event) TableName() string { +func (*Event) TableName() string { return "audit_event" } diff --git a/routers/init.go b/routers/init.go index a810ea1d36fdf..ac5a051950a90 100644 --- a/routers/init.go +++ b/routers/init.go @@ -164,7 +164,7 @@ func InitWebInstalled(ctx context.Context) { actions_service.Init() - audit.Init() + mustInit(audit.Init) // Finally start up the cron cron.NewContext(ctx) diff --git a/routers/web/user/setting/security/security.go b/routers/web/user/setting/security/security.go index a4ffda00da4b7..fa8a6bd370bee 100644 --- a/routers/web/user/setting/security/security.go +++ b/routers/web/user/setting/security/security.go @@ -51,14 +51,14 @@ func DeleteAccountLink(ctx *context.Context) { } if _, err := user_model.RemoveAccountLink(ctx, ctx.Doer, elu.LoginSourceID); err != nil { - ctx.Flash.Error("RemoveAccountLink: " + err.Error()) + ctx.ServerError("RemoveAccountLink", err) return - } else { - audit.Record(ctx, audit_model.UserExternalLoginRemove, ctx.Doer, ctx.Doer, ctx.Doer, "Removed external login %s for user %s.", elu.ExternalID, ctx.Doer.Name) - - ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success")) } + audit.Record(ctx, audit_model.UserExternalLoginRemove, ctx.Doer, ctx.Doer, ctx.Doer, "Removed external login %s for user %s.", elu.ExternalID, ctx.Doer.Name) + + ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success")) + ctx.JSONRedirect(setting.AppSubURL + "/user/settings/security") } diff --git a/templates/shared/audit/list.tmpl b/templates/shared/audit/list.tmpl index f8eecd127a13f..6f42f959e20db 100644 --- a/templates/shared/audit/list.tmpl +++ b/templates/shared/audit/list.tmpl @@ -54,4 +54,4 @@ -{{template "base/paginate" .}} \ No newline at end of file +{{template "base/paginate" .}} From fa8d53015346b0357325b7f7e0889cbc2de4254a Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sun, 26 Nov 2023 21:36:02 +0000 Subject: [PATCH 32/44] lint --- templates/shared/audit/list.tmpl | 2 +- tests/integration/audit_test.go | 29 +++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/templates/shared/audit/list.tmpl b/templates/shared/audit/list.tmpl index 6f42f959e20db..88fc3fe44eb5a 100644 --- a/templates/shared/audit/list.tmpl +++ b/templates/shared/audit/list.tmpl @@ -17,7 +17,7 @@ {{if .Actor.Object}} {{.Actor.DisplayName}} {{else}} - {{ctx.Locale.Tr "admin.monitor.audit.deleted.actor"}} + {{ctx.Locale.Tr "admin.monitor.audit.deleted.actor"}} {{end}} diff --git a/tests/integration/audit_test.go b/tests/integration/audit_test.go index 5ee52ef923463..cad54dc9a58a4 100644 --- a/tests/integration/audit_test.go +++ b/tests/integration/audit_test.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/audit" @@ -77,42 +78,42 @@ func TestAuditLogging(t *testing.T) { }{ { Action: audit_model.UserAccessTokenAdd, - Scope: TestTypeDescriptor{Type: audit_model.TypeUser, DisplayName: "user1", HTMLURL: "http://localhost:3003/user1"}, + Scope: TestTypeDescriptor{Type: audit_model.TypeUser, DisplayName: "user1", HTMLURL: setting.AppURL + "user1"}, Target: TestTypeDescriptor{Type: audit_model.TypeAccessToken, DisplayName: fmt.Sprintf("api-testing-token-%d", atomic.LoadInt64(&tokenCounter))}, }, { Action: audit_model.OrganizationCreate, - Scope: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: "http://localhost:3003/user1_audit_org"}, - Target: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: "http://localhost:3003/user1_audit_org"}, + Scope: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: setting.AppURL + "user1_audit_org"}, + Target: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: setting.AppURL + "user1_audit_org"}, }, { Action: audit_model.OrganizationUpdate, - Scope: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: "http://localhost:3003/user1_audit_org"}, - Target: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: "http://localhost:3003/user1_audit_org"}, + Scope: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: setting.AppURL + "user1_audit_org"}, + Target: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: setting.AppURL + "user1_audit_org"}, }, { Action: audit_model.OrganizationVisibility, - Scope: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: "http://localhost:3003/user1_audit_org"}, - Target: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: "http://localhost:3003/user1_audit_org"}, + Scope: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: setting.AppURL + "user1_audit_org"}, + Target: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: setting.AppURL + "user1_audit_org"}, }, { Action: audit_model.RepositoryCreate, - Scope: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: "http://localhost:3003/user1/audit_repo"}, - Target: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: "http://localhost:3003/user1/audit_repo"}, + Scope: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: setting.AppURL + "user1/audit_repo"}, + Target: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: setting.AppURL + "user1/audit_repo"}, }, { Action: audit_model.RepositoryUpdate, - Scope: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: "http://localhost:3003/user1/audit_repo"}, - Target: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: "http://localhost:3003/user1/audit_repo"}, + Scope: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: setting.AppURL + "user1/audit_repo"}, + Target: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: setting.AppURL + "user1/audit_repo"}, }, { Action: audit_model.RepositoryVisibility, - Scope: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: "http://localhost:3003/user1/audit_repo"}, - Target: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: "http://localhost:3003/user1/audit_repo"}, + Scope: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: setting.AppURL + "user1/audit_repo"}, + Target: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: setting.AppURL + "user1/audit_repo"}, }, { Action: audit_model.UserSecretAdd, - Scope: TestTypeDescriptor{Type: audit_model.TypeUser, DisplayName: "user1", HTMLURL: "http://localhost:3003/user1"}, + Scope: TestTypeDescriptor{Type: audit_model.TypeUser, DisplayName: "user1", HTMLURL: setting.AppURL + "user1"}, Target: TestTypeDescriptor{Type: audit_model.TypeSecret, DisplayName: "AUDIT_SECRET"}, }, } From ec55854bfd65275ce6857dde68247f292cf9f99c Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 29 Dec 2023 18:05:27 +0000 Subject: [PATCH 33/44] Add some suggestions. --- .../administration/audit-logging.en-us.md | 1 + models/asymkey/ssh_key.go | 8 +-- models/audit/action.go | 1 + models/db/context.go | 5 -- models/user/openid.go | 17 ++++++ models/user/user.go | 54 ++++++++++++------- models/user/user_test.go | 31 ++++++----- modules/setting/audit.go | 2 +- routers/api/v1/admin/user.go | 15 +++++- routers/api/v1/user/app.go | 4 +- routers/api/v1/user/settings.go | 2 +- routers/web/admin/users.go | 11 +++- routers/web/auth/auth.go | 8 +-- routers/web/auth/oauth.go | 2 +- routers/web/org/setting.go | 2 +- routers/web/user/setting/keys.go | 21 +++++--- routers/web/user/setting/security/openid.go | 6 +-- routers/web/user/setting/security/security.go | 10 ++-- services/audit/display.go | 48 +++++++++-------- services/auth/source/ldap/source_sync.go | 10 +++- tests/integration/audit_test.go | 26 ++++----- 21 files changed, 175 insertions(+), 109 deletions(-) diff --git a/docs/content/administration/audit-logging.en-us.md b/docs/content/administration/audit-logging.en-us.md index faaacf0a82a38..b527fd9344f6c 100644 --- a/docs/content/administration/audit-logging.en-us.md +++ b/docs/content/administration/audit-logging.en-us.md @@ -51,6 +51,7 @@ Audit events are grouped by `user`, `organization`, `repository` and `system`. | `user:password` | Changed password of user | | `user:password:reset` | Requested a password reset | | `user:visibility` | Changed visibility of user | +| `user:email:primary` | Changed primary email of user | | `user:email:add` | Added email to user | | `user:email:activate` | Activated email of user | | `user:email:remove` | Removed email from user | diff --git a/models/asymkey/ssh_key.go b/models/asymkey/ssh_key.go index ae021bb4d187f..1155f719c9bd0 100644 --- a/models/asymkey/ssh_key.go +++ b/models/asymkey/ssh_key.go @@ -367,8 +367,8 @@ func AddPublicKeysBySource(ctx context.Context, usr *user_model.User, s *auth.So return addedKeys } -// SynchronizePublicKeys updates a users public keys. Returns true if there are changes. -func SynchronizePublicKeys(ctx context.Context, usr *user_model.User, s *auth.Source, sshPublicKeys []string) ([]*PublicKey, []*PublicKey) { +// SynchronizePublicKeys updates a users public keys. Returns the updated keys. +func SynchronizePublicKeys(ctx context.Context, usr *user_model.User, s *auth.Source, sshPublicKeys []string) (addedKeys, deletedKeys []*PublicKey) { log.Trace("synchronizePublicKeys[%s]: Handling Public SSH Key synchronization for user %s", s.Name, usr.Name) // Get Public Keys from DB with current LDAP source @@ -412,7 +412,7 @@ func SynchronizePublicKeys(ctx context.Context, usr *user_model.User, s *auth.So } } - addedKeys := AddPublicKeysBySource(ctx, usr, s, newKeys) + addedKeys = AddPublicKeysBySource(ctx, usr, s, newKeys) // Mark keys from DB that no longer exist in the source for deletion var giteaKeysToDelete []string @@ -424,7 +424,7 @@ func SynchronizePublicKeys(ctx context.Context, usr *user_model.User, s *auth.So } // Delete keys from DB that no longer exist in the source - deletedKeys, err := deleteKeysMarkedForDeletion(ctx, giteaKeysToDelete) + deletedKeys, err = deleteKeysMarkedForDeletion(ctx, giteaKeysToDelete) if err != nil { log.Error("synchronizePublicKeys[%s]: Error deleting Public Keys marked for deletion for user %s: %v", s.Name, usr.Name, err) } diff --git a/models/audit/action.go b/models/audit/action.go index 4571cc481f511..f4455a07efb09 100644 --- a/models/audit/action.go +++ b/models/audit/action.go @@ -19,6 +19,7 @@ const ( UserPassword Action = "user:password" UserPasswordReset Action = "user:password:reset" UserVisibility Action = "user:visibility" + UserEmailPrimaryChange Action = "user:email:primary" UserEmailAdd Action = "user:email:add" UserEmailActivate Action = "user:email:activate" UserEmailRemove Action = "user:email:remove" diff --git a/models/db/context.go b/models/db/context.go index a5c88ce06b760..cda608af19669 100644 --- a/models/db/context.go +++ b/models/db/context.go @@ -238,11 +238,6 @@ func Delete[T any](ctx context.Context, opts FindOptions) (int64, error) { return GetEngine(ctx).Where(opts.ToConds()).NoAutoCondition().NoAutoTime().Delete(&bean) } -// GetByID retrieves the bean with the given ID (given that all non-empty fields match) -func GetByID(ctx context.Context, id, bean any) (bool, error) { - return GetEngine(ctx).ID(id).Get(bean) -} - // DeleteByBean deletes all records according non-empty fields of the bean as conditions. func DeleteByBean(ctx context.Context, bean any) (int64, error) { return GetEngine(ctx).Delete(bean) diff --git a/models/user/openid.go b/models/user/openid.go index ee4ecabae0b76..e084e71b02702 100644 --- a/models/user/openid.go +++ b/models/user/openid.go @@ -9,6 +9,8 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/util" + + "xorm.io/builder" ) // ErrOpenIDNotExist openid is not known @@ -40,6 +42,21 @@ func GetUserOpenIDs(ctx context.Context, uid int64) ([]*UserOpenID, error) { return openids, nil } +func GetUserOpenID(ctx context.Context, id, uid int64) (*UserOpenID, error) { + openid, has, err := db.Get[UserOpenID](ctx, builder.Eq{ + "id": id, + "uid": uid, + }) + + if err != nil { + return nil, err + } else if !has { + return nil, util.ErrNotExist + } + + return openid, nil +} + // isOpenIDUsed returns true if the openid has been used. func isOpenIDUsed(ctx context.Context, uri string) (bool, error) { if len(uri) == 0 { diff --git a/models/user/user.go b/models/user/user.go index ce0e055b15c66..15a9396236eae 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -796,7 +796,7 @@ func ValidateUser(u *User, cols ...string) error { } // UpdateUser updates user's information. -func UpdateUser(ctx context.Context, u *User, changePrimaryEmail bool, cols ...string) error { +func UpdateUser(ctx context.Context, u *User, cols ...string) error { err := ValidateUser(u, cols...) if err != nil { return err @@ -804,14 +804,31 @@ func UpdateUser(ctx context.Context, u *User, changePrimaryEmail bool, cols ...s e := db.GetEngine(ctx) + if len(cols) == 0 { + _, err = e.ID(u.ID).AllCols().Update(u) + } else { + _, err = e.ID(u.ID).Cols(cols...).Update(u) + } + return err +} + +func UpdateOrSetPrimaryEmail(ctx context.Context, u *User, changePrimaryEmail bool) (*EmailAddress, error) { + u.Email = strings.ToLower(u.Email) + if err := ValidateEmail(u.Email); err != nil { + return nil, err + } + + var emailAddress *EmailAddress + + e := db.GetEngine(ctx) + if changePrimaryEmail { - var emailAddress EmailAddress - has, err := e.Where("lower_email=?", strings.ToLower(u.Email)).Get(&emailAddress) + has, err := e.Where("lower_email=?", strings.ToLower(u.Email)).Get(emailAddress) if err != nil { - return err + return nil, err } if has && emailAddress.UID != u.ID { - return ErrEmailAlreadyUsed{ + return nil, ErrEmailAlreadyUsed{ Email: u.Email, } } @@ -819,7 +836,7 @@ func UpdateUser(ctx context.Context, u *User, changePrimaryEmail bool, cols ...s if _, err = e.Where("uid=? AND is_primary=?", u.ID, true).Cols("is_primary").Update(&EmailAddress{ IsPrimary: false, }); err != nil { - return err + return nil, err } if !has { @@ -827,38 +844,35 @@ func UpdateUser(ctx context.Context, u *User, changePrimaryEmail bool, cols ...s emailAddress.UID = u.ID emailAddress.IsActivated = true emailAddress.IsPrimary = true - if _, err := e.Insert(&emailAddress); err != nil { - return err + if _, err := e.Insert(emailAddress); err != nil { + return nil, err } } else if _, err := e.ID(emailAddress.ID).Cols("is_primary").Update(&EmailAddress{ IsPrimary: true, }); err != nil { - return err + return nil, err } } else if !u.IsOrganization() { // check if primary email in email_address table primaryEmailExist, err := e.Where("uid=? AND is_primary=?", u.ID, true).Exist(&EmailAddress{}) if err != nil { - return err + return nil, err } if !primaryEmailExist { - if _, err := e.Insert(&EmailAddress{ + emailAddress = &EmailAddress{ Email: u.Email, UID: u.ID, IsActivated: true, IsPrimary: true, - }); err != nil { - return err + } + if _, err := e.Insert(emailAddress); err != nil { + return nil, err } } } - if len(cols) == 0 { - _, err = e.ID(u.ID).AllCols().Update(u) - } else { - _, err = e.ID(u.ID).Cols(cols...).Update(u) - } - return err + _, err := e.ID(u.ID).Cols("email").Update(u) + return emailAddress, err } // UpdateUserCols update user according special columns @@ -884,7 +898,7 @@ func UpdateUserSetting(ctx context.Context, u *User) (err error) { return err } } - if err = UpdateUser(ctx, u, false); err != nil { + if err = UpdateUser(ctx, u); err != nil { return err } return committer.Commit() diff --git a/models/user/user_test.go b/models/user/user_test.go index 971117482c2d4..5fbe1451d9400 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -348,35 +348,40 @@ func TestUpdateUser(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user.KeepActivityPrivate = true - assert.NoError(t, user_model.UpdateUser(db.DefaultContext, user, false)) + assert.NoError(t, user_model.UpdateUser(db.DefaultContext, user)) user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) assert.True(t, user.KeepActivityPrivate) setting.Service.AllowedUserVisibilityModesSlice = []bool{true, false, false} user.KeepActivityPrivate = false user.Visibility = structs.VisibleTypePrivate - assert.Error(t, user_model.UpdateUser(db.DefaultContext, user, false)) + assert.Error(t, user_model.UpdateUser(db.DefaultContext, user)) user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) assert.True(t, user.KeepActivityPrivate) - - newEmail := "new_" + user.Email - user.Email = newEmail - assert.NoError(t, user_model.UpdateUser(db.DefaultContext, user, true)) - user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - assert.Equal(t, newEmail, user.Email) - - user.Email = "no mail@mail.org" - assert.Error(t, user_model.UpdateUser(db.DefaultContext, user, true)) } -func TestUpdateUserEmailAlreadyUsed(t *testing.T) { +func TestUpdateOrSetPrimaryEmail(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) + newEmail := "new_" + user2.Email + user2.Email = newEmail + obj, err := user_model.UpdateOrSetPrimaryEmail(db.DefaultContext, user2, true) + assert.NoError(t, err) + assert.NotNil(t, obj) + user2 = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + assert.Equal(t, newEmail, user2.Email) + + user2.Email = "no mail@mail.org" + obj, err = user_model.UpdateOrSetPrimaryEmail(db.DefaultContext, user2, true) + assert.Error(t, err) + assert.NotNil(t, obj) + user2.Email = org3.Email - err := user_model.UpdateUser(db.DefaultContext, user2, true) + obj, err = user_model.UpdateOrSetPrimaryEmail(db.DefaultContext, user2, true) assert.True(t, user_model.IsErrEmailAlreadyUsed(err)) + assert.Nil(t, obj) } func TestNewUserRedirect(t *testing.T) { diff --git a/modules/setting/audit.go b/modules/setting/audit.go index 33c261a278ec4..ef6298610c584 100644 --- a/modules/setting/audit.go +++ b/modules/setting/audit.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package setting diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 18be257a0dcfd..329555837a79e 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -244,6 +244,8 @@ func EditUser(ctx *context.APIContext) { ctx.InternalServerError(err) return } + + passwordChanged = true } if form.MustChangePassword != nil { @@ -308,20 +310,29 @@ func EditUser(ctx *context.APIContext) { ctx.ContextUser.IsRestricted = *form.Restricted } - if err := user_model.UpdateUser(ctx, ctx.ContextUser, emailChanged); err != nil { + emailAddress, err := user_model.UpdateOrSetPrimaryEmail(ctx, ctx.ContextUser, emailChanged) + if err != nil { if user_model.IsErrEmailAlreadyUsed(err) || user_model.IsErrEmailCharIsNotSupported(err) || user_model.IsErrEmailInvalid(err) { ctx.Error(http.StatusUnprocessableEntity, "", err) } else { - ctx.Error(http.StatusInternalServerError, "UpdateUser", err) + ctx.Error(http.StatusInternalServerError, "UpdateOrSetPrimaryEmail", err) } return } + if err := user_model.UpdateUser(ctx, ctx.ContextUser); err != nil { + ctx.Error(http.StatusInternalServerError, "UpdateUser", err) + return + } + if passwordChanged { audit.Record(ctx, audit_model.UserPassword, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Changed password of user %s.", ctx.ContextUser.Name) } + if emailChanged { + audit.Record(ctx, audit_model.UserEmailPrimaryChange, ctx.Doer, ctx.Doer, emailAddress, "Changed primary email of user %s to %s.", ctx.ContextUser.Name, emailAddress.Email) + } if auditFields.LoginSource != ctx.ContextUser.LoginSource { audit.Record(ctx, audit_model.UserAuthenticationSource, ctx.Doer, ctx.ContextUser, ctx.ContextUser, "Changed authentication source of user %s.", ctx.ContextUser.Name) } diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index edb09b0940fae..bbed96803ad30 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -191,8 +191,8 @@ func DeleteAccessToken(ctx *context.APIContext) { return } } else { - t = &auth_model.AccessToken{} - _, err := db.GetByID(ctx, tokenID, t) + var err error + t, _, err = db.GetByID[auth_model.AccessToken](ctx, tokenID) if err != nil { ctx.Error(http.StatusInternalServerError, "GetByID", err) return diff --git a/routers/api/v1/user/settings.go b/routers/api/v1/user/settings.go index 53794c82f8385..0662e2757cec7 100644 --- a/routers/api/v1/user/settings.go +++ b/routers/api/v1/user/settings.go @@ -73,7 +73,7 @@ func UpdateUserSettings(ctx *context.APIContext) { ctx.Doer.KeepActivityPrivate = *form.HideActivity } - if err := user_model.UpdateUser(ctx, ctx.Doer, false); err != nil { + if err := user_model.UpdateUser(ctx, ctx.Doer); err != nil { ctx.InternalServerError(err) return } diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 735e5747e95e9..56062657a6db6 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -485,7 +485,8 @@ func EditUserPost(ctx *context.Context) { u.ProhibitLogin = form.ProhibitLogin } - if err := user_model.UpdateUser(ctx, u, emailChanged); err != nil { + emailAddress, err := user_model.UpdateOrSetPrimaryEmail(ctx, u, emailChanged) + if err != nil { if user_model.IsErrEmailAlreadyUsed(err) { ctx.Data["Err_Email"] = true ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserEdit, &form) @@ -499,9 +500,17 @@ func EditUserPost(ctx *context.Context) { return } + if err := user_model.UpdateUser(ctx, u); err != nil { + ctx.ServerError("UpdateUser", err) + return + } + if passwordChanged { audit.Record(ctx, audit_model.UserPassword, ctx.Doer, u, u, "Changed password of user %s.", u.Name) } + if emailChanged { + audit.Record(ctx, audit_model.UserEmailPrimaryChange, ctx.Doer, u, emailAddress, "Changed primary email of user %s to %s.", u.Name, emailAddress.Email) + } if auditFields.LoginSource != u.LoginSource { audit.Record(ctx, audit_model.UserAuthenticationSource, ctx.Doer, u, u, "Changed authentication source of user %s.", u.Name) } diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 39ca476780f51..3dc8d4d0a25a8 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -790,12 +790,8 @@ func ActivateEmail(ctx *context.Context) { log.Trace("Email activated: %s", email.Email) ctx.Flash.Success(ctx.Tr("settings.add_email_success")) - if u, err := user_model.GetUserByID(ctx, email.UID); err != nil { - log.Warn("GetUserByID: %d", email.UID) - } else { - // Allow user to validate more emails - _ = ctx.Cache.Delete("MailResendLimit_" + user.LowerName) - } + // Allow user to validate more emails + _ = ctx.Cache.Delete("MailResendLimit_" + user.LowerName) audit.Record(ctx, audit_model.UserEmailActivate, user, user, email, "Activated email %s of user %s.", email.Email, user.Name) } diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 7c65b3258110e..3ce1c025e0c38 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -1065,7 +1065,7 @@ func getClaimedGroups(source *oauth2.Source, gothUser *goth.User) container.Set[ return claimValueToStringSet(groupClaims) } -func setUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, u *user_model.User, gothUser *goth.User) (bool, bool) { +func setUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, u *user_model.User, gothUser *goth.User) (adminChanged, restrictedChanged bool) { groups := getClaimedGroups(source, gothUser) wasAdmin, wasRestricted := u.IsAdmin, u.IsRestricted diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 1cf157e8151bd..f4d8e736416cd 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -121,7 +121,7 @@ func SettingsPost(ctx *context.Context) { oldVisibility := org.Visibility org.Visibility = form.Visibility - if err := user_model.UpdateUser(ctx, org.AsUser(), false); err != nil { + if err := user_model.UpdateUser(ctx, org.AsUser()); err != nil { ctx.ServerError("UpdateUser", err) return } diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index b761f58b7d3da..dbed642218418 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -241,21 +241,26 @@ func DeleteKey(ctx *context.Context) { case "gpg": key, err := asymkey_model.GetGPGKeyForUserByID(ctx, ctx.Doer.ID, ctx.FormInt64("id")) if err != nil && !asymkey_model.IsErrGPGKeyNotExist(err) { - ctx.Flash.Error("GetGPGKeyForUserByID: " + err.Error()) - } else { + ctx.ServerError("GetGPGKeyForUserByID", err) + return + } + if key != nil { if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, key.ID); err != nil { - ctx.Flash.Error("DeleteGPGKey: " + err.Error()) - } else { - audit.Record(ctx, audit_model.UserKeyGPGRemove, ctx.Doer, ctx.Doer, key, "Removed GPG key %s.", key.KeyID) - - ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success")) + ctx.ServerError("DeleteGPGKey", err) + return } + + audit.Record(ctx, audit_model.UserKeyGPGRemove, ctx.Doer, ctx.Doer, key, "Removed GPG key %s.", key.KeyID) + + ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success")) + } else { + ctx.Flash.Error(ctx.Tr("error.occurred")) } case "ssh": keyID := ctx.FormInt64("id") external, err := asymkey_model.PublicKeyIsExternallyManaged(ctx, keyID) if err != nil { - ctx.ServerError("sshKeysExternalManaged", err) + ctx.ServerError("PublicKeyIsExternallyManaged", err) return } if external { diff --git a/routers/web/user/setting/security/openid.go b/routers/web/user/setting/security/openid.go index 7ab01fc1431c0..d127397422e6a 100644 --- a/routers/web/user/setting/security/openid.go +++ b/routers/web/user/setting/security/openid.go @@ -7,7 +7,6 @@ import ( "net/http" audit_model "code.gitea.io/gitea/models/audit" - "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/openid" "code.gitea.io/gitea/modules/context" @@ -111,10 +110,9 @@ func settingsOpenIDVerify(ctx *context.Context) { // DeleteOpenID response for delete user's openid func DeleteOpenID(ctx *context.Context) { - oid := &user_model.UserOpenID{UID: ctx.Doer.ID} - _, err := db.GetByID(ctx, ctx.FormInt64("id"), oid) + oid, err := user_model.GetUserOpenID(ctx, ctx.FormInt64("id"), ctx.Doer.ID) if err != nil { - ctx.ServerError("GetByID", err) + ctx.ServerError("GetUserOpenID", err) return } diff --git a/routers/web/user/setting/security/security.go b/routers/web/user/setting/security/security.go index 31aef658d4746..199c8b0aa6026 100644 --- a/routers/web/user/setting/security/security.go +++ b/routers/web/user/setting/security/security.go @@ -42,21 +42,21 @@ func Security(ctx *context.Context) { // DeleteAccountLink delete a single account link func DeleteAccountLink(ctx *context.Context) { - elu := &user_model.ExternalLoginUser{UserID: ctx.Doer.ID, LoginSourceID: ctx.FormInt64("id")} - if has, err := user_model.GetExternalLogin(ctx, elu); err != nil || !has { + user := &user_model.ExternalLoginUser{UserID: ctx.Doer.ID, LoginSourceID: ctx.FormInt64("id")} + if has, err := user_model.GetExternalLogin(ctx, user); err != nil || !has { if !has { - err = user_model.ErrExternalLoginUserNotExist{UserID: elu.UserID, LoginSourceID: elu.LoginSourceID} + err = user_model.ErrExternalLoginUserNotExist{UserID: user.UserID, LoginSourceID: user.LoginSourceID} } ctx.ServerError("RemoveAccountLink", err) return } - if _, err := user_model.RemoveAccountLink(ctx, ctx.Doer, elu.LoginSourceID); err != nil { + if _, err := user_model.RemoveAccountLink(ctx, ctx.Doer, user.LoginSourceID); err != nil { ctx.ServerError("RemoveAccountLink", err) return } - audit.Record(ctx, audit_model.UserExternalLoginRemove, ctx.Doer, ctx.Doer, ctx.Doer, "Removed external login %s for user %s.", elu.ExternalID, ctx.Doer.Name) + audit.Record(ctx, audit_model.UserExternalLoginRemove, ctx.Doer, ctx.Doer, ctx.Doer, "Removed external login %s for user %s.", user.ExternalID, ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success")) diff --git a/services/audit/display.go b/services/audit/display.go index be5f5016e2cf8..a9f139e6bac6f 100644 --- a/services/audit/display.go +++ b/services/audit/display.go @@ -64,54 +64,58 @@ func resolveType(ctx context.Context, t audit_model.ObjectType, id int64, c cach return td } - var bean any - switch t { case audit_model.TypeRepository: - bean = &repository_model.Repository{} + td, has = getTypeDescriptorByID[repository_model.Repository](ctx, id) case audit_model.TypeUser: - bean = &user_model.User{} + td, has = getTypeDescriptorByID[user_model.User](ctx, id) case audit_model.TypeOrganization: - bean = &organization_model.Organization{} + td, has = getTypeDescriptorByID[organization_model.Organization](ctx, id) case audit_model.TypeEmailAddress: - bean = &user_model.EmailAddress{} + td, has = getTypeDescriptorByID[user_model.EmailAddress](ctx, id) case audit_model.TypeTeam: - bean = &organization_model.Team{} + td, has = getTypeDescriptorByID[organization_model.Team](ctx, id) case audit_model.TypeWebAuthnCredential: - bean = &auth_model.WebAuthnCredential{} + td, has = getTypeDescriptorByID[auth_model.WebAuthnCredential](ctx, id) case audit_model.TypeOpenID: - bean = &user_model.UserOpenID{} + td, has = getTypeDescriptorByID[user_model.UserOpenID](ctx, id) case audit_model.TypeAccessToken: - bean = &auth_model.AccessToken{} + td, has = getTypeDescriptorByID[auth_model.AccessToken](ctx, id) case audit_model.TypeOAuth2Application: - bean = &auth_model.OAuth2Application{} + td, has = getTypeDescriptorByID[auth_model.OAuth2Application](ctx, id) case audit_model.TypeAuthenticationSource: - bean = &auth_model.Source{} + td, has = getTypeDescriptorByID[auth_model.Source](ctx, id) case audit_model.TypePublicKey: - bean = &asymkey_model.PublicKey{} + td, has = getTypeDescriptorByID[asymkey_model.PublicKey](ctx, id) case audit_model.TypeGPGKey: - bean = &asymkey_model.GPGKey{} + td, has = getTypeDescriptorByID[asymkey_model.GPGKey](ctx, id) case audit_model.TypeSecret: - bean = &secret_model.Secret{} + td, has = getTypeDescriptorByID[secret_model.Secret](ctx, id) case audit_model.TypeWebhook: - bean = &webhook_model.Webhook{} + td, has = getTypeDescriptorByID[webhook_model.Webhook](ctx, id) case audit_model.TypeProtectedTag: - bean = &git_model.ProtectedTag{} + td, has = getTypeDescriptorByID[git_model.ProtectedTag](ctx, id) case audit_model.TypeProtectedBranch: - bean = &git_model.ProtectedBranch{} + td, has = getTypeDescriptorByID[git_model.ProtectedBranch](ctx, id) case audit_model.TypePushMirror: - bean = &repository_model.PushMirror{} + td, has = getTypeDescriptorByID[repository_model.PushMirror](ctx, id) default: panic(fmt.Sprintf("unsupported type: %v", t)) } - if has, _ = db.GetByID(ctx, id, bean); !has { + if !has { td = TypeDescriptor{t, id, nil} - } else { - td = typeToDescription(bean) } oc[id] = td return td } + +func getTypeDescriptorByID[T any](ctx context.Context, id int64) (TypeDescriptor, bool) { + if bean, has, _ := db.GetByID[T](ctx, id); has { + return typeToDescription(bean), true + } + + return TypeDescriptor{}, false +} diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index 9679f6e207023..1e7c3d274ef70 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -193,11 +193,19 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { isActiveChanged := !usr.IsActive usr.IsActive = true - err = user_model.UpdateUser(ctx, usr, emailChanged, "full_name", "email", "is_admin", "is_restricted", "is_active") + emailAddress, err := user_model.UpdateOrSetPrimaryEmail(ctx, usr, emailChanged) + if err != nil { + log.Error("SyncExternalUsers[%s]: Error updating user primary email %s: %v", source.authSource.Name, usr.Name, err) + } + + err = user_model.UpdateUser(ctx, usr, "full_name", "email", "is_admin", "is_restricted", "is_active") if err != nil { log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", source.authSource.Name, usr.Name, err) } + if emailChanged { + audit.Record(ctx, audit_model.UserEmailPrimaryChange, audit.NewAuthenticationSourceUser(), usr, emailAddress, "Changed primary email of user %s to %s.", usr.Name, emailAddress.Email) + } if isActiveChanged { audit.Record(ctx, audit_model.UserActive, audit.NewAuthenticationSourceUser(), usr, usr, "Changed activation status of user %s to %s.", usr.Name, audit.UserActiveString(usr.IsActive)) } diff --git a/tests/integration/audit_test.go b/tests/integration/audit_test.go index cad54dc9a58a4..1731a38f9b547 100644 --- a/tests/integration/audit_test.go +++ b/tests/integration/audit_test.go @@ -31,38 +31,38 @@ func TestAuditLogging(t *testing.T) { actor := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) token := getUserToken(t, actor.Name, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - req := NewRequestWithJSON(t, "POST", "/api/v1/orgs?token="+token, &api.CreateOrgOption{ + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &api.CreateOrgOption{ UserName: "user1_audit_org", FullName: "User1's organization", Description: "This organization created by user1", Website: "https://try.gitea.io", Location: "Universe", Visibility: "limited", - }) + }).AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) - req = NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/user1_audit_org?token="+token, &api.EditOrgOption{ + req = NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/user1_audit_org", &api.EditOrgOption{ Description: "A new description", Website: "https://try.gitea.io/new", Location: "Earth", Visibility: "private", - }) + }).AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) - req = NewRequestWithJSON(t, "POST", "/api/v1/user/repos?token="+token, &api.CreateRepoOption{ + req = NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{ Name: "audit_repo", - }) + }).AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) - req = NewRequestWithJSON(t, "PATCH", "/api/v1/repos/user1/audit_repo?token="+token, &api.EditRepoOption{ + req = NewRequestWithJSON(t, "PATCH", "/api/v1/repos/user1/audit_repo", &api.EditRepoOption{ Description: util.ToPointer("A new description"), Private: util.ToPointer(true), - }) + }).AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) - req = NewRequestWithJSON(t, "PUT", "/api/v1/repos/user1/audit_repo/actions/secrets/audit_secret?token="+token, &api.CreateOrUpdateSecretOption{ + req = NewRequestWithJSON(t, "PUT", "/api/v1/repos/user1/audit_repo/actions/secrets/audit_secret", &api.CreateOrUpdateSecretOption{ Data: "my secret", - }) + }).AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) type TestTypeDescriptor struct { @@ -147,10 +147,12 @@ func TestAuditLogging(t *testing.T) { assert.NoError(t, db.TruncateBeans(db.DefaultContext, &audit_model.Event{})) - req = NewRequest(t, "DELETE", "/api/v1/orgs/user1_audit_org?token="+token) + req = NewRequest(t, "DELETE", "/api/v1/orgs/user1_audit_org"). + AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "DELETE", "/api/v1/repos/user1/audit_repo?token="+token) + req = NewRequest(t, "DELETE", "/api/v1/repos/user1/audit_repo"). + AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) cases = []struct { From 6fffaac112983d509e782f30d6627a30e9c57ef6 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Tue, 6 Feb 2024 22:13:00 +0100 Subject: [PATCH 34/44] Use individual methods. --- cmd/admin_auth.go | 5 +- cmd/admin_auth_ldap.go | 5 +- cmd/admin_user_create.go | 5 +- cmd/admin_user_generate_access_token.go | 3 +- models/audit/action.go | 4 +- models/audit/audit_event.go | 2 +- models/audit/types.go | 2 +- models/migrations/v1_22/v287.go | 2 +- models/user/email_address.go | 26 +- modules/setting/audit.go | 2 +- routers/api/v1/admin/org.go | 3 +- routers/api/v1/admin/user.go | 3 +- routers/api/v1/api.go | 3 +- routers/api/v1/org/org.go | 3 +- routers/api/v1/org/team.go | 14 +- routers/api/v1/repo/collaborators.go | 7 +- routers/api/v1/repo/repo.go | 4 +- routers/api/v1/repo/transfer.go | 3 +- routers/api/v1/user/app.go | 11 +- routers/install/install.go | 3 +- routers/private/hook_post_receive.go | 3 +- routers/web/admin/audit.go | 2 +- routers/web/admin/auths.go | 5 +- routers/web/admin/emails.go | 14 +- routers/web/admin/hooks.go | 3 +- routers/web/admin/users.go | 3 +- routers/web/auth/2fa.go | 3 +- routers/web/auth/auth.go | 11 +- routers/web/auth/oauth.go | 3 +- routers/web/auth/openid.go | 5 +- routers/web/auth/password.go | 8 +- routers/web/org/org.go | 3 +- routers/web/org/setting.go | 3 +- routers/web/org/setting/audit.go | 2 +- routers/web/org/teams.go | 24 +- routers/web/repo/repo.go | 3 +- routers/web/repo/setting/audit.go | 2 +- routers/web/repo/setting/collaboration.go | 13 +- routers/web/repo/setting/deploy_key.go | 3 +- routers/web/repo/setting/protected_branch.go | 7 +- routers/web/repo/setting/protected_tag.go | 7 +- routers/web/repo/setting/setting.go | 20 +- routers/web/repo/setting/webhook.go | 6 +- routers/web/user/setting/applications.go | 5 +- routers/web/user/setting/keys.go | 9 +- routers/web/user/setting/oauth2_common.go | 21 +- routers/web/user/setting/security/2fa.go | 7 +- routers/web/user/setting/security/openid.go | 5 +- routers/web/user/setting/security/security.go | 3 +- routers/web/user/setting/security/webauthn.go | 5 +- routers/web/user/setting/webhooks.go | 3 +- services/asymkey/deploy_key.go | 3 +- services/asymkey/ssh_key.go | 5 +- services/audit/audit.go | 41 +- services/audit/audit_test.go | 18 +- services/audit/database.go | 2 +- services/audit/display.go | 2 +- services/audit/file.go | 2 +- services/audit/file_test.go | 4 +- services/audit/helper.go | 30 +- services/audit/record.go | 503 ++++++++++++++++++ services/auth/basic.go | 3 +- services/auth/reverseproxy.go | 3 +- services/auth/source.go | 3 +- .../auth/source/ldap/source_authenticate.go | 9 +- services/auth/source/ldap/source_sync.go | 9 +- .../auth/source/pam/source_authenticate.go | 3 +- .../auth/source/smtp/source_authenticate.go | 3 +- services/auth/source/source_group_sync.go | 5 +- services/auth/sspi.go | 3 +- services/externalaccount/user.go | 3 +- services/org/org.go | 3 +- services/org/repo.go | 3 +- services/repository/branch.go | 3 +- services/repository/delete.go | 3 +- services/repository/fork.go | 5 +- services/repository/repository.go | 5 +- services/repository/transfer.go | 9 +- services/secrets/secrets.go | 45 +- services/user/email.go | 9 +- services/user/email_test.go | 11 +- services/user/update.go | 14 +- services/user/user.go | 6 +- services/wiki/wiki.go | 3 +- tests/integration/audit_test.go | 20 +- 85 files changed, 708 insertions(+), 406 deletions(-) create mode 100644 services/audit/record.go diff --git a/cmd/admin_auth.go b/cmd/admin_auth.go index 058dd6261f891..6327333e08f44 100644 --- a/cmd/admin_auth.go +++ b/cmd/admin_auth.go @@ -9,7 +9,6 @@ import ( "os" "text/tabwriter" - audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/services/audit" @@ -97,7 +96,7 @@ func createSource(ctx context.Context, source *auth_model.Source) error { return err } - audit.Record(ctx, audit_model.SystemAuthenticationSourceAdd, audit.NewCLIUser(), nil, source, "Created authentication source %s [%s].", source.Name, source.Type.String()) + audit.RecordSystemAuthenticationSourceAdd(ctx, audit.NewCLIUser(), source) return nil } @@ -107,7 +106,7 @@ func updateSource(ctx context.Context, source *auth_model.Source) error { return err } - audit.Record(ctx, audit_model.SystemAuthenticationSourceUpdate, audit.NewCLIUser(), nil, source, "Updated authentication source %s.", source.Name) + audit.RecordSystemAuthenticationSourceUpdate(ctx, audit.NewCLIUser(), source) return nil } diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index a5d0d8045b06e..938c49e9be1d4 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -8,7 +8,6 @@ import ( "fmt" "strings" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/auth/source/ldap" @@ -360,7 +359,7 @@ func (a *authService) addLdapSource(c *cli.Context, authType auth.Type, args ... return err } - audit.Record(ctx, audit_model.SystemAuthenticationSourceAdd, audit.NewCLIUser(), nil, authSource, "Created authentication source %s [%s].", authSource.Name, authSource.Type.String()) + audit.RecordSystemAuthenticationSourceAdd(ctx, audit.NewCLIUser(), authSource) return nil } @@ -390,7 +389,7 @@ func (a *authService) updateLdapSource(c *cli.Context, authType auth.Type) error return err } - audit.Record(ctx, audit_model.SystemAuthenticationSourceUpdate, audit.NewCLIUser(), nil, authSource, "Updated authentication source %s.", authSource.Name) + audit.RecordSystemAuthenticationSourceUpdate(ctx, audit.NewCLIUser(), authSource) return nil } diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 650a4d63556f7..d6b26c127d859 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" - audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" pwd "code.gitea.io/gitea/modules/auth/password" @@ -155,7 +154,7 @@ func runCreateUser(c *cli.Context) error { return fmt.Errorf("CreateUser: %w", err) } - audit.Record(ctx, audit_model.UserCreate, audit.NewCLIUser(), u, u, "Created user %s.", u.Name) + audit.RecordUserCreate(ctx, audit.NewCLIUser(), u) if c.Bool("access-token") { t := &auth_model.AccessToken{ @@ -167,7 +166,7 @@ func runCreateUser(c *cli.Context) error { return err } - audit.Record(ctx, audit_model.UserAccessTokenAdd, audit.NewCLIUser(), u, t, "Added access token %s for user %s with scope %s.", t.Name, u.Name, t.Scope) + audit.RecordUserAccessTokenAdd(ctx, audit.NewCLIUser(), u, t) fmt.Printf("Access token was successfully created... %s\n", t.Token) } diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go index 0ad2e6631a870..1a3ebe6b1bc55 100644 --- a/cmd/admin_user_generate_access_token.go +++ b/cmd/admin_user_generate_access_token.go @@ -6,7 +6,6 @@ package cmd import ( "fmt" - audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/services/audit" @@ -88,7 +87,7 @@ func runGenerateAccessToken(c *cli.Context) error { return err } - audit.Record(ctx, audit_model.UserAccessTokenAdd, audit.NewCLIUser(), user, t, "Added access token %s for user %s with scope %s.", t.Name, user.Name, t.Scope) + audit.RecordUserAccessTokenAdd(ctx, audit.NewCLIUser(), user, t) if c.Bool("raw") { fmt.Printf("%s\n", t.Token) diff --git a/models/audit/action.go b/models/audit/action.go index f4455a07efb09..3dfa4f0c0aea0 100644 --- a/models/audit/action.go +++ b/models/audit/action.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package audit @@ -8,7 +8,6 @@ type Action string const ( UserImpersonation Action = "user:impersonation" UserCreate Action = "user:create" - UserUpdate Action = "user:update" UserDelete Action = "user:delete" UserAuthenticationFailTwoFactor Action = "user:authentication:fail:twofactor" UserAuthenticationSource Action = "user:authentication:source" @@ -54,7 +53,6 @@ const ( UserWebhookRemove Action = "user:webhook:remove" OrganizationCreate Action = "organization:create" - OrganizationUpdate Action = "organization:update" OrganizationDelete Action = "organization:delete" OrganizationName Action = "organization:name" OrganizationVisibility Action = "organization:visibility" diff --git a/models/audit/audit_event.go b/models/audit/audit_event.go index 18278b3f86bd3..8e95a051589a1 100644 --- a/models/audit/audit_event.go +++ b/models/audit/audit_event.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package audit diff --git a/models/audit/types.go b/models/audit/types.go index 2a0d165c08858..f6e098e8fe5e3 100644 --- a/models/audit/types.go +++ b/models/audit/types.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package audit diff --git a/models/migrations/v1_22/v287.go b/models/migrations/v1_22/v287.go index cd7217c1f5e5d..bd0563fc49dfb 100644 --- a/models/migrations/v1_22/v287.go +++ b/models/migrations/v1_22/v287.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package v1_22 //nolint diff --git a/models/user/email_address.go b/models/user/email_address.go index 8141af37ce3f4..df57ce8a8ff50 100644 --- a/models/user/email_address.go +++ b/models/user/email_address.go @@ -475,10 +475,10 @@ func SearchEmails(ctx context.Context, opts *SearchEmailOptions) ([]*SearchEmail // ActivateUserEmail will change the activated state of an email address, // either primary or secondary (all in the email_address table) -func ActivateUserEmail(ctx context.Context, userID int64, email string, activate bool) (err error) { +func ActivateUserEmail(ctx context.Context, userID int64, email string, activate bool) (*EmailAddress, error) { ctx, committer, err := db.TxContext(ctx) if err != nil { - return err + return nil, err } defer committer.Close() @@ -486,46 +486,46 @@ func ActivateUserEmail(ctx context.Context, userID int64, email string, activate // First check if there's another user active with the same address addr, exist, err := db.Get[EmailAddress](ctx, builder.Eq{"uid": userID, "lower_email": strings.ToLower(email)}) if err != nil { - return err + return nil, err } else if !exist { - return fmt.Errorf("no such email: %d (%s)", userID, email) + return nil, fmt.Errorf("no such email: %d (%s)", userID, email) } if addr.IsActivated == activate { // Already in the desired state; no action - return nil + return addr, nil } if activate { if used, err := IsEmailActive(ctx, email, addr.ID); err != nil { - return fmt.Errorf("unable to check isEmailActive() for %s: %w", email, err) + return nil, fmt.Errorf("unable to check isEmailActive() for %s: %w", email, err) } else if used { - return ErrEmailAlreadyUsed{Email: email} + return nil, ErrEmailAlreadyUsed{Email: email} } } if err = updateActivation(ctx, addr, activate); err != nil { - return fmt.Errorf("unable to updateActivation() for %d:%s: %w", addr.ID, addr.Email, err) + return nil, fmt.Errorf("unable to updateActivation() for %d:%s: %w", addr.ID, addr.Email, err) } // Activate/deactivate a user's primary email address and account if addr.IsPrimary { user, exist, err := db.Get[User](ctx, builder.Eq{"id": userID, "email": email}) if err != nil { - return err + return nil, err } else if !exist { - return fmt.Errorf("no user with ID: %d and Email: %s", userID, email) + return nil, fmt.Errorf("no user with ID: %d and Email: %s", userID, email) } // The user's activation state should be synchronized with the primary email if user.IsActive != activate { user.IsActive = activate if user.Rands, err = GetUserSalt(); err != nil { - return fmt.Errorf("unable to generate salt: %w", err) + return nil, fmt.Errorf("unable to generate salt: %w", err) } if err = UpdateUserCols(ctx, user, "is_active", "rands"); err != nil { - return fmt.Errorf("unable to updateUserCols() for user ID: %d: %w", userID, err) + return nil, fmt.Errorf("unable to updateUserCols() for user ID: %d: %w", userID, err) } } } - return committer.Commit() + return addr, committer.Commit() } diff --git a/modules/setting/audit.go b/modules/setting/audit.go index ef6298610c584..56fc153a09024 100644 --- a/modules/setting/audit.go +++ b/modules/setting/audit.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package setting diff --git a/routers/api/v1/admin/org.go b/routers/api/v1/admin/org.go index 412019057dcf6..386d91f08211c 100644 --- a/routers/api/v1/admin/org.go +++ b/routers/api/v1/admin/org.go @@ -7,7 +7,6 @@ package admin import ( "net/http" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" @@ -76,7 +75,7 @@ func CreateOrg(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.OrganizationCreate, ctx.Doer, org, org, "Created organization %s.", org.Name) + audit.RecordUserCreate(ctx, ctx.Doer, org.AsUser()) ctx.JSON(http.StatusCreated, convert.ToOrganization(ctx, org)) } diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index c78fbc462bdaf..e2dadf2f3d2df 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -11,7 +11,6 @@ import ( "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" @@ -154,7 +153,7 @@ func CreateUser(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.UserCreate, ctx.Doer, u, u, "Created user %s.", u.Name) + audit.RecordUserCreate(ctx, ctx.Doer, u) log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index cadbfa0f33a02..bdbd8b2e307b5 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -71,7 +71,6 @@ import ( "strings" actions_model "code.gitea.io/gitea/models/actions" - audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" @@ -125,7 +124,7 @@ func sudo() func(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.UserImpersonation, ctx.Doer, ctx.Doer, user, "User %s impersonating user %s.", ctx.Doer.Name, user.Name) + audit.RecordUserImpersonation(ctx, ctx.Doer, user) log.Trace("Sudo from (%s) to: %s", ctx.Doer.Name, user.Name) ctx.Doer = user diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go index 8a6a5ee1f7275..7bfb7fef7f747 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -8,7 +8,6 @@ import ( "net/http" activities_model "code.gitea.io/gitea/models/activities" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" @@ -279,7 +278,7 @@ func Create(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.OrganizationCreate, ctx.Doer, org, org, "Created organization %s.", org.Name) + audit.RecordUserCreate(ctx, ctx.Doer, org.AsUser()) ctx.JSON(http.StatusCreated, convert.ToOrganization(ctx, org)) } diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index 738a7b3db645c..d271df682fd00 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -10,7 +10,6 @@ import ( "code.gitea.io/gitea/models" activities_model "code.gitea.io/gitea/models/activities" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" @@ -250,7 +249,7 @@ func CreateTeam(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, team, "Added team %s to organization %s.", team.Name, ctx.Org.Organization.Name) + audit.RecordOrganizationTeamAdd(ctx, ctx.Doer, ctx.Org.Organization, team) apiTeam, err := convert.ToTeam(ctx, team, true) if err != nil { @@ -312,7 +311,6 @@ func EditTeam(ctx *context.APIContext) { } isAuthChanged := false - oldAccessMode := team.AccessMode isIncludeAllChanged := false if !team.IsOwnerTeam() && len(form.Permission) != 0 { // Validate permission level. @@ -347,9 +345,9 @@ func EditTeam(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.OrganizationTeamUpdate, ctx.Doer, org, team, "Updated settings of team %s/%s.", org.Name, team.Name) + audit.RecordOrganizationTeamUpdate(ctx, ctx.Doer, org, team) if isAuthChanged { - audit.Record(ctx, audit_model.OrganizationTeamPermission, ctx.Doer, org, team, "Changed permission of team %s/%s from %s to %s.", org.Name, team.Name, oldAccessMode.String(), team.AccessMode.String()) + audit.RecordOrganizationTeamPermission(ctx, ctx.Doer, org, team) } apiTeam, err := convert.ToTeam(ctx, team) @@ -389,7 +387,7 @@ func DeleteTeam(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.OrganizationTeamRemove, ctx.Doer, org, ctx.Org.Team, "Removed team %s from organization %s.", ctx.Org.Team.Name, org.Name) + audit.RecordOrganizationTeamRemove(ctx, ctx.Doer, org, ctx.Org.Team) ctx.Status(http.StatusNoContent) } @@ -530,7 +528,7 @@ func AddTeamMember(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, ctx.Doer, org, ctx.Org.Team, "Added user %s to team %s/%s.", u.Name, org.Name, ctx.Org.Team.Name) + audit.RecordOrganizationTeamMemberAdd(ctx, ctx.Doer, org, ctx.Org.Team, u) ctx.Status(http.StatusNoContent) } @@ -576,7 +574,7 @@ func RemoveTeamMember(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.OrganizationTeamMemberRemove, ctx.Doer, org, ctx.Org.Team, "Removed user %s from team %s/%s.", u.Name, org.Name, ctx.Org.Team.Name) + audit.RecordOrganizationTeamMemberRemove(ctx, ctx.Doer, org, ctx.Org.Team, u) ctx.Status(http.StatusNoContent) } diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go index 01e8c709774a2..65fb3703f01f9 100644 --- a/routers/api/v1/repo/collaborators.go +++ b/routers/api/v1/repo/collaborators.go @@ -8,7 +8,6 @@ import ( "errors" "net/http" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" @@ -188,7 +187,7 @@ func AddCollaborator(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.RepositoryCollaboratorAdd, ctx.Doer, ctx.Repo.Repository, collaborator, "Added user %s as collaborator for repository %s.", collaborator.Name, ctx.Repo.Repository.FullName()) + audit.RecordRepositoryCollaboratorAdd(ctx, ctx.Doer, ctx.Repo.Repository, collaborator) if form.Permission != nil { accessMode := perm.ParseAccessMode(*form.Permission) @@ -197,7 +196,7 @@ func AddCollaborator(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.RepositoryCollaboratorAccess, ctx.Doer, ctx.Repo.Repository, collaborator, "Changed access mode of collaborator %s to %s.", collaborator.Name, accessMode.String()) + audit.RecordRepositoryCollaboratorAccess(ctx, ctx.Doer, ctx.Repo.Repository, collaborator, accessMode) } ctx.Status(http.StatusNoContent) @@ -249,7 +248,7 @@ func DeleteCollaborator(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.RepositoryCollaboratorRemove, ctx.Doer, ctx.Repo.Repository, collaborator, "Removed collaborator %s.", collaborator.Name) + audit.RecordRepositoryCollaboratorRemove(ctx, ctx.Doer, ctx.Repo.Repository, collaborator) ctx.Status(http.StatusNoContent) } diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 2ba452179c4c4..f0651c0e07adf 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -12,7 +12,6 @@ import ( "time" activities_model "code.gitea.io/gitea/models/activities" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" @@ -747,9 +746,8 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err return err } - audit.Record(ctx, audit_model.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) if visibilityChanged { - audit.Record(ctx, audit_model.RepositoryVisibility, ctx.Doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) + audit.RecordRepositoryVisibility(ctx, ctx.Doer, repo) } log.Trace("Repository basic settings updated: %s/%s", owner.Name, repo.Name) diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go index 3ff7a1ef81327..0a30b3faf4ea4 100644 --- a/routers/api/v1/repo/transfer.go +++ b/routers/api/v1/repo/transfer.go @@ -8,7 +8,6 @@ import ( "net/http" "code.gitea.io/gitea/models" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" @@ -236,7 +235,7 @@ func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error { return err } - audit.Record(ctx, audit_model.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected repository transfer.") + audit.RecordRepositoryTransferReject(ctx, ctx.Doer, ctx.Repo.Repository) return nil } diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index bbed96803ad30..5d77dd8932abc 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -11,7 +11,6 @@ import ( "strconv" "strings" - audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" @@ -128,7 +127,7 @@ func CreateAccessToken(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.UserAccessTokenAdd, ctx.Doer, ctx.Doer, t, "Added access token %s for user %s with scope %s.", t.Name, ctx.Doer.Name, t.Scope) + audit.RecordUserAccessTokenAdd(ctx, ctx.Doer, ctx.Doer, t) ctx.JSON(http.StatusCreated, &api.AccessToken{ Name: t.Name, @@ -212,7 +211,7 @@ func DeleteAccessToken(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.UserAccessTokenRemove, ctx.Doer, ctx.Doer, t, "Removed access token %s from user %s.", t.Name, ctx.Doer.Name) + audit.RecordUserAccessTokenRemove(ctx, ctx.Doer, ctx.Doer, t) ctx.Status(http.StatusNoContent) } @@ -255,7 +254,7 @@ func CreateOauth2Application(ctx *context.APIContext) { } app.ClientSecret = secret - audit.Record(ctx, audit_model.UserOAuth2ApplicationAdd, ctx.Doer, ctx.Doer, app, "Created OAuth2 application %s.", app.Name) + audit.RecordOAuth2ApplicationAdd(ctx, ctx.Doer, ctx.Doer, app) ctx.JSON(http.StatusCreated, convert.ToOAuth2Application(app)) } @@ -338,7 +337,7 @@ func DeleteOauth2Application(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.UserOAuth2ApplicationRemove, ctx.Doer, ctx.Doer, app, "Removed OAuth2 application %s.", app.Name) + audit.RecordOAuth2ApplicationRemove(ctx, ctx.Doer, ctx.Doer, app) ctx.Status(http.StatusNoContent) } @@ -431,7 +430,7 @@ func UpdateOauth2Application(ctx *context.APIContext) { return } - audit.Record(ctx, audit_model.UserOAuth2ApplicationUpdate, ctx.Doer, ctx.Doer, app, "Updated OAuth2 application %s.", app.Name) + audit.RecordOAuth2ApplicationUpdate(ctx, ctx.Doer, ctx.Doer, app) ctx.JSON(http.StatusOK, convert.ToOAuth2Application(app)) } diff --git a/routers/install/install.go b/routers/install/install.go index e268d1a8a73aa..f6d38172b09a1 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -14,7 +14,6 @@ import ( "strings" "time" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" db_install "code.gitea.io/gitea/models/db/install" "code.gitea.io/gitea/models/migrations" @@ -551,7 +550,7 @@ func SubmitInstall(ctx *context.Context) { u, _ = user_model.GetUserByName(ctx, u.Name) } - audit.Record(ctx, audit_model.UserCreate, u, u, u, "Created user %s.", u.Name) + audit.RecordUserCreate(ctx, u, u) nt, token, err := auth_service.CreateAuthTokenForUserID(ctx, u.ID) if err != nil { diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index cda9ba2624a0f..f9285d32adcbc 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -8,7 +8,6 @@ import ( "net/http" "strconv" - audit_model "code.gitea.io/gitea/models/audit" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -124,7 +123,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { return } - audit.Record(ctx, audit_model.RepositoryVisibility, doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) + audit.RecordRepositoryVisibility(ctx, doer, repo) } } diff --git a/routers/web/admin/audit.go b/routers/web/admin/audit.go index 6a536b9581205..3b7158675aef3 100644 --- a/routers/web/admin/audit.go +++ b/routers/web/admin/audit.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package admin diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index cc0bba5f502a9..2d12b07a70e56 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -12,7 +12,6 @@ import ( "strconv" "strings" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/auth/pam" @@ -325,7 +324,7 @@ func NewAuthSourcePost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.SystemAuthenticationSourceAdd, ctx.Doer, nil, source, "Created authentication source %s [%s].", source.Name, source.Type.String()) + audit.RecordSystemAuthenticationSourceAdd(ctx, ctx.Doer, source) log.Trace("Authentication created by admin(%s): %s", ctx.Doer.Name, form.Name) @@ -441,7 +440,7 @@ func EditAuthSourcePost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.SystemAuthenticationSourceUpdate, ctx.Doer, nil, source, "Updated authentication source %s.", source.Name) + audit.RecordSystemAuthenticationSourceUpdate(ctx, ctx.Doer, source) log.Trace("Authentication changed by admin(%s): %d", ctx.Doer.Name, source.ID) diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go index ef9cd0bf6bc63..c15f3481c62e2 100644 --- a/routers/web/admin/emails.go +++ b/routers/web/admin/emails.go @@ -8,7 +8,6 @@ import ( "net/http" "net/url" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" @@ -113,21 +112,20 @@ func ActivateEmail(ctx *context.Context) { uid := ctx.FormInt64("uid") email := ctx.FormString("email") - primary, okp := truefalse[ctx.FormString("primary")] activate, oka := truefalse[ctx.FormString("activate")] - if uid == 0 || len(email) == 0 || !okp || !oka { + if uid == 0 || len(email) == 0 || !oka { ctx.Error(http.StatusBadRequest) return } - log.Info("Changing activation for User ID: %d, email: %s, primary: %v to %v", uid, email, primary, activate) + log.Info("Changing activation for User ID: %d, email: %s to %v", uid, email, activate) u, err := user_model.GetUserByID(ctx, uid) if err != nil { ctx.Flash.Error(ctx.Tr("admin.emails.not_updated", err)) } else { - if err := user_model.ActivateUserEmail(ctx, uid, email, activate); err != nil { + if email, err := user_model.ActivateUserEmail(ctx, uid, email, activate); err != nil { log.Error("ActivateUserEmail(%v,%v,%v): %v", uid, email, activate, err) if user_model.IsErrEmailAlreadyUsed(err) { ctx.Flash.Error(ctx.Tr("admin.emails.duplicate_active")) @@ -135,9 +133,11 @@ func ActivateEmail(ctx *context.Context) { ctx.Flash.Error(ctx.Tr("admin.emails.not_updated", err)) } } else { - audit.Record(ctx, audit_model.UserEmailActivate, ctx.Doer, u, u, "Activated email %s of user %s.", email, u.Name) + if activate { + audit.RecordUserEmailActivate(ctx, ctx.Doer, u, email) + } - log.Info("Activation for User ID: %d, email: %s, primary: %v changed to %v", uid, email, primary, activate) + log.Info("Activation for User ID: %d, email: %s changed to %v", uid, email, activate) ctx.Flash.Info(ctx.Tr("admin.emails.updated")) } } diff --git a/routers/web/admin/hooks.go b/routers/web/admin/hooks.go index a7b76e0abb60a..eca2245842fbc 100644 --- a/routers/web/admin/hooks.go +++ b/routers/web/admin/hooks.go @@ -6,7 +6,6 @@ package admin import ( "net/http" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -72,7 +71,7 @@ func DeleteDefaultOrSystemWebhook(ctx *context.Context) { if err := webhook.DeleteDefaultSystemWebhook(ctx, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteDefaultWebhook: " + err.Error()) } else { - audit.Record(ctx, audit_model.SystemWebhookRemove, ctx.Doer, nil, hook, "Removed webhook %s.", hook.URL) + audit.RecordWebhookRemove(ctx, ctx.Doer, nil, nil, hook) ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 101369a327641..1e32f9742090a 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -12,7 +12,6 @@ import ( "strings" "code.gitea.io/gitea/models" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" org_model "code.gitea.io/gitea/models/organization" @@ -205,7 +204,7 @@ func NewUserPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserCreate, ctx.Doer, u, u, "Created user %s.", u.Name) + audit.RecordUserCreate(ctx, ctx.Doer, u) log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name) diff --git a/routers/web/auth/2fa.go b/routers/web/auth/2fa.go index 8d4d217e27b1a..b3e47138aaef6 100644 --- a/routers/web/auth/2fa.go +++ b/routers/web/auth/2fa.go @@ -7,7 +7,6 @@ import ( "errors" "net/http" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" @@ -95,7 +94,7 @@ func TwoFactorPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserAuthenticationFailTwoFactor, u, u, u, "Failed two-factor authentication for user %s.", u.Name) + audit.RecordUserAuthenticationFailTwoFactor(ctx, u) ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplTwofa, forms.TwoFactorAuthForm{}) } diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 6083e3b16181a..301c21a00d2f0 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -10,7 +10,6 @@ import ( "net/http" "strings" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" @@ -586,7 +585,7 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *us return false } - audit.Record(ctx, audit_model.UserCreate, audit.NewAuthenticationSourceUser(), u, u, "Created user %s.", u.Name) + audit.RecordUserCreate(ctx, audit.NewAuthenticationSourceUser(), u) log.Trace("Account created: %s", u.Name) return true @@ -741,13 +740,15 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) { return } - if err := user_model.ActivateUserEmail(ctx, user.ID, user.Email, true); err != nil { + email, err := user_model.ActivateUserEmail(ctx, user.ID, user.Email, true) + if err != nil { log.Error("Unable to activate email for user: %-v with email: %s: %v", user, user.Email, err) ctx.ServerError("ActivateUserEmail", err) return } - audit.Record(ctx, audit_model.UserActive, user, user, user, "Changed activation status of user %s to %s.", user.Name, audit.UserActiveString(user.IsActive)) + audit.RecordUserActive(ctx, user, user) + audit.RecordUserEmailActivate(ctx, user, user, email) log.Trace("User activated: %s", user.Name) @@ -797,7 +798,7 @@ func ActivateEmail(ctx *context.Context) { // Allow user to validate more emails _ = ctx.Cache.Delete("MailResendLimit_" + user.LowerName) - audit.Record(ctx, audit_model.UserEmailActivate, user, user, email, "Activated email %s of user %s.", email.Email, user.Name) + audit.RecordUserEmailActivate(ctx, user, user, email) } // FIXME: e-mail verification does not require the user to be logged in, diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index b0ab49c3221f9..28903ccb0b19f 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -15,7 +15,6 @@ import ( "sort" "strings" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" org_model "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" @@ -567,7 +566,7 @@ func GrantApplicationOAuth(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserOAuth2ApplicationGrant, ctx.Doer, owner, app, "Granted OAuth2 access to application %s.", app.Name) + audit.RecordUserOAuth2ApplicationGrant(ctx, ctx.Doer, owner, app, grant) if len(form.Nonce) > 0 { err := grant.SetNonce(ctx, form.Nonce) diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go index 513f7f66bc4b6..9108c6cef05f5 100644 --- a/routers/web/auth/openid.go +++ b/routers/web/auth/openid.go @@ -8,7 +8,6 @@ import ( "net/http" "net/url" - audit_model "code.gitea.io/gitea/models/audit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/openid" "code.gitea.io/gitea/modules/base" @@ -282,7 +281,7 @@ func ConnectOpenIDPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserOpenIDAdd, u, u, userOID, "Associated OpenID %s to user %s.", userOID.URI, u.Name) + audit.RecordUserOpenIDAdd(ctx, u, u, userOID) ctx.Flash.Success(ctx.Tr("settings.add_openid_success")) @@ -388,7 +387,7 @@ func RegisterOpenIDPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserOpenIDAdd, u, u, userOID, "Associated OpenID %s to user %s.", userOID.URI, u.Name) + audit.RecordUserOpenIDAdd(ctx, u, u, userOID) remember, _ := ctx.Session.Get("openid_signin_remember").(bool) log.Trace("Session stored openid-remember: %t", remember) diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go index 0afca4f6116f5..dd80e42db4f0c 100644 --- a/routers/web/auth/password.go +++ b/routers/web/auth/password.go @@ -8,7 +8,6 @@ import ( "fmt" "net/http" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/password" @@ -91,7 +90,7 @@ func ForgotPasswdPost(ctx *context.Context) { mailer.SendResetPasswordMail(u) - audit.Record(ctx, audit_model.UserPasswordReset, u, u, u, "Requested passwort reset for user %s.", u.Name) + audit.RecordUserPasswordReset(ctx, u) if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { log.Error("Set cache(MailResendLimit) fail: %v", err) @@ -190,7 +189,7 @@ func ResetPasswdPost(ctx *context.Context) { return } if !ok || twofa.LastUsedPasscode == passcode { - audit.Record(ctx, audit_model.UserAuthenticationFailTwoFactor, u, u, u, "Failed two-factor authentication for user %s.", u.Name) + audit.RecordUserAuthenticationFailTwoFactor(ctx, u) ctx.Data["IsResetForm"] = true ctx.Data["Err_Passcode"] = true @@ -229,9 +228,6 @@ func ResetPasswdPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserPassword, u, u, u, "Changed password of user %s.", u.Name) - - log.Trace("User password reset: %s", u.Name) ctx.Data["IsResetFailed"] = true remember := len(ctx.FormString("remember")) != 0 diff --git a/routers/web/org/org.go b/routers/web/org/org.go index ba3aae996563a..1303de16dd06e 100644 --- a/routers/web/org/org.go +++ b/routers/web/org/org.go @@ -8,7 +8,6 @@ import ( "errors" "net/http" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" @@ -77,7 +76,7 @@ func CreatePost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.OrganizationCreate, ctx.Doer, org, org, "Created organization %s.", org.Name) + audit.RecordUserCreate(ctx, ctx.Doer, org.AsUser()) log.Trace("Organization created: %s", org.Name) diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index f06e7a4fc4155..282ad22b6004d 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -9,7 +9,6 @@ import ( "net/url" "code.gitea.io/gitea/models" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -240,7 +239,7 @@ func DeleteWebhook(ctx *context.Context) { if err := webhook.DeleteWebhookByOwnerID(ctx, ctx.Org.Organization.ID, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error()) } else { - audit.Record(ctx, audit_model.OrganizationWebhookRemove, ctx.Doer, ctx.Org.Organization, hook, "Removed webhook %s.", hook.URL) + audit.RecordWebhookRemove(ctx, ctx.Doer, ctx.Org.Organization.AsUser(), nil, hook) ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } diff --git a/routers/web/org/setting/audit.go b/routers/web/org/setting/audit.go index c1ecf813b4e3d..7981cb0610cb2 100644 --- a/routers/web/org/setting/audit.go +++ b/routers/web/org/setting/audit.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package setting diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index 8b531eeb2e1b7..7e8705cfb997d 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -13,7 +13,6 @@ import ( "strings" "code.gitea.io/gitea/models" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" org_model "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" @@ -81,7 +80,7 @@ func TeamsAction(ctx *context.Context) { } err = models.AddTeamMember(ctx, ctx.Org.Team, ctx.Doer.ID) if err == nil { - audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Added user %s to team %s/%s.", ctx.Doer.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + audit.RecordOrganizationTeamMemberAdd(ctx, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, ctx.Doer) } case "leave": err = models.RemoveTeamMember(ctx, ctx.Org.Team, ctx.Doer.ID) @@ -97,7 +96,7 @@ func TeamsAction(ctx *context.Context) { return } } else { - audit.Record(ctx, audit_model.OrganizationTeamMemberRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Removed user %s from team %s/%s.", ctx.Doer.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + audit.RecordOrganizationTeamMemberRemove(ctx, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, ctx.Doer) } checkIsOrgMemberAndRedirect(ctx, ctx.Org.OrgLink+"/teams/") return @@ -126,7 +125,7 @@ func TeamsAction(ctx *context.Context) { return } } else { - audit.Record(ctx, audit_model.OrganizationTeamMemberRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Removed user %s from team %s/%s.", u.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + audit.RecordOrganizationTeamMemberRemove(ctx, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, u) } checkIsOrgMemberAndRedirect(ctx, ctx.Org.OrgLink+"/teams/"+url.PathEscape(ctx.Org.Team.LowerName)) return @@ -172,7 +171,7 @@ func TeamsAction(ctx *context.Context) { } else { err = models.AddTeamMember(ctx, ctx.Org.Team, u.ID) if err == nil { - audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Added user %s to team %s/%s.", u.Name, ctx.Org.Organization.Name, ctx.Org.Team.Name) + audit.RecordOrganizationTeamMemberAdd(ctx, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, u) } } @@ -277,7 +276,7 @@ func TeamsRepoAction(ctx *context.Context) { } for _, repo := range added { - audit.Record(ctx, audit_model.RepositoryCollaboratorTeamAdd, ctx.Doer, repo, ctx.Org.Team, "Added team %s as collaborator for %s.", ctx.Org.Team.Name, repo.FullName()) + audit.RecordRepositoryCollaboratorTeamAdd(ctx, ctx.Doer, repo, ctx.Org.Team) } case "removeall": if err := ctx.Org.Team.LoadRepositories(ctx); err != nil { @@ -291,7 +290,7 @@ func TeamsRepoAction(ctx *context.Context) { } for _, repo := range ctx.Org.Team.Repos { - audit.Record(ctx, audit_model.RepositoryCollaboratorTeamRemove, ctx.Doer, repo, ctx.Org.Team, "Removed team %s as collaborator from %s.", ctx.Org.Team.Name, repo.FullName()) + audit.RecordRepositoryCollaboratorTeamRemove(ctx, ctx.Doer, repo, ctx.Org.Team) } } @@ -400,7 +399,7 @@ func NewTeamPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.OrganizationTeamAdd, ctx.Doer, ctx.Org.Organization, t, "Added team %s to organization %s.", t.Name, ctx.Org.Organization.Name) + audit.RecordOrganizationTeamAdd(ctx, ctx.Doer, ctx.Org.Organization, t) log.Trace("Team created: %s/%s", ctx.Org.Organization.Name, t.Name) ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(t.LowerName)) @@ -532,7 +531,6 @@ func EditTeamPost(ctx *context.Context) { ctx.Data["Team"] = t ctx.Data["Units"] = unit_model.Units - oldAccessMode := t.AccessMode if !t.IsOwnerTeam() { t.Name = form.TeamName if t.AccessMode != newAccessMode { @@ -582,9 +580,9 @@ func EditTeamPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.OrganizationTeamUpdate, ctx.Doer, ctx.Org.Organization, t, "Updated settings of team %s/%s.", ctx.Org.Organization.Name, t.Name) + audit.RecordOrganizationTeamUpdate(ctx, ctx.Doer, ctx.Org.Organization, t) if isAuthChanged { - audit.Record(ctx, audit_model.OrganizationTeamPermission, ctx.Doer, ctx.Org.Organization, t, "Changed permission of team %s/%s from %s to %s.", ctx.Org.Organization.Name, t.Name, oldAccessMode.String(), t.AccessMode.String()) + audit.RecordOrganizationTeamPermission(ctx, ctx.Doer, ctx.Org.Organization, t) } ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(t.LowerName)) @@ -595,7 +593,7 @@ func DeleteTeam(ctx *context.Context) { if err := models.DeleteTeam(ctx, ctx.Org.Team); err != nil { ctx.Flash.Error("DeleteTeam: " + err.Error()) } else { - audit.Record(ctx, audit_model.OrganizationTeamRemove, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, "Removed team %s from organization %s.", ctx.Org.Team.Name, ctx.Org.Organization.Name) + audit.RecordOrganizationTeamRemove(ctx, ctx.Doer, ctx.Org.Organization, ctx.Org.Team) ctx.Flash.Success(ctx.Tr("org.teams.delete_team_success")) } @@ -641,7 +639,7 @@ func TeamInvitePost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, ctx.Doer, org, team, "Added user %s to team %s/%s.", ctx.Doer.Name, org.Name, team.Name) + audit.RecordOrganizationTeamMemberAdd(ctx, ctx.Doer, org, team, ctx.Doer) if err := org_model.RemoveInviteByID(ctx, invite.ID, team.ID); err != nil { log.Error("RemoveInviteByID: %v", err) diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index ce9ee3fddf8c1..70fa196cc0f15 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -12,7 +12,6 @@ import ( "strings" "code.gitea.io/gitea/models" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/organization" @@ -399,7 +398,7 @@ func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) error { return err } - audit.Record(ctx, audit_model.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected repository transfer.") + audit.RecordRepositoryTransferReject(ctx, ctx.Doer, ctx.Repo.Repository) ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected")) } diff --git a/routers/web/repo/setting/audit.go b/routers/web/repo/setting/audit.go index 4cc2ebba874e6..c452d5a43df99 100644 --- a/routers/web/repo/setting/audit.go +++ b/routers/web/repo/setting/audit.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package setting diff --git a/routers/web/repo/setting/collaboration.go b/routers/web/repo/setting/collaboration.go index 0afdddae8398b..7b8a0bb5a3053 100644 --- a/routers/web/repo/setting/collaboration.go +++ b/routers/web/repo/setting/collaboration.go @@ -7,7 +7,6 @@ import ( "net/http" "strings" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" @@ -111,7 +110,7 @@ func CollaborationPost(ctx *context.Context) { mailer.SendCollaboratorMail(u, ctx.Doer, ctx.Repo.Repository) } - audit.Record(ctx, audit_model.RepositoryCollaboratorAdd, ctx.Doer, ctx.Repo.Repository, u, "Added user %s as collaborator for repository %s.", u.Name, ctx.Repo.Repository.FullName()) + audit.RecordRepositoryCollaboratorAdd(ctx, ctx.Doer, ctx.Repo.Repository, u) ctx.Flash.Success(ctx.Tr("repo.settings.add_collaborator_success")) ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) @@ -125,16 +124,18 @@ func ChangeCollaborationAccessMode(ctx *context.Context) { return } + accessMode := perm.AccessMode(ctx.FormInt("mode")) + if err := repo_model.ChangeCollaborationAccessMode( ctx, ctx.Repo.Repository, u.ID, - perm.AccessMode(ctx.FormInt("mode"))); err != nil { + accessMode); err != nil { log.Error("ChangeCollaborationAccessMode: %v", err) return } - audit.Record(ctx, audit_model.RepositoryCollaboratorAccess, ctx.Doer, ctx.Repo.Repository, u, "Changed access mode of collaborator %s to %s.", u.Name, perm.AccessMode(ctx.FormInt("mode")).String()) + audit.RecordRepositoryCollaboratorAccess(ctx, ctx.Doer, ctx.Repo.Repository, u, accessMode) } // DeleteCollaboration delete a collaboration for a repository @@ -148,7 +149,7 @@ func DeleteCollaboration(ctx *context.Context) { if err := repo_service.DeleteCollaboration(ctx, ctx.Repo.Repository, u.ID); err != nil { ctx.Flash.Error("DeleteCollaboration: " + err.Error()) } else { - audit.Record(ctx, audit_model.RepositoryCollaboratorRemove, ctx.Doer, ctx.Repo.Repository, u, "Removed user %s as collaborator.", u.Name) + audit.RecordRepositoryCollaboratorRemove(ctx, ctx.Doer, ctx.Repo.Repository, u) ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success")) } @@ -221,7 +222,7 @@ func DeleteTeam(ctx *context.Context) { return } - audit.Record(ctx, audit_model.RepositoryCollaboratorTeamRemove, ctx.Doer, ctx.Repo.Repository, team, "Removed team %s as collaborator from %s.", team.Name, ctx.Repo.Repository.FullName()) + audit.RecordRepositoryCollaboratorTeamRemove(ctx, ctx.Doer, ctx.Repo.Repository, team) ctx.Flash.Success(ctx.Tr("repo.settings.remove_team_success")) ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/collaboration") diff --git a/routers/web/repo/setting/deploy_key.go b/routers/web/repo/setting/deploy_key.go index aa9248b7d6f89..e5323476410d4 100644 --- a/routers/web/repo/setting/deploy_key.go +++ b/routers/web/repo/setting/deploy_key.go @@ -7,7 +7,6 @@ import ( "net/http" asymkey_model "code.gitea.io/gitea/models/asymkey" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" @@ -94,7 +93,7 @@ func DeployKeysPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.RepositoryDeployKeyAdd, ctx.Doer, ctx.Repo.Repository, key, "Added deploy key %s.", key.Name) + audit.RecordRepositoryDeployKeyAdd(ctx, ctx.Doer, ctx.Repo.Repository, key) log.Trace("Deploy key added: %d", ctx.Repo.Repository.ID) ctx.Flash.Success(ctx.Tr("repo.settings.add_key_success", key.Name)) diff --git a/routers/web/repo/setting/protected_branch.go b/routers/web/repo/setting/protected_branch.go index d5b9df5274d3d..8e9b6e36e6cfa 100644 --- a/routers/web/repo/setting/protected_branch.go +++ b/routers/web/repo/setting/protected_branch.go @@ -10,7 +10,6 @@ import ( "strings" "time" - audit_model "code.gitea.io/gitea/models/audit" git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" @@ -252,9 +251,9 @@ func SettingsProtectedBranchPost(ctx *context.Context) { } if isNewProtectedBranch { - audit.Record(ctx, audit_model.RepositoryBranchProtectionAdd, ctx.Doer, ctx.Repo.Repository, protectBranch, "Added branch protection %s.", protectBranch.RuleName) + audit.RecordRepositoryBranchProtectionAdd(ctx, ctx.Doer, ctx.Repo.Repository, protectBranch) } else { - audit.Record(ctx, audit_model.RepositoryBranchProtectionUpdate, ctx.Doer, ctx.Repo.Repository, protectBranch, "Updated branch protection %s.", protectBranch.RuleName) + audit.RecordRepositoryBranchProtectionUpdate(ctx, ctx.Doer, ctx.Repo.Repository, protectBranch) } // FIXME: since we only need to recheck files protected rules, we could improve this @@ -302,7 +301,7 @@ func DeleteProtectedBranchRulePost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.RepositoryBranchProtectionRemove, ctx.Doer, ctx.Repo.Repository, rule, "Removed branch protection %s.", rule.RuleName) + audit.RecordRepositoryBranchProtectionRemove(ctx, ctx.Doer, ctx.Repo.Repository, rule) ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", rule.RuleName)) ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) diff --git a/routers/web/repo/setting/protected_tag.go b/routers/web/repo/setting/protected_tag.go index 01c758d406bc9..6fea72a7bab53 100644 --- a/routers/web/repo/setting/protected_tag.go +++ b/routers/web/repo/setting/protected_tag.go @@ -8,7 +8,6 @@ import ( "net/http" "strings" - audit_model "code.gitea.io/gitea/models/audit" git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" @@ -65,7 +64,7 @@ func NewProtectedTagPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.RepositoryTagProtectionAdd, ctx.Doer, repo, pt, "Added tag protection for %s.", pt.NamePattern) + audit.RecordRepositoryTagProtectionAdd(ctx, ctx.Doer, repo, pt) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) @@ -120,7 +119,7 @@ func EditProtectedTagPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.RepositoryTagProtectionUpdate, ctx.Doer, ctx.Repo.Repository, pt, "Updated tag protection for %s.", pt.NamePattern) + audit.RecordRepositoryTagProtectionUpdate(ctx, ctx.Doer, ctx.Repo.Repository, pt) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/tags") @@ -138,7 +137,7 @@ func DeleteProtectedTagPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.RepositoryTagProtectionRemove, ctx.Doer, ctx.Repo.Repository, pt, "Removed tag protection for %s.", pt.NamePattern) + audit.RecordRepositoryTagProtectionRemove(ctx, ctx.Doer, ctx.Repo.Repository, pt) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/tags") diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index 5ee5d09ec9738..2f7d12a4a144e 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -12,7 +12,6 @@ import ( "time" "code.gitea.io/gitea/models" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" @@ -182,9 +181,8 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) if visibilityChanged { - audit.Record(ctx, audit_model.RepositoryVisibility, ctx.Doer, repo, repo, "Changed visibility of repository %s to %s.", repo.FullName(), audit.PublicString(!repo.IsPrivate)) + audit.RecordRepositoryVisibility(ctx, ctx.Doer, repo) } log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) @@ -376,7 +374,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.RepositoryMirrorPushRemove, ctx.Doer, repo, m, "Removed push mirror for repository %s.", repo.FullName()) + audit.RecordRepositoryMirrorPushRemove(ctx, ctx.Doer, repo, m) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(repo.Link() + "/settings") @@ -441,7 +439,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.RepositoryMirrorPushAdd, ctx.Doer, repo, m, "Added push mirror for repository %s.", repo.FullName()) + audit.RecordRepositoryMirrorPushAdd(ctx, ctx.Doer, repo, m) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Redirect(repo.Link() + "/settings") @@ -617,8 +615,6 @@ func SettingsPost(ctx *context.Context) { } } - audit.Record(ctx, audit_model.RepositoryUpdate, ctx.Doer, repo, repo, "Changed settings of repository %s.", repo.FullName()) - log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) @@ -638,7 +634,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.RepositorySigningVerification, ctx.Doer, repo, repo, "Changed signing verification of repository %s to %s.", repo.FullName(), repo.TrustModel.String()) + audit.RecordRepositorySigningVerification(ctx, ctx.Doer, repo) } log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) @@ -717,7 +713,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.RepositoryConvertMirror, ctx.Doer, repo, repo, "Converted repository %s from mirror to regular repository.", repo.FullName()) + audit.RecordRepositoryConvertMirror(ctx, ctx.Doer, repo) log.Trace("Repository converted from mirror to regular: %s", repo.FullName()) ctx.Flash.Success(ctx.Tr("repo.settings.convert_succeed")) @@ -838,7 +834,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.RepositoryTransferReject, ctx.Doer, ctx.Repo.Repository, ctx.Repo.Repository, "Rejected transfer of repository %s.", ctx.Repo.Repository.FullName()) + audit.RecordRepositoryTransferReject(ctx, ctx.Doer, ctx.Repo.Repository) log.Trace("Repository transfer process was cancelled: %s/%s ", ctx.Repo.Owner.Name, repo.Name) ctx.Flash.Success(ctx.Tr("repo.settings.transfer_abort_success", repoTransfer.Recipient.Name)) @@ -907,7 +903,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.RepositoryArchive, ctx.Doer, repo, repo, "Archived repository %s.", repo.FullName()) + audit.RecordRepositoryArchive(ctx, ctx.Doer, repo) ctx.Flash.Success(ctx.Tr("repo.settings.archive.success")) @@ -927,7 +923,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.RepositoryUnarchive, ctx.Doer, repo, repo, "Unarchived repository %s.", repo.FullName()) + audit.RecordRepositoryUnarchive(ctx, ctx.Doer, repo) ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success")) diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go index 73ebe04c6e464..7944d22421fc5 100644 --- a/routers/web/repo/setting/webhook.go +++ b/routers/web/repo/setting/webhook.go @@ -289,7 +289,7 @@ func createWebhook(ctx *context.Context, params webhookParams) { return } - audit.Record(ctx, orCtx.auditActionSwitch(audit_model.UserWebhookAdd, audit_model.OrganizationWebhookAdd, audit_model.RepositoryWebhookAdd, audit_model.SystemWebhookAdd), ctx.Doer, orCtx.auditScopeSwitch(), w, "Added webhook %s.", w.URL) + audit.RecordWebhookAdd(ctx, ctx.Doer, orCtx.Owner, orCtx.Repo, w) ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) ctx.Redirect(orCtx.Link) @@ -343,7 +343,7 @@ func editWebhook(ctx *context.Context, params webhookParams) { return } - audit.Record(ctx, orCtx.auditActionSwitch(audit_model.UserWebhookUpdate, audit_model.OrganizationWebhookUpdate, audit_model.RepositoryWebhookUpdate, audit_model.SystemWebhookUpdate), ctx.Doer, orCtx.auditScopeSwitch(), w, "Updated webhook %s.", w.URL) + audit.RecordWebhookUpdate(ctx, ctx.Doer, orCtx.Owner, orCtx.Repo, w) ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success")) ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) @@ -779,7 +779,7 @@ func DeleteWebhook(ctx *context.Context) { if err := webhook.DeleteWebhookByRepoID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteWebhookByRepoID: " + err.Error()) } else { - audit.Record(ctx, audit_model.RepositoryWebhookRemove, ctx.Doer, ctx.Repo.Repository, hook, "Removed webhook %s.", hook.URL) + audit.RecordWebhookRemove(ctx, ctx.Doer, nil, ctx.Repo.Repository, hook) ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index 61d575e35a218..2a64c2b8c4dc3 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -7,7 +7,6 @@ package setting import ( "net/http" - audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" @@ -72,7 +71,7 @@ func ApplicationsPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserAccessTokenAdd, ctx.Doer, ctx.Doer, t, "Added access token %s for user %s with scope %s.", t.Name, ctx.Doer.Name, t.Scope) + audit.RecordUserAccessTokenAdd(ctx, ctx.Doer, ctx.Doer, t) ctx.Flash.Success(ctx.Tr("settings.generate_token_success")) ctx.Flash.Info(t.Token) @@ -91,7 +90,7 @@ func DeleteApplication(ctx *context.Context) { if err := auth_model.DeleteAccessTokenByID(ctx, t.ID, ctx.Doer.ID); err != nil { ctx.Flash.Error("DeleteAccessTokenByID: " + err.Error()) } else { - audit.Record(ctx, audit_model.UserAccessTokenRemove, ctx.Doer, ctx.Doer, t, "Removed access token %s from user %s.", t.Name, ctx.Doer.Name) + audit.RecordUserAccessTokenRemove(ctx, ctx.Doer, ctx.Doer, t) ctx.Flash.Success(ctx.Tr("settings.delete_token_success")) } diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index baeb21272ba17..ea9bb992e46ff 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -8,7 +8,6 @@ import ( "net/http" asymkey_model "code.gitea.io/gitea/models/asymkey" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -78,7 +77,7 @@ func KeysPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserKeyPrincipalAdd, ctx.Doer, ctx.Doer, key, "Added principal key %s.", key.Name) + audit.RecordUserKeyPrincipalAdd(ctx, ctx.Doer, ctx.Doer, key) ctx.Flash.Success(ctx.Tr("settings.add_principal_success", form.Content)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") @@ -125,7 +124,7 @@ func KeysPost(ctx *context.Context) { } for _, key := range keys { - audit.Record(ctx, audit_model.UserKeyGPGAdd, ctx.Doer, ctx.Doer, key, "Added GPG key %s.", key.KeyID) + audit.RecordUserKeyGPGAdd(ctx, ctx.Doer, ctx.Doer, key) } keyIDs := "" @@ -202,7 +201,7 @@ func KeysPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserKeySSHAdd, ctx.Doer, ctx.Doer, key, "Added SSH key %s.", key.Fingerprint) + audit.RecordUserKeySSHAdd(ctx, ctx.Doer, ctx.Doer, key) ctx.Flash.Success(ctx.Tr("settings.add_key_success", form.Title)) ctx.Redirect(setting.AppSubURL + "/user/settings/keys") @@ -250,7 +249,7 @@ func DeleteKey(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserKeyGPGRemove, ctx.Doer, ctx.Doer, key, "Removed GPG key %s.", key.KeyID) + audit.RecordUserKeyGPGRemove(ctx, ctx.Doer, ctx.Doer, key) ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success")) } else { diff --git a/routers/web/user/setting/oauth2_common.go b/routers/web/user/setting/oauth2_common.go index 9fca776146d70..11074599514cb 100644 --- a/routers/web/user/setting/oauth2_common.go +++ b/routers/web/user/setting/oauth2_common.go @@ -8,7 +8,6 @@ import ( "fmt" "net/http" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" @@ -35,16 +34,6 @@ func (oa *OAuth2CommonHandlers) ownerID() int64 { return 0 } -func (oa *OAuth2CommonHandlers) auditActionSwitch(user, org, system audit_model.Action) audit_model.Action { - if oa.Owner == nil { - return system - } - if oa.Owner.IsOrganization() { - return org - } - return user -} - func (oa *OAuth2CommonHandlers) renderEditPage(ctx *context.Context) { app := ctx.Data["App"].(*auth.OAuth2Application) ctx.Data["FormActionPath"] = fmt.Sprintf("%s/%d", oa.BasePathEditPrefix, app.ID) @@ -81,7 +70,7 @@ func (oa *OAuth2CommonHandlers) AddApp(ctx *context.Context) { return } - audit.Record(ctx, oa.auditActionSwitch(audit_model.UserOAuth2ApplicationAdd, audit_model.OrganizationOAuth2ApplicationAdd, audit_model.SystemOAuth2ApplicationAdd), oa.Doer, oa.Owner, app, "Created OAuth2 application %s.", app.Name) + audit.RecordOAuth2ApplicationAdd(ctx, oa.Doer, oa.Owner, app) // render the edit page with secret ctx.Flash.Success(ctx.Tr("settings.create_oauth2_application_success"), true) @@ -138,7 +127,7 @@ func (oa *OAuth2CommonHandlers) EditSave(ctx *context.Context) { ctx.Data["App"] = app - audit.Record(ctx, oa.auditActionSwitch(audit_model.UserOAuth2ApplicationUpdate, audit_model.OrganizationOAuth2ApplicationUpdate, audit_model.SystemOAuth2ApplicationUpdate), oa.Doer, oa.Owner, app, "Updated OAuth2 application %s.", app.Name) + audit.RecordOAuth2ApplicationUpdate(ctx, oa.Doer, oa.Owner, app) ctx.Flash.Success(ctx.Tr("settings.update_oauth2_application_success")) ctx.Redirect(oa.BasePathList) @@ -166,7 +155,7 @@ func (oa *OAuth2CommonHandlers) RegenerateSecret(ctx *context.Context) { return } - audit.Record(ctx, oa.auditActionSwitch(audit_model.UserOAuth2ApplicationSecret, audit_model.OrganizationOAuth2ApplicationSecret, audit_model.SystemOAuth2ApplicationSecret), oa.Doer, oa.Owner, app, "Regenerated secret for OAuth2 application %s.", app.Name) + audit.RecordOAuth2ApplicationSecret(ctx, oa.Doer, oa.Owner, app) ctx.Flash.Success(ctx.Tr("settings.update_oauth2_application_success"), true) oa.renderEditPage(ctx) @@ -189,7 +178,7 @@ func (oa *OAuth2CommonHandlers) DeleteApp(ctx *context.Context) { return } - audit.Record(ctx, oa.auditActionSwitch(audit_model.UserOAuth2ApplicationRemove, audit_model.OrganizationOAuth2ApplicationRemove, audit_model.SystemOAuth2ApplicationRemove), oa.Doer, oa.Owner, app, "Removed OAuth2 application %s.", app.Name) + audit.RecordOAuth2ApplicationRemove(ctx, oa.Doer, oa.Owner, app) ctx.Flash.Success(ctx.Tr("settings.remove_oauth2_application_success")) ctx.JSONRedirect(oa.BasePathList) @@ -222,7 +211,7 @@ func (oa *OAuth2CommonHandlers) RevokeGrant(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserOAuth2ApplicationRevoke, oa.Doer, oa.Owner, grant, "Revoked OAuth2 grant for application %s.", app.Name) + audit.RecordUserOAuth2ApplicationRevoke(ctx, oa.Doer, oa.Owner, app, grant) ctx.Flash.Success(ctx.Tr("settings.revoke_oauth2_grant_success")) ctx.JSONRedirect(oa.BasePathList) diff --git a/routers/web/user/setting/security/2fa.go b/routers/web/user/setting/security/2fa.go index 3fa056c5fce45..bda53d1fab0a1 100644 --- a/routers/web/user/setting/security/2fa.go +++ b/routers/web/user/setting/security/2fa.go @@ -12,7 +12,6 @@ import ( "net/http" "strings" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" @@ -51,7 +50,7 @@ func RegenerateScratchTwoFactor(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserTwoFactorRegenerate, ctx.Doer, ctx.Doer, t, "Regenerated two-factor authentication secret for user %s.", ctx.Doer.Name) + audit.RecordUserTwoFactorRegenerate(ctx, ctx.Doer, ctx.Doer, t) ctx.Flash.Success(ctx.Tr("settings.twofa_scratch_token_regenerated", token)) ctx.Redirect(setting.AppSubURL + "/user/settings/security") @@ -82,7 +81,7 @@ func DisableTwoFactor(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserTwoFactorDisable, ctx.Doer, ctx.Doer, t, "Disabled two-factor authentication for user %s.", ctx.Doer.Name) + audit.RecordUserTwoFactorDisable(ctx, ctx.Doer, ctx.Doer, t) ctx.Flash.Success(ctx.Tr("settings.twofa_disabled")) ctx.Redirect(setting.AppSubURL + "/user/settings/security") @@ -250,7 +249,7 @@ func EnrollTwoFactorPost(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserTwoFactorEnable, ctx.Doer, ctx.Doer, ctx.Doer, "Enabled two-factor authentication for user %s.", ctx.Doer.Name) + audit.RecordUserTwoFactorEnable(ctx, ctx.Doer, ctx.Doer) ctx.Flash.Success(ctx.Tr("settings.twofa_enrolled", token)) ctx.Redirect(setting.AppSubURL + "/user/settings/security") diff --git a/routers/web/user/setting/security/openid.go b/routers/web/user/setting/security/openid.go index d127397422e6a..fffaf138c5bb6 100644 --- a/routers/web/user/setting/security/openid.go +++ b/routers/web/user/setting/security/openid.go @@ -6,7 +6,6 @@ package security import ( "net/http" - audit_model "code.gitea.io/gitea/models/audit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/openid" "code.gitea.io/gitea/modules/context" @@ -100,7 +99,7 @@ func settingsOpenIDVerify(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserOpenIDAdd, ctx.Doer, ctx.Doer, oid, "Associated OpenID %s to user %s.", oid.URI, ctx.Doer.Name) + audit.RecordUserOpenIDAdd(ctx, ctx.Doer, ctx.Doer, oid) log.Trace("Associated OpenID %s to user %s", id, ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.add_openid_success")) @@ -121,7 +120,7 @@ func DeleteOpenID(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserOpenIDRemove, ctx.Doer, ctx.Doer, oid, "Removed OpenID %s from user %s.", oid.URI, ctx.Doer.Name) + audit.RecordUserOpenIDAdd(ctx, ctx.Doer, ctx.Doer, oid) log.Trace("OpenID address deleted: %s", ctx.Doer.Name) diff --git a/routers/web/user/setting/security/security.go b/routers/web/user/setting/security/security.go index 199c8b0aa6026..49a3ffd95f247 100644 --- a/routers/web/user/setting/security/security.go +++ b/routers/web/user/setting/security/security.go @@ -8,7 +8,6 @@ import ( "net/http" "sort" - audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" @@ -56,7 +55,7 @@ func DeleteAccountLink(ctx *context.Context) { return } - audit.Record(ctx, audit_model.UserExternalLoginRemove, ctx.Doer, ctx.Doer, ctx.Doer, "Removed external login %s for user %s.", user.ExternalID, ctx.Doer.Name) + audit.RecordUserExternalLoginRemove(ctx, ctx.Doer, ctx.Doer, user) ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success")) diff --git a/routers/web/user/setting/security/webauthn.go b/routers/web/user/setting/security/webauthn.go index d5a0f628dc323..487debbeadfc8 100644 --- a/routers/web/user/setting/security/webauthn.go +++ b/routers/web/user/setting/security/webauthn.go @@ -9,7 +9,6 @@ import ( "strconv" "time" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" wa "code.gitea.io/gitea/modules/auth/webauthn" "code.gitea.io/gitea/modules/context" @@ -108,7 +107,7 @@ func WebauthnRegisterPost(ctx *context.Context) { } _ = ctx.Session.Delete("webauthnName") - audit.Record(ctx, audit_model.UserWebAuthAdd, ctx.Doer, ctx.Doer, dbCred, "Added WebAuthn key %s for user %s.", dbCred.Name, ctx.Doer.Name) + audit.RecordUserWebAuthAdd(ctx, ctx.Doer, ctx.Doer, dbCred) ctx.JSON(http.StatusCreated, cred) } @@ -127,7 +126,7 @@ func WebauthnDelete(ctx *context.Context) { ctx.ServerError("DeleteCredential", err) return } else if ok { - audit.Record(ctx, audit_model.UserWebAuthRemove, ctx.Doer, ctx.Doer, cred, "Removed WebAuthn key %s from user %s.", cred.Name, ctx.Doer.Name) + audit.RecordUserWebAuthRemove(ctx, ctx.Doer, ctx.Doer, cred) } ctx.JSONRedirect(setting.AppSubURL + "/user/settings/security") diff --git a/routers/web/user/setting/webhooks.go b/routers/web/user/setting/webhooks.go index 793a3118692e3..2a830ed8ac2f1 100644 --- a/routers/web/user/setting/webhooks.go +++ b/routers/web/user/setting/webhooks.go @@ -6,7 +6,6 @@ package setting import ( "net/http" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/base" @@ -48,7 +47,7 @@ func DeleteWebhook(ctx *context.Context) { if err := webhook.DeleteWebhookByOwnerID(ctx, ctx.Doer.ID, hook.ID); err != nil { ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error()) } else { - audit.Record(ctx, audit_model.UserWebhookRemove, ctx.Doer, ctx.Doer, hook, "Removed webhook %s.", hook.URL) + audit.RecordWebhookRemove(ctx, ctx.Doer, ctx.Doer, nil, hook) ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) } diff --git a/services/asymkey/deploy_key.go b/services/asymkey/deploy_key.go index 588799bfbdad1..ab470fe7fe530 100644 --- a/services/asymkey/deploy_key.go +++ b/services/asymkey/deploy_key.go @@ -10,7 +10,6 @@ import ( "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -43,7 +42,7 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error return err } - audit.Record(ctx, audit_model.RepositoryDeployKeyRemove, doer, repo, key, "Removed deploy key %s.", key.Name) + audit.RecordRepositoryDeployKeyRemove(ctx, doer, repo, key) return asymkey_model.RewriteAllPublicKeys(ctx) } diff --git a/services/asymkey/ssh_key.go b/services/asymkey/ssh_key.go index 50852b3306d64..612fe48ffb737 100644 --- a/services/asymkey/ssh_key.go +++ b/services/asymkey/ssh_key.go @@ -7,7 +7,6 @@ import ( "context" asymkey_model "code.gitea.io/gitea/models/asymkey" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/services/audit" @@ -50,12 +49,12 @@ func DeletePublicKey(ctx context.Context, doer *user_model.User, id int64) (err committer.Close() if key.Type == asymkey_model.KeyTypePrincipal { - audit.Record(ctx, audit_model.UserKeyPrincipalRemove, doer, owner, key, "Removed principal key %s.", key.Name) + audit.RecordUserKeyPrincipalRemove(ctx, doer, owner, key) return asymkey_model.RewriteAllPrincipalKeys(ctx) } - audit.Record(ctx, audit_model.UserKeySSHRemove, doer, owner, key, "Removed SSH key %s.", key.Fingerprint) + audit.RecordUserKeySSHRemove(ctx, doer, owner, key) return asymkey_model.RewriteAllPublicKeys(ctx) } diff --git a/services/audit/audit.go b/services/audit/audit.go index 84db3ed598b25..48ebe40038c4b 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package audit @@ -7,7 +7,6 @@ import ( "context" "fmt" "net" - "time" asymkey_model "code.gitea.io/gitea/models/asymkey" audit_model "code.gitea.io/gitea/models/audit" @@ -18,7 +17,6 @@ import ( secret_model "code.gitea.io/gitea/models/secret" user_model "code.gitea.io/gitea/models/user" webhook_model "code.gitea.io/gitea/models/webhook" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web/middleware" ) @@ -86,16 +84,6 @@ func (d TypeDescriptor) HTMLURL() string { return "" } -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"` -} - func Init() error { if !setting.Audit.Enabled { return nil @@ -104,33 +92,6 @@ func Init() error { return initAuditFile() } -func Record(ctx context.Context, action audit_model.Action, actor *user_model.User, scope, target any, message string, v ...any) { - if !setting.Audit.Enabled { - return - } - - e := BuildEvent(ctx, action, actor, scope, target, message, v...) - - if err := writeToFile(e); err != nil { - log.Error("Error writing audit event to file: %v", err) - } - if err := writeToDatabase(ctx, e); err != nil { - log.Error("Error writing audit event to database: %v", err) - } -} - -func BuildEvent(ctx context.Context, action audit_model.Action, actor *user_model.User, scope, target any, message string, v ...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), - } -} - func scopeToDescription(scope any) TypeDescriptor { if scope == nil { return TypeDescriptor{audit_model.TypeSystem, 0, nil} diff --git a/services/audit/audit_test.go b/services/audit/audit_test.go index a42fcfc08d55e..f2fdbfcd95543 100644 --- a/services/audit/audit_test.go +++ b/services/audit/audit_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package audit @@ -41,19 +41,19 @@ func TestBuildEvent(t *testing.T) { equal( &Event{ - Action: audit_model.UserUpdate, + 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: "Updated settings of user TestUser.", + Message: "Created user TestUser.", }, - BuildEvent( + buildEvent( ctx, - audit_model.UserUpdate, + audit_model.UserCreate, doer, u, u, - "Updated settings of user %s.", + "Created user %s.", u.Name, ), ) @@ -65,7 +65,7 @@ func TestBuildEvent(t *testing.T) { Target: TypeDescriptor{Type: "push_mirror", ID: 4, Object: m}, Message: "Added push mirror for repository TestUser/TestRepo.", }, - BuildEvent( + buildEvent( ctx, audit_model.RepositoryMirrorPushAdd, doer, @@ -76,12 +76,12 @@ func TestBuildEvent(t *testing.T) { ), ) - e := BuildEvent(ctx, audit_model.UserUpdate, doer, u, u, "") + e := buildEvent(ctx, audit_model.UserCreate, doer, u, u, "") assert.Empty(t, e.IPAddress) ctx = middleware.WithContextRequest(ctx, &http.Request{RemoteAddr: "127.0.0.1:1234"}) - e = BuildEvent(ctx, audit_model.UserUpdate, doer, u, u, "") + e = buildEvent(ctx, audit_model.UserCreate, doer, u, u, "") assert.Equal(t, "127.0.0.1", e.IPAddress) } diff --git a/services/audit/database.go b/services/audit/database.go index 7a62adce9cf8a..e8a4cbd27968e 100644 --- a/services/audit/database.go +++ b/services/audit/database.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package audit diff --git a/services/audit/display.go b/services/audit/display.go index a9f139e6bac6f..dfd6c20f4b405 100644 --- a/services/audit/display.go +++ b/services/audit/display.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package audit diff --git a/services/audit/file.go b/services/audit/file.go index 01e31411a32ff..70a236921943b 100644 --- a/services/audit/file.go +++ b/services/audit/file.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package audit diff --git a/services/audit/file_test.go b/services/audit/file_test.go index daff408692ffb..acad109a74067 100644 --- a/services/audit/file_test.go +++ b/services/audit/file_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package audit @@ -25,7 +25,7 @@ func TestWriteEventAsJSON(t *testing.T) { ctx := middleware.WithContextRequest(context.Background(), &http.Request{RemoteAddr: "127.0.0.1:1234"}) - e := BuildEvent( + e := buildEvent( ctx, audit_model.RepositoryMirrorPushAdd, doer, diff --git a/services/audit/helper.go b/services/audit/helper.go index 36617b197c35c..0ff8b1087f5c0 100644 --- a/services/audit/helper.go +++ b/services/audit/helper.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package audit @@ -22,31 +22,3 @@ func NewAuthenticationSourceUser() *user_model.User { LowerName: "authenticationsource", } } - -func UserActiveString(isActive bool) string { - if isActive { - return "active" - } - return "inactive" -} - -func UserAdminString(isAdmin bool) string { - if isAdmin { - return "admin" - } - return "normal user" -} - -func UserRestrictedString(isRestricted bool) string { - if isRestricted { - return "restricted" - } - return "unrestricted" -} - -func PublicString(isPublic bool) string { - if isPublic { - return "public" - } - return "private" -} diff --git a/services/audit/record.go b/services/audit/record.go new file mode 100644 index 0000000000000..121ffa0638557 --- /dev/null +++ b/services/audit/record.go @@ -0,0 +1,503 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package audit + +import ( + "context" + "fmt" + "time" + + asymkey_model "code.gitea.io/gitea/models/asymkey" + audit_model "code.gitea.io/gitea/models/audit" + auth_model "code.gitea.io/gitea/models/auth" + git_model "code.gitea.io/gitea/models/git" + organization_model "code.gitea.io/gitea/models/organization" + perm_model "code.gitea.io/gitea/models/perm" + repository_model "code.gitea.io/gitea/models/repo" + secret_model "code.gitea.io/gitea/models/secret" + user_model "code.gitea.io/gitea/models/user" + webhook_model "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" +) + +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"` +} + +func buildEvent(ctx context.Context, action audit_model.Action, actor *user_model.User, scope, target any, message string, v ...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), + } +} + +func record(ctx context.Context, action audit_model.Action, actor *user_model.User, scope, target any, message string, v ...any) { + if !setting.Audit.Enabled { + return + } + + e := buildEvent(ctx, action, actor, scope, target, message, v...) + + if err := writeToFile(e); err != nil { + log.Error("Error writing audit event to file: %v", err) + } + if err := writeToDatabase(ctx, e); err != nil { + log.Error("Error writing audit event to database: %v", err) + } +} + +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) +} + +func RecordUserCreate(ctx context.Context, doer, user *user_model.User) { + 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) + } +} + +func RecordUserDelete(ctx context.Context, doer, user *user_model.User) { + 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) + } +} + +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) +} + +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) +} + +func RecordUserActive(ctx context.Context, doer, user *user_model.User) { + status := "active" + if !user.IsActive { + status = "inactive" + } + + record(ctx, audit_model.UserActive, doer, user, user, "Changed activation status of user %s to %s.", user.Name, status) +} + +func RecordUserRestricted(ctx context.Context, doer, user *user_model.User) { + status := "restricted" + if !user.IsRestricted { + status = "unrestricted" + } + + record(ctx, audit_model.UserRestricted, doer, user, user, "Changed restricted status of user %s to %s.", user.Name, status) +} + +func RecordUserAdmin(ctx context.Context, doer, user *user_model.User) { + status := "admin" + if !user.IsAdmin { + status = "normal user" + } + + record(ctx, audit_model.UserAdmin, doer, user, user, "Changed admin status of user %s to %s.", user.Name, status) +} + +func RecordUserName(ctx context.Context, doer, user *user_model.User) { + 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) + } +} + +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) +} + +func RecordUserPasswordReset(ctx context.Context, doer *user_model.User) { + record(ctx, audit_model.UserPasswordReset, doer, doer, doer, "Requested passwort reset for user %s.", doer.Name) +} + +func RecordUserVisibility(ctx context.Context, doer, user *user_model.User) { + 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()) + } +} + +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) +} + +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) +} + +func RecordUserEmailActivate(ctx context.Context, doer, user *user_model.User, email *user_model.EmailAddress) { + status := "active" + if !email.IsActivated { + 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) +} + +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) +} + +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) +} + +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) +} + +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) +} + +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) +} + +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) +} + +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) +} + +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) +} + +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) +} + +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) +} + +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) +} + +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) +} + +func RecordOAuth2ApplicationAdd(ctx context.Context, doer, user *user_model.User, app *auth_model.OAuth2Application) { + if user == nil { + record(ctx, audit_model.SystemOAuth2ApplicationAdd, doer, nil, app, "Created instance-wide OAuth2 application %s", 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) + } else { + record(ctx, audit_model.UserOAuth2ApplicationAdd, doer, user, app, "Created OAuth2 application %s for user %s", 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, nil, app, "Updated instance-wide OAuth2 application %s", 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) + } else { + record(ctx, audit_model.UserOAuth2ApplicationUpdate, doer, user, app, "Updated OAuth2 application %s of user %s", 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, nil, app, "Regenerated secret for instance-wide OAuth2 application %s", 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) + } else { + record(ctx, audit_model.UserOAuth2ApplicationSecret, doer, user, app, "Regenerated secret for OAuth2 application %s of user %s", 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) +} + +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) +} + +func RecordOAuth2ApplicationRemove(ctx context.Context, doer, user *user_model.User, app *auth_model.OAuth2Application) { + if user == nil { + record(ctx, audit_model.SystemOAuth2ApplicationRemove, doer, nil, app, "Removed instance-wide OAuth2 application %s", 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) + } else { + record(ctx, audit_model.UserOAuth2ApplicationRemove, doer, user, app, "Removed OAuth2 application %s of user %s", 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) +} + +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) +} + +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) +} + +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) +} + +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) +} + +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) +} + +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.Name) + } else if owner.IsOrganization() { + record(ctx, audit_model.OrganizationSecretAdd, doer, owner, secret, "Added secret %s for organization %s.", secret.Name, owner.Name) + } else { + record(ctx, audit_model.UserSecretAdd, doer, owner, secret, "Added secret %s for user %s.", 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.Name) + } else if owner.IsOrganization() { + record(ctx, audit_model.OrganizationSecretUpdate, doer, owner, secret, "Updated secret %s of organization %s.", secret.Name, owner.Name) + } else { + record(ctx, audit_model.UserSecretUpdate, doer, owner, secret, "Updated secret %s of user %s.", 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.Name) + } else if owner.IsOrganization() { + record(ctx, audit_model.OrganizationSecretRemove, doer, owner, secret, "Removed secret %s of organization %s.", secret.Name, owner.Name) + } else { + record(ctx, audit_model.UserSecretRemove, doer, owner, secret, "Removed secret %s of user %s.", 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, nil, hook, "Added instance-wide webhook %s.", hook.URL) + } else if repo != nil { + record(ctx, audit_model.RepositoryWebhookAdd, doer, repo, hook, "Added webhook %s for repository %s.", hook.URL, repo.Name) + } else if owner.IsOrganization() { + record(ctx, audit_model.OrganizationWebhookAdd, doer, owner, hook, "Added webhook %s for organization %s.", hook.URL, owner.Name) + } else { + record(ctx, audit_model.UserWebhookAdd, doer, owner, hook, "Added webhook %s for user %s.", 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, nil, hook, "Updated instance-wide webhook %s.", hook.URL) + } else if repo != nil { + record(ctx, audit_model.RepositoryWebhookUpdate, doer, repo, hook, "Updated webhook %s of repository %s.", hook.URL, repo.Name) + } else if owner.IsOrganization() { + record(ctx, audit_model.OrganizationWebhookUpdate, doer, owner, hook, "Updated webhook %s of organization %s.", hook.URL, owner.Name) + } else { + record(ctx, audit_model.UserWebhookUpdate, doer, owner, hook, "Updated webhook %s of user %s.", 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, nil, hook, "Removed instance-wide webhook %s.", hook.URL) + } else if repo != nil { + record(ctx, audit_model.RepositoryWebhookRemove, doer, repo, hook, "Removed webhook %s of repository %s.", hook.URL, repo.Name) + } else if owner.IsOrganization() { + record(ctx, audit_model.OrganizationWebhookRemove, doer, owner, hook, "Removed webhook %s of organization %s.", hook.URL, owner.Name) + } else { + record(ctx, audit_model.UserWebhookRemove, doer, owner, hook, "Removed webhook %s of user %s.", 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) +} + +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) +} + +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) +} + +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()) +} + +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) +} + +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) +} + +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()) +} + +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()) +} + +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()) +} + +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()) +} + +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()) +} + +func RecordRepositoryName(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { + record(ctx, audit_model.RepositoryName, doer, repo, repo, "Changed repository name to %s.", repo.FullName()) +} + +func RecordRepositoryVisibility(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { + status := "public" + if repo.IsPrivate { + status = "private" + } + + record(ctx, audit_model.RepositoryVisibility, doer, repo, repo, "Changed visibility of repository %s to %s.", 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()) +} + +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 mirror to regular repository.", 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 for repository %s.", 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 for repository %s.", 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()) +} + +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 from %s to %s.", repo.OwnerName, newOwner.Name) +} + +func RecordRepositoryTransferAccept(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, oldOwner *user_model.User) { + record(ctx, audit_model.RepositoryTransferAccept, doer, repo, repo, "Accepted repository transfer from %s to %s.", oldOwner.Name, repo.OwnerName) +} + +func RecordRepositoryTransferReject(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { + record(ctx, audit_model.RepositoryTransferReject, doer, repo, repo, "Rejected transfer of repository %s.", 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()) +} + +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()) +} + +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()) +} + +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()) +} + +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()) +} + +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()) +} + +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) +} + +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()) +} + +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()) +} + +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()) +} + +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()) +} + +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()) +} + +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()) +} + +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()) +} + +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()) +} + +func RecordSystemAuthenticationSourceAdd(ctx context.Context, doer *user_model.User, authSource *auth_model.Source) { + record(ctx, audit_model.SystemAuthenticationSourceAdd, doer, nil, authSource, "Created authentication source %s of type %s.", authSource.Name, authSource.Type.String()) +} + +func RecordSystemAuthenticationSourceUpdate(ctx context.Context, doer *user_model.User, authSource *auth_model.Source) { + record(ctx, audit_model.SystemAuthenticationSourceUpdate, doer, nil, authSource, "Updated authentication source %s.", authSource.Name) +} + +func RecordSystemAuthenticationSourceRemove(ctx context.Context, doer *user_model.User, authSource *auth_model.Source) { + record(ctx, audit_model.SystemAuthenticationSourceRemove, doer, nil, authSource, "Removed authentication source %s.", authSource.Name) +} diff --git a/services/auth/basic.go b/services/auth/basic.go index 73bfe7fe9131d..554097fed0b55 100644 --- a/services/auth/basic.go +++ b/services/auth/basic.go @@ -9,7 +9,6 @@ import ( "strings" actions_model "code.gitea.io/gitea/models/actions" - audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" @@ -157,7 +156,7 @@ func validateTOTP(req *http.Request, u *user_model.User) error { if ok, err := twofa.ValidateTOTP(req.Header.Get("X-Gitea-OTP")); err != nil { return err } else if !ok { - audit.Record(req.Context(), audit_model.UserAuthenticationFailTwoFactor, u, u, u, "Failed two-factor authentication for user %s.", u.Name) + audit.RecordUserAuthenticationFailTwoFactor(req.Context(), u) return util.NewInvalidArgumentErrorf("invalid provided OTP") } diff --git a/services/auth/reverseproxy.go b/services/auth/reverseproxy.go index dbce4f7ecec5d..af0be36632cd0 100644 --- a/services/auth/reverseproxy.go +++ b/services/auth/reverseproxy.go @@ -8,7 +8,6 @@ import ( "net/http" "strings" - audit_model "code.gitea.io/gitea/models/audit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -172,7 +171,7 @@ func (r *ReverseProxy) newUser(req *http.Request) *user_model.User { return nil } - audit.Record(req.Context(), audit_model.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + audit.RecordUserCreate(req.Context(), audit.NewAuthenticationSourceUser(), user) return user } diff --git a/services/auth/source.go b/services/auth/source.go index d60e99b9511bb..e4cf22beaaddf 100644 --- a/services/auth/source.go +++ b/services/auth/source.go @@ -6,7 +6,6 @@ package auth import ( "context" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" @@ -42,7 +41,7 @@ func DeleteSource(ctx context.Context, doer *user_model.User, source *auth.Sourc _, err = db.GetEngine(ctx).ID(source.ID).Delete(new(auth.Source)) if err == nil { - audit.Record(ctx, audit_model.SystemAuthenticationSourceRemove, doer, nil, source, "Removed authentication source %s.", source.Name) + audit.RecordSystemAuthenticationSourceRemove(ctx, doer, source) } return err diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index f05abb346ba64..2a4b454838127 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -9,7 +9,6 @@ import ( "strings" asymkey_model "code.gitea.io/gitea/models/asymkey" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" auth_module "code.gitea.io/gitea/modules/auth" @@ -73,10 +72,10 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u if isAttributeSSHPublicKeySet { if addedKeys, deletedKeys := asymkey_model.SynchronizePublicKeys(ctx, user, source.authSource, sr.SSHPublicKey); len(addedKeys) > 0 || len(deletedKeys) > 0 { for _, key := range addedKeys { - audit.Record(ctx, audit_model.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), user, user, "Added SSH key %s.", key.Fingerprint) + audit.RecordUserKeySSHAdd(ctx, audit.NewAuthenticationSourceUser(), user, key) } for _, key := range deletedKeys { - audit.Record(ctx, audit_model.UserKeySSHRemove, audit.NewAuthenticationSourceUser(), user, user, "Removed SSH key %s.", key.Fingerprint) + audit.RecordUserKeySSHRemove(ctx, audit.NewAuthenticationSourceUser(), user, key) } if err := asymkey_model.RewriteAllPublicKeys(ctx); err != nil { @@ -105,12 +104,12 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u return user, err } - audit.Record(ctx, audit_model.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + audit.RecordUserCreate(ctx, audit.NewAuthenticationSourceUser(), user) if isAttributeSSHPublicKeySet { if addedKeys := asymkey_model.AddPublicKeysBySource(ctx, user, source.authSource, sr.SSHPublicKey); len(addedKeys) > 0 { for _, key := range addedKeys { - audit.Record(ctx, audit_model.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), user, user, "Added SSH key %s.", key.Fingerprint) + audit.RecordUserKeySSHAdd(ctx, audit.NewAuthenticationSourceUser(), user, key) } if err := asymkey_model.RewriteAllPublicKeys(ctx); err != nil { diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index cb666f2ff07b4..baac4d9b37c5d 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -9,7 +9,6 @@ import ( "strings" asymkey_model "code.gitea.io/gitea/models/asymkey" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" @@ -135,7 +134,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { if err != nil { log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.authSource.Name, su.Username, err) } else { - audit.Record(ctx, audit_model.UserCreate, audit.NewAuthenticationSourceUser(), usr, usr, "Created user %s.", usr.Name) + audit.RecordUserCreate(ctx, audit.NewAuthenticationSourceUser(), usr) if isAttributeSSHPublicKeySet { log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", source.authSource.Name, usr.Name) @@ -143,7 +142,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { sshKeysNeedUpdate = true for _, key := range addedKeys { - audit.Record(ctx, audit_model.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), usr, usr, "Added SSH key %s.", key.Fingerprint) + audit.RecordUserKeySSHAdd(ctx, audit.NewAuthenticationSourceUser(), usr, key) } } } @@ -159,10 +158,10 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { sshKeysNeedUpdate = true for _, key := range addedKeys { - audit.Record(ctx, audit_model.UserKeySSHAdd, audit.NewAuthenticationSourceUser(), usr, usr, "Added SSH key %s.", key.Fingerprint) + audit.RecordUserKeySSHAdd(ctx, audit.NewAuthenticationSourceUser(), usr, key) } for _, key := range deletedKeys { - audit.Record(ctx, audit_model.UserKeySSHRemove, audit.NewAuthenticationSourceUser(), usr, usr, "Removed SSH key %s.", key.Fingerprint) + audit.RecordUserKeySSHRemove(ctx, audit.NewAuthenticationSourceUser(), usr, key) } } } diff --git a/services/auth/source/pam/source_authenticate.go b/services/auth/source/pam/source_authenticate.go index 23debe57e37d1..44bcaffc9417a 100644 --- a/services/auth/source/pam/source_authenticate.go +++ b/services/auth/source/pam/source_authenticate.go @@ -8,7 +8,6 @@ import ( "fmt" "strings" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/pam" @@ -69,7 +68,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u return user, err } - audit.Record(ctx, audit_model.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + audit.RecordUserCreate(ctx, audit.NewAuthenticationSourceUser(), user) return user, nil } diff --git a/services/auth/source/smtp/source_authenticate.go b/services/auth/source/smtp/source_authenticate.go index 6dafd81790793..355521c91c33d 100644 --- a/services/auth/source/smtp/source_authenticate.go +++ b/services/auth/source/smtp/source_authenticate.go @@ -10,7 +10,6 @@ import ( "net/textproto" "strings" - audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/util" @@ -84,7 +83,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u return user, err } - audit.Record(ctx, audit_model.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + audit.RecordUserCreate(ctx, audit.NewAuthenticationSourceUser(), user) return user, nil } diff --git a/services/auth/source/source_group_sync.go b/services/auth/source/source_group_sync.go index 1331bae109e7a..fe13a5db2a427 100644 --- a/services/auth/source/source_group_sync.go +++ b/services/auth/source/source_group_sync.go @@ -8,7 +8,6 @@ import ( "fmt" "code.gitea.io/gitea/models" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/organization" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" @@ -107,14 +106,14 @@ func syncGroupsToTeamsCached(ctx context.Context, user *user_model.User, orgTeam return err } - audit.Record(ctx, audit_model.OrganizationTeamMemberAdd, audit.NewAuthenticationSourceUser(), org, team, "Added user %s to team %s/%s.", user.Name, org.Name, team.Name) + audit.RecordOrganizationTeamMemberAdd(ctx, audit.NewAuthenticationSourceUser(), org, team, user) } else if action == syncRemove && isMember { if err := models.RemoveTeamMember(ctx, team, user.ID); err != nil { log.Error("group sync: Could not remove user from team: %v", err) return err } - audit.Record(ctx, audit_model.OrganizationTeamMemberRemove, audit.NewAuthenticationSourceUser(), org, team, "Removed user %s from team %s/%s.", user.Name, org.Name, team.Name) + audit.RecordOrganizationTeamMemberRemove(ctx, audit.NewAuthenticationSourceUser(), org, team, user) } } } diff --git a/services/auth/sspi.go b/services/auth/sspi.go index 5d8af940fb4ae..4ccc9474603a5 100644 --- a/services/auth/sspi.go +++ b/services/auth/sspi.go @@ -10,7 +10,6 @@ import ( "strings" "sync" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" @@ -182,7 +181,7 @@ func (s *SSPI) newUser(ctx context.Context, username string, cfg *sspi.Source) ( return nil, err } - audit.Record(ctx, audit_model.UserCreate, audit.NewAuthenticationSourceUser(), user, user, "Created user %s.", user.Name) + audit.RecordUserCreate(ctx, audit.NewAuthenticationSourceUser(), user) return user, nil } diff --git a/services/externalaccount/user.go b/services/externalaccount/user.go index e28d248b965dd..2367ecfeca1c9 100644 --- a/services/externalaccount/user.go +++ b/services/externalaccount/user.go @@ -7,7 +7,6 @@ import ( "context" "strings" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/auth" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" @@ -55,7 +54,7 @@ func LinkAccountToUser(ctx context.Context, user *user_model.User, gothUser goth return err } - audit.Record(ctx, audit_model.UserExternalLoginAdd, user, user, user, "Added external login %s for user %s using provider %s.", externalLoginUser.ExternalID, user.Name, gothUser.Provider) + audit.RecordUserExternalLoginAdd(ctx, user, user, externalLoginUser) externalID := externalLoginUser.ExternalID diff --git a/services/org/org.go b/services/org/org.go index baf1e094b4a70..c0275fb5ac560 100644 --- a/services/org/org.go +++ b/services/org/org.go @@ -8,7 +8,6 @@ import ( "fmt" "code.gitea.io/gitea/models" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" org_model "code.gitea.io/gitea/models/organization" packages_model "code.gitea.io/gitea/models/packages" @@ -58,7 +57,7 @@ func DeleteOrganization(ctx context.Context, doer *user_model.User, org *org_mod return err } - audit.Record(ctx, audit_model.OrganizationDelete, doer, org, org, "Deleted organization %s.", org.Name) + audit.RecordUserDelete(ctx, doer, org.AsUser()) // FIXME: system notice // Note: There are something just cannot be roll back, diff --git a/services/org/repo.go b/services/org/repo.go index b545167ddcc84..ffcfd2fede316 100644 --- a/services/org/repo.go +++ b/services/org/repo.go @@ -8,7 +8,6 @@ import ( "errors" "code.gitea.io/gitea/models" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" @@ -31,7 +30,7 @@ func TeamAddRepository(ctx context.Context, doer *user_model.User, t *organizati return err } - audit.Record(ctx, audit_model.RepositoryCollaboratorTeamAdd, doer, repo, t, "Added team %s as collaborator for %s.", t.Name, repo.FullName()) + audit.RecordRepositoryCollaboratorTeamAdd(ctx, doer, repo, t) return nil } diff --git a/services/repository/branch.go b/services/repository/branch.go index 4f9a11fd5bfb8..941bf162fc05b 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -11,7 +11,6 @@ import ( "code.gitea.io/gitea/models" actions_model "code.gitea.io/gitea/models/actions" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -508,7 +507,7 @@ func SetRepoDefaultBranch(ctx context.Context, doer *user_model.User, repo *repo return err } - audit.Record(ctx, audit_model.RepositoryBranchDefault, doer, repo, repo, "Changed default branch from %s to %s.", oldDefaultBranchName, newBranchName) + audit.RecordRepositoryBranchDefault(ctx, doer, repo) notify_service.ChangeDefaultBranch(ctx, repo) diff --git a/services/repository/delete.go b/services/repository/delete.go index f44421fde8dfb..82133b5dd46d4 100644 --- a/services/repository/delete.go +++ b/services/repository/delete.go @@ -12,7 +12,6 @@ import ( activities_model "code.gitea.io/gitea/models/activities" admin_model "code.gitea.io/gitea/models/admin" asymkey_model "code.gitea.io/gitea/models/asymkey" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -422,7 +421,7 @@ func RemoveRepositoryFromTeam(ctx context.Context, doer *user_model.User, t *org return err } - audit.Record(ctx, audit_model.RepositoryCollaboratorTeamRemove, doer, repo, t, "Removed team %s as collaborator from %s.", t.Name, repo.FullName()) + audit.RecordRepositoryCollaboratorTeamRemove(ctx, doer, repo, t) return nil } diff --git a/services/repository/fork.go b/services/repository/fork.go index d82f5e4f7c41e..9dd7a933b5aa3 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -9,7 +9,6 @@ import ( "strings" "time" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" @@ -205,7 +204,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork notify_service.ForkRepository(ctx, doer, opts.BaseRepo, repo) - audit.Record(ctx, audit_model.RepositoryCreateFork, doer, repo, repo, "Created fork %s of repository %s.", repo.FullName(), opts.BaseRepo.FullName()) + audit.RecordRepositoryCreateFork(ctx, doer, repo, opts.BaseRepo) return repo, nil } @@ -241,7 +240,7 @@ func ConvertForkToNormalRepository(ctx context.Context, doer *user_model.User, r return err } - audit.Record(ctx, audit_model.RepositoryConvertFork, doer, repo, repo, "Converted repository %s from fork to regular repository.", repo.FullName()) + audit.RecordRepositoryConvertFork(ctx, doer, repo) return nil } diff --git a/services/repository/repository.go b/services/repository/repository.go index 22f5a5e1944c5..3b78740501611 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -7,7 +7,6 @@ import ( "context" "fmt" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -50,7 +49,7 @@ func CreateRepository(ctx context.Context, doer, owner *user_model.User, opts Cr notify_service.CreateRepository(ctx, doer, owner, repo) - audit.Record(ctx, audit_model.RepositoryCreate, doer, repo, repo, "Created repository %s.", repo.FullName()) + audit.RecordRepositoryCreate(ctx, doer, repo) return repo, nil } @@ -74,7 +73,7 @@ func DeleteRepository(ctx context.Context, doer *user_model.User, repo *repo_mod return err } - audit.Record(ctx, audit_model.RepositoryDelete, doer, repo, repo, "Deleted repository %s.", repo.FullName()) + audit.RecordRepositoryDelete(ctx, doer, repo) return nil } diff --git a/services/repository/transfer.go b/services/repository/transfer.go index 4a1344160990e..f95ef3099110c 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -10,7 +10,6 @@ import ( "strings" "code.gitea.io/gitea/models" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" @@ -55,14 +54,14 @@ func TransferOwnership(ctx context.Context, doer, newOwner *user_model.User, rep return err } - audit.Record(ctx, audit_model.RepositoryTransferAccept, doer, newRepo, newRepo, "Accepted repository transfer from %s to %s.", oldOwner.Name, newRepo.OwnerName) + audit.RecordRepositoryTransferAccept(ctx, doer, newRepo, oldOwner) for _, team := range teams { if err := models.AddRepository(ctx, team, newRepo); err != nil { return err } - audit.Record(ctx, audit_model.RepositoryCollaboratorTeamAdd, doer, newRepo, team, "Added team %s as collaborator for %s.", team.Name, newRepo.FullName()) + audit.RecordRepositoryCollaboratorTeamAdd(ctx, doer, newRepo, team) } notify_service.TransferRepository(ctx, doer, repo, oldOwner.Name) @@ -361,7 +360,7 @@ func ChangeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo repo.Name = newRepoName - audit.Record(ctx, audit_model.RepositoryName, doer, repo, repo, "Changed repository name from %s to %s.", oldRepoName, newRepoName) + audit.RecordRepositoryName(ctx, doer, repo) notify_service.RenameRepository(ctx, doer, repo, oldRepoName) @@ -411,7 +410,7 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use return err } - audit.Record(ctx, audit_model.RepositoryTransferStart, doer, repo, repo, "Started repository transfer from %s to %s.", repo.OwnerName, newOwner.Name) + audit.RecordRepositoryTransferStart(ctx, doer, repo, newOwner) // notify users who are able to accept / reject transfer notify_service.RepoPendingTransfer(ctx, doer, newOwner, repo) diff --git a/services/secrets/secrets.go b/services/secrets/secrets.go index a0b16ef2daa5a..051c507bc5f9c 100644 --- a/services/secrets/secrets.go +++ b/services/secrets/secrets.go @@ -6,7 +6,6 @@ package secrets import ( "context" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" secret_model "code.gitea.io/gitea/models/secret" @@ -34,14 +33,7 @@ func CreateOrUpdateSecret(ctx context.Context, doer, owner *user_model.User, rep return nil, false, err } - audit.Record(ctx, - auditActionSwitch(owner, repo, audit_model.UserSecretAdd, audit_model.OrganizationSecretAdd, audit_model.RepositorySecretAdd), - doer, - auditScopeSwitch(owner, repo), - s, - "Added secret %s.", - s.Name, - ) + audit.RecordSecretAdd(ctx, doer, owner, repo, s) return s, true, nil } @@ -52,14 +44,7 @@ func CreateOrUpdateSecret(ctx context.Context, doer, owner *user_model.User, rep return nil, false, err } - audit.Record(ctx, - auditActionSwitch(owner, repo, audit_model.UserSecretUpdate, audit_model.OrganizationSecretUpdate, audit_model.RepositorySecretUpdate), - doer, - auditScopeSwitch(owner, repo), - s, - "Added secret %s.", - s.Name, - ) + audit.RecordSecretUpdate(ctx, doer, owner, repo, s) return s, false, nil } @@ -105,14 +90,7 @@ func deleteSecret(ctx context.Context, doer, owner *user_model.User, repo *repo_ return err } - audit.Record(ctx, - auditActionSwitch(owner, repo, audit_model.UserSecretRemove, audit_model.OrganizationSecretRemove, audit_model.RepositorySecretRemove), - doer, - auditScopeSwitch(owner, repo), - s, - "Removed secret %s.", - s.Name, - ) + audit.RecordSecretRemove(ctx, doer, owner, repo, s) return nil } @@ -130,20 +108,3 @@ func tryGetRepositoryID(repo *repo_model.Repository) int64 { } return repo.ID } - -func auditActionSwitch(owner *user_model.User, repo *repo_model.Repository, userAction, orgAction, repoAction audit_model.Action) audit_model.Action { - if owner == nil { - return repoAction - } - if owner.IsOrganization() { - return orgAction - } - return userAction -} - -func auditScopeSwitch(owner *user_model.User, repo *repo_model.Repository) any { - if owner != nil { - return owner - } - return repo -} diff --git a/services/user/email.go b/services/user/email.go index e834a039e1e76..61d774dae86f2 100644 --- a/services/user/email.go +++ b/services/user/email.go @@ -8,7 +8,6 @@ import ( "errors" "strings" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" @@ -70,7 +69,7 @@ func AddOrSetPrimaryEmailAddress(ctx context.Context, doer, u *user_model.User, return err } - audit.Record(ctx, audit_model.UserEmailPrimaryChange, doer, u, email, "Changed primary email of user %s to %s.", u.Name, email.Email) + audit.RecordUserEmailPrimaryChange(ctx, doer, u, email) return nil } @@ -123,7 +122,7 @@ func ReplacePrimaryEmailAddress(ctx context.Context, doer, u *user_model.User, e return err } - audit.Record(ctx, audit_model.UserEmailPrimaryChange, doer, u, email, "Changed primary email of user %s to %s.", u.Name, email.Email) + audit.RecordUserEmailPrimaryChange(ctx, doer, u, email) } else { u.Email = emailStr @@ -167,7 +166,7 @@ func AddEmailAddresses(ctx context.Context, doer, u *user_model.User, emailsToAd } for _, email := range emails { - audit.Record(ctx, audit_model.UserEmailAdd, doer, u, email, "Added email %s to user %s.", email.Email, u.Name) + audit.RecordUserEmailAdd(ctx, doer, u, email) } return nil @@ -195,7 +194,7 @@ func DeleteEmailAddresses(ctx context.Context, doer, u *user_model.User, emailsT } for _, email := range emails { - audit.Record(ctx, audit_model.UserEmailRemove, doer, u, email, "Removed email %s from user %s.", email.Email, u.Name) + audit.RecordUserEmailRemove(ctx, doer, u, email) } return nil diff --git a/services/user/email_test.go b/services/user/email_test.go index 9efa857ab9a97..39e45047559df 100644 --- a/services/user/email_test.go +++ b/services/user/email_test.go @@ -28,7 +28,7 @@ func TestAddOrSetPrimaryEmailAddress(t *testing.T) { assert.NotEqual(t, "new-primary@example.com", primary.Email) assert.Equal(t, user.Email, primary.Email) - assert.NoError(t, AddOrSetPrimaryEmailAddress(db.DefaultContext, user, "new-primary@example.com")) + assert.NoError(t, AddOrSetPrimaryEmailAddress(db.DefaultContext, user, user, "new-primary@example.com")) primary, err = user_model.GetPrimaryEmailAddressOfUser(db.DefaultContext, user.ID) assert.NoError(t, err) @@ -39,7 +39,7 @@ func TestAddOrSetPrimaryEmailAddress(t *testing.T) { assert.NoError(t, err) assert.Len(t, emails, 2) - assert.NoError(t, AddOrSetPrimaryEmailAddress(db.DefaultContext, user, "user27@example.com")) + assert.NoError(t, AddOrSetPrimaryEmailAddress(db.DefaultContext, user, user, "user27@example.com")) primary, err = user_model.GetPrimaryEmailAddressOfUser(db.DefaultContext, user.ID) assert.NoError(t, err) @@ -66,7 +66,7 @@ func TestReplacePrimaryEmailAddress(t *testing.T) { assert.NotEqual(t, "primary-13@example.com", primary.Email) assert.Equal(t, user.Email, primary.Email) - assert.NoError(t, ReplacePrimaryEmailAddress(db.DefaultContext, user, "primary-13@example.com")) + assert.NoError(t, ReplacePrimaryEmailAddress(db.DefaultContext, user, user, "primary-13@example.com")) primary, err = user_model.GetPrimaryEmailAddressOfUser(db.DefaultContext, user.ID) assert.NoError(t, err) @@ -77,15 +77,16 @@ func TestReplacePrimaryEmailAddress(t *testing.T) { assert.NoError(t, err) assert.Len(t, emails, 1) - assert.NoError(t, ReplacePrimaryEmailAddress(db.DefaultContext, user, "primary-13@example.com")) + assert.NoError(t, ReplacePrimaryEmailAddress(db.DefaultContext, user, user, "primary-13@example.com")) }) t.Run("Organization", func(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) org := unittest.AssertExistsAndLoadBean(t, &organization_model.Organization{ID: 3}) assert.Equal(t, "org3@example.com", org.Email) - assert.NoError(t, ReplacePrimaryEmailAddress(db.DefaultContext, org.AsUser(), "primary-org@example.com")) + assert.NoError(t, ReplacePrimaryEmailAddress(db.DefaultContext, user, org.AsUser(), "primary-org@example.com")) assert.Equal(t, "primary-org@example.com", org.Email) }) diff --git a/services/user/update.go b/services/user/update.go index e1b20a3122ec8..baefa3c61d946 100644 --- a/services/user/update.go +++ b/services/user/update.go @@ -8,7 +8,6 @@ import ( "fmt" "code.gitea.io/gitea/models" - audit_model "code.gitea.io/gitea/models/audit" auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" password_module "code.gitea.io/gitea/modules/auth/password" @@ -176,17 +175,16 @@ func UpdateUser(ctx context.Context, doer, u *user_model.User, opts *UpdateOptio } if isActiveChanged { - audit.Record(ctx, audit_model.UserActive, doer, u, u, "Changed activation status of user %s to %s.", u.Name, audit.UserActiveString(u.IsActive)) + audit.RecordUserActive(ctx, doer, u) } if isAdminChanged { - audit.Record(ctx, audit_model.UserAdmin, doer, u, u, "Changed admin status of user %s to %s.", u.Name, audit.UserAdminString(u.IsAdmin)) + audit.RecordUserAdmin(ctx, doer, u) } if isRestrictedChanged { - audit.Record(ctx, audit_model.UserRestricted, doer, u, u, "Changed restricted status of user %s to %s.", u.Name, audit.UserRestrictedString(u.IsRestricted)) + audit.RecordUserRestricted(ctx, doer, u) } if visibilityChanged { - audit.Record(ctx, audit_model.UserVisibility, doer, u, u, "Changed visibility of user %s to %s.", u.Name, u.Visibility.String()) - // audit.Record(ctx, audit_model.OrganizationVisibility, ctx.Doer, org, org, "Changed visibility of organization %s from %s to %s.", org.Name, oldVisibility.String(), org.Visibility.String()) + audit.RecordUserVisibility(ctx, doer, u) } return nil @@ -254,10 +252,10 @@ func UpdateAuth(ctx context.Context, doer, u *user_model.User, opts *UpdateAuthO } if passwordChanged { - audit.Record(ctx, audit_model.UserPassword, doer, u, u, "Changed password of user %s.", u.Name) + audit.RecordUserPassword(ctx, doer, u) } if loginSourceChanged { - audit.Record(ctx, audit_model.UserAuthenticationSource, doer, u, u, "Changed authentication source of user %s.", u.Name) + audit.RecordUserAuthenticationSource(ctx, doer, u) } return nil diff --git a/services/user/user.go b/services/user/user.go index 3830f411df5f4..bf5abda414c20 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -12,7 +12,6 @@ import ( "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" packages_model "code.gitea.io/gitea/models/packages" @@ -118,8 +117,7 @@ func RenameUser(ctx context.Context, doer, u *user_model.User, newUserName strin return err } - audit.Record(ctx, audit_model.UserName, doer, u, u, "Changed user name from %s to %s.", oldUserName, newUserName) - // audit.Record(ctx, audit_model.OrganizationName, doer, org, org, "Changed organization name from %s to %s.", oldName, org.Name) + audit.RecordUserName(ctx, doer, u) return nil } @@ -283,7 +281,7 @@ func DeleteUser(ctx context.Context, doer, u *user_model.User, purge bool) error } } - audit.Record(ctx, audit_model.UserDelete, doer, u, u, "Deleted user %s.", u.Name) + audit.RecordUserDelete(ctx, doer, u) return nil } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index e5580ba5af2be..b54339285dc34 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -10,7 +10,6 @@ import ( "os" "strings" - audit_model "code.gitea.io/gitea/models/audit" repo_model "code.gitea.io/gitea/models/repo" system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unit" @@ -359,7 +358,7 @@ func DeleteWiki(ctx context.Context, doer *user_model.User, repo *repo_model.Rep system_model.RemoveAllWithNotice(ctx, "Delete repository wiki", repo.WikiPath()) - audit.Record(ctx, audit_model.RepositoryWikiDelete, doer, repo, repo, "Deleted wiki of repository %s.", repo.FullName()) + audit.RecordRepositoryWikiDelete(ctx, doer, repo) return nil } diff --git a/tests/integration/audit_test.go b/tests/integration/audit_test.go index 1731a38f9b547..47be3c72ebe38 100644 --- a/tests/integration/audit_test.go +++ b/tests/integration/audit_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. +// Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package integration @@ -42,10 +42,7 @@ func TestAuditLogging(t *testing.T) { MakeRequest(t, req, http.StatusCreated) req = NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/user1_audit_org", &api.EditOrgOption{ - Description: "A new description", - Website: "https://try.gitea.io/new", - Location: "Earth", - Visibility: "private", + Visibility: "private", }).AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) @@ -55,8 +52,7 @@ func TestAuditLogging(t *testing.T) { MakeRequest(t, req, http.StatusCreated) req = NewRequestWithJSON(t, "PATCH", "/api/v1/repos/user1/audit_repo", &api.EditRepoOption{ - Description: util.ToPointer("A new description"), - Private: util.ToPointer(true), + Private: util.ToPointer(true), }).AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) @@ -86,11 +82,6 @@ func TestAuditLogging(t *testing.T) { Scope: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: setting.AppURL + "user1_audit_org"}, Target: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: setting.AppURL + "user1_audit_org"}, }, - { - Action: audit_model.OrganizationUpdate, - Scope: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: setting.AppURL + "user1_audit_org"}, - Target: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: setting.AppURL + "user1_audit_org"}, - }, { Action: audit_model.OrganizationVisibility, Scope: TestTypeDescriptor{Type: audit_model.TypeOrganization, DisplayName: "user1_audit_org", HTMLURL: setting.AppURL + "user1_audit_org"}, @@ -101,11 +92,6 @@ func TestAuditLogging(t *testing.T) { Scope: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: setting.AppURL + "user1/audit_repo"}, Target: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: setting.AppURL + "user1/audit_repo"}, }, - { - Action: audit_model.RepositoryUpdate, - Scope: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: setting.AppURL + "user1/audit_repo"}, - Target: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: setting.AppURL + "user1/audit_repo"}, - }, { Action: audit_model.RepositoryVisibility, Scope: TestTypeDescriptor{Type: audit_model.TypeRepository, DisplayName: "user1/audit_repo", HTMLURL: setting.AppURL + "user1/audit_repo"}, From 3b1336a3b7789a894b681b57dbc39969f51fbb71 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Thu, 8 Feb 2024 11:08:22 +0000 Subject: [PATCH 35/44] Log system startup/shutdown. --- cmd/admin_auth.go | 7 +++-- cmd/admin_auth_ldap.go | 5 ++-- cmd/admin_user_change_password.go | 3 +- cmd/admin_user_create.go | 4 +-- cmd/admin_user_delete.go | 2 +- cmd/admin_user_generate_access_token.go | 2 +- cmd/web.go | 9 ++++++ .../administration/audit-logging.en-us.md | 5 ++-- models/audit/action.go | 3 +- models/user/user_system.go | 16 +++++++++++ routers/web/auth/auth.go | 2 +- routers/web/auth/oauth.go | 4 +-- services/audit/audit.go | 8 +++++- services/audit/audit_test.go | 15 ++++++++-- services/audit/display.go | 13 +++++++++ services/audit/helper.go | 24 ---------------- services/audit/record.go | 28 ++++++++++++------- services/auth/reverseproxy.go | 2 +- .../auth/source/ldap/source_authenticate.go | 10 +++---- services/auth/source/ldap/source_sync.go | 14 +++++----- .../auth/source/pam/source_authenticate.go | 2 +- .../auth/source/smtp/source_authenticate.go | 2 +- services/auth/source/source_group_sync.go | 4 +-- services/auth/sspi.go | 2 +- templates/shared/audit/list.tmpl | 8 ++++-- 25 files changed, 119 insertions(+), 75 deletions(-) delete mode 100644 services/audit/helper.go diff --git a/cmd/admin_auth.go b/cmd/admin_auth.go index 6327333e08f44..b2f3679d09c7e 100644 --- a/cmd/admin_auth.go +++ b/cmd/admin_auth.go @@ -11,6 +11,7 @@ import ( auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/services/audit" auth_service "code.gitea.io/gitea/services/auth" @@ -96,7 +97,7 @@ func createSource(ctx context.Context, source *auth_model.Source) error { return err } - audit.RecordSystemAuthenticationSourceAdd(ctx, audit.NewCLIUser(), source) + audit.RecordSystemAuthenticationSourceAdd(ctx, user_model.NewCLIUser(), source) return nil } @@ -106,7 +107,7 @@ func updateSource(ctx context.Context, source *auth_model.Source) error { return err } - audit.RecordSystemAuthenticationSourceUpdate(ctx, audit.NewCLIUser(), source) + audit.RecordSystemAuthenticationSourceUpdate(ctx, user_model.NewCLIUser(), source) return nil } @@ -128,5 +129,5 @@ func runDeleteAuth(c *cli.Context) error { return err } - return auth_service.DeleteSource(ctx, audit.NewCLIUser(), source) + return auth_service.DeleteSource(ctx, user_model.NewCLIUser(), source) } diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index 938c49e9be1d4..a52ac9cdfd31e 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -9,6 +9,7 @@ import ( "strings" "code.gitea.io/gitea/models/auth" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/auth/source/ldap" @@ -359,7 +360,7 @@ func (a *authService) addLdapSource(c *cli.Context, authType auth.Type, args ... return err } - audit.RecordSystemAuthenticationSourceAdd(ctx, audit.NewCLIUser(), authSource) + audit.RecordSystemAuthenticationSourceAdd(ctx, user_model.NewCLIUser(), authSource) return nil } @@ -389,7 +390,7 @@ func (a *authService) updateLdapSource(c *cli.Context, authType auth.Type) error return err } - audit.RecordSystemAuthenticationSourceUpdate(ctx, audit.NewCLIUser(), authSource) + audit.RecordSystemAuthenticationSourceUpdate(ctx, user_model.NewCLIUser(), authSource) return nil } diff --git a/cmd/admin_user_change_password.go b/cmd/admin_user_change_password.go index fbb3266cf94a1..5aa4e1846b51d 100644 --- a/cmd/admin_user_change_password.go +++ b/cmd/admin_user_change_password.go @@ -11,7 +11,6 @@ import ( "code.gitea.io/gitea/modules/auth/password" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/services/audit" user_service "code.gitea.io/gitea/services/user" "github.com/urfave/cli/v2" @@ -67,7 +66,7 @@ func runChangePassword(c *cli.Context) error { Password: optional.Some(c.String("password")), MustChangePassword: mustChangePassword, } - if err := user_service.UpdateAuth(ctx, audit.NewCLIUser(), user, opts); err != nil { + if err := user_service.UpdateAuth(ctx, user_model.NewCLIUser(), user, opts); err != nil { switch { case errors.Is(err, password.ErrMinLength): return fmt.Errorf("Password is not long enough. Needs to be at least %d", setting.MinPasswordLength) diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index d6b26c127d859..acf5c9e94f523 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -154,7 +154,7 @@ func runCreateUser(c *cli.Context) error { return fmt.Errorf("CreateUser: %w", err) } - audit.RecordUserCreate(ctx, audit.NewCLIUser(), u) + audit.RecordUserCreate(ctx, user_model.NewCLIUser(), u) if c.Bool("access-token") { t := &auth_model.AccessToken{ @@ -166,7 +166,7 @@ func runCreateUser(c *cli.Context) error { return err } - audit.RecordUserAccessTokenAdd(ctx, audit.NewCLIUser(), u, t) + audit.RecordUserAccessTokenAdd(ctx, user_model.NewCLIUser(), u, t) fmt.Printf("Access token was successfully created... %s\n", t.Token) } diff --git a/cmd/admin_user_delete.go b/cmd/admin_user_delete.go index 4bbe291c49c82..4beb4801b6b4a 100644 --- a/cmd/admin_user_delete.go +++ b/cmd/admin_user_delete.go @@ -79,5 +79,5 @@ func runDeleteUser(c *cli.Context) error { return fmt.Errorf("The user %s does not match the provided id %d", user.Name, c.Int64("id")) } - return user_service.DeleteUser(ctx, audit.NewCLIUser(), user, c.Bool("purge")) + return user_service.DeleteUser(ctx, user_model.NewCLIUser(), user, c.Bool("purge")) } diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go index 1a3ebe6b1bc55..7a20e9bcaea76 100644 --- a/cmd/admin_user_generate_access_token.go +++ b/cmd/admin_user_generate_access_token.go @@ -87,7 +87,7 @@ func runGenerateAccessToken(c *cli.Context) error { return err } - audit.RecordUserAccessTokenAdd(ctx, audit.NewCLIUser(), user, t) + audit.RecordUserAccessTokenAdd(ctx, user_model.NewCLIUser(), user, t) if c.Bool("raw") { fmt.Printf("%s\n", t.Token) diff --git a/cmd/web.go b/cmd/web.go index 01386251becfa..e62b4ac2829bc 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -15,6 +15,8 @@ import ( _ "net/http/pprof" // Used for debugging if enabled and a web server is running + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" @@ -23,6 +25,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers/install" + "code.gitea.io/gitea/services/audit" "github.com/felixge/fgprof" "github.com/urfave/cli/v2" @@ -209,7 +212,13 @@ func serveInstalled(ctx *cli.Context) error { // Set up Chi routes webRoutes := routers.NormalRoutes() + + audit.RecordSystemStartup(db.DefaultContext, user_model.NewCLIUser(), setting.AppVer) + err := listen(webRoutes, true) + + audit.RecordSystemShutdown(db.DefaultContext, user_model.NewCLIUser()) + <-graceful.GetManager().Done() log.Info("PID: %d Gitea Web Finished", os.Getpid()) log.GetManager().Close() diff --git a/docs/content/administration/audit-logging.en-us.md b/docs/content/administration/audit-logging.en-us.md index b527fd9344f6c..5af37304e4114 100644 --- a/docs/content/administration/audit-logging.en-us.md +++ b/docs/content/administration/audit-logging.en-us.md @@ -40,7 +40,6 @@ Audit events are grouped by `user`, `organization`, `repository` and `system`. | - | - | | `user:impersonation` | Admin impersonating user | | `user:create` | Created user | -| `user:update` | Updated settings of user | | `user:delete` | Deleted user | | `user:authentication:fail:twofactor` | Failed two-factor authentication for user | | `user:authentication:source` | Changed authentication source of user | @@ -90,7 +89,6 @@ Audit events are grouped by `user`, `organization`, `repository` and `system`. | Event | Description | | - | - | | `organization:create` | Created organization | -| `organization:update` | Updated settings of organization | | `organization:delete` | Deleted organization | | `organization:name` | Changed organization name | | `organization:visibility` | Changed visibility of organization | @@ -117,7 +115,6 @@ Audit events are grouped by `user`, `organization`, `repository` and `system`. | - | - | | `repository:create` | Crated repository | | `repository:create:fork` | Created fork of repository | -| `repository:update` | Updated settings of repository | | `repository:archive` | Archived repository | | `repository:unarchive` | Unarchived repository | | `repository:delete` | Deleted repository | @@ -157,6 +154,8 @@ Audit events are grouped by `user`, `organization`, `repository` and `system`. | Event | Description | | - | - | +| `system:startup` | System startup | +| `system:shutdown` | System shutdown | | `system:webhook:add` | Added webhook | | `system:webhook:update` | Updated webhook | | `system:webhook:remove` | Removed webhook | diff --git a/models/audit/action.go b/models/audit/action.go index 3dfa4f0c0aea0..caf89c15346ab 100644 --- a/models/audit/action.go +++ b/models/audit/action.go @@ -75,7 +75,6 @@ const ( RepositoryCreate Action = "repository:create" RepositoryCreateFork Action = "repository:create:fork" - RepositoryUpdate Action = "repository:update" RepositoryArchive Action = "repository:archive" RepositoryUnarchive Action = "repository:unarchive" RepositoryDelete Action = "repository:delete" @@ -111,6 +110,8 @@ const ( RepositorySecretUpdate Action = "repository:secret:update" RepositorySecretRemove Action = "repository:secret:remove" + SystemStartup Action = "system:startup" + SystemShutdown Action = "system:shutdown" SystemWebhookAdd Action = "system:webhook:add" SystemWebhookUpdate Action = "system:webhook:update" SystemWebhookRemove Action = "system:webhook:remove" diff --git a/models/user/user_system.go b/models/user/user_system.go index 612cdb2caef26..a50bad05ceddb 100644 --- a/models/user/user_system.go +++ b/models/user/user_system.go @@ -68,3 +68,19 @@ func NewActionsUser() *User { func (u *User) IsActions() bool { return u != nil && u.ID == ActionsUserID } + +func NewCLIUser() *User { + return &User{ + ID: -3, + Name: "CLI", + LowerName: "cli", + } +} + +func NewAuthenticationSourceUser() *User { + return &User{ + ID: -4, + Name: "AuthenticationSource", + LowerName: "authenticationsource", + } +} diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 301c21a00d2f0..e6195598e37d5 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -585,7 +585,7 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *us return false } - audit.RecordUserCreate(ctx, audit.NewAuthenticationSourceUser(), u) + audit.RecordUserCreate(ctx, user_model.NewAuthenticationSourceUser(), u) log.Trace("Account created: %s", u.Name) return true diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 28903ccb0b19f..4559f2c3fc284 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -1153,7 +1153,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model SetLastLogin: true, } opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser) - if err := user_service.UpdateUser(ctx, audit.NewAuthenticationSourceUser(), u, opts); err != nil { + if err := user_service.UpdateUser(ctx, user_model.NewAuthenticationSourceUser(), u, opts); err != nil { ctx.ServerError("UpdateUser", err) return } @@ -1190,7 +1190,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model opts := &user_service.UpdateOptions{} opts.IsAdmin, opts.IsRestricted = getUserAdminAndRestrictedFromGroupClaims(oauth2Source, &gothUser) if opts.IsAdmin.Has() || opts.IsRestricted.Has() { - if err := user_service.UpdateUser(ctx, audit.NewAuthenticationSourceUser(), u, opts); err != nil { + if err := user_service.UpdateUser(ctx, user_model.NewAuthenticationSourceUser(), u, opts); err != nil { ctx.ServerError("UpdateUser", err) return } diff --git a/services/audit/audit.go b/services/audit/audit.go index 48ebe40038c4b..40d4173efcfb0 100644 --- a/services/audit/audit.go +++ b/services/audit/audit.go @@ -92,8 +92,10 @@ func Init() error { return initAuditFile() } +var systemObject struct{} + func scopeToDescription(scope any) TypeDescriptor { - if scope == nil { + if scope == &systemObject { return TypeDescriptor{audit_model.TypeSystem, 0, nil} } @@ -106,6 +108,10 @@ func scopeToDescription(scope any) TypeDescriptor { } func typeToDescription(val any) TypeDescriptor { + if val == &systemObject { + return TypeDescriptor{audit_model.TypeSystem, 0, nil} + } + switch t := val.(type) { case *repository_model.Repository: return TypeDescriptor{audit_model.TypeRepository, t.ID, val} diff --git a/services/audit/audit_test.go b/services/audit/audit_test.go index f2fdbfcd95543..de65d334b4646 100644 --- a/services/audit/audit_test.go +++ b/services/audit/audit_test.go @@ -92,7 +92,11 @@ func TestScopeToDescription(t *testing.T) { Expected TypeDescriptor }{ { - Scope: nil, + Scope: nil, + ShouldPanic: true, + }, + { + Scope: &systemObject, Expected: TypeDescriptor{Type: audit_model.TypeSystem, ID: 0}, }, { @@ -148,8 +152,15 @@ func TestTypeToDescription(t *testing.T) { Expected Expected }{ { - ShouldPanic: true, Type: nil, + ShouldPanic: true, + }, + { + Type: &systemObject, + Expected: Expected{ + TypeDescriptor: TypeDescriptor{Type: audit_model.TypeSystem, ID: 0}, + DisplayName: "System", + }, }, { Type: &user_model.User{ID: 1, Name: "TestUser"}, diff --git a/services/audit/display.go b/services/audit/display.go index dfd6c20f4b405..03cafbd7c5a26 100644 --- a/services/audit/display.go +++ b/services/audit/display.go @@ -33,6 +33,17 @@ func FindEvents(ctx context.Context, opts *audit_model.EventSearchOptions) ([]*E func fromDatabaseEvents(ctx context.Context, evs []*audit_model.Event) []*Event { c := cache{} + users := make(map[int64]TypeDescriptor) + for _, systemUser := range []*user_model.User{ + user_model.NewGhostUser(), + user_model.NewActionsUser(), + user_model.NewCLIUser(), + user_model.NewAuthenticationSourceUser(), + } { + users[systemUser.ID] = typeToDescription(systemUser) + } + c[audit_model.TypeUser] = users + events := make([]*Event, 0, len(evs)) for _, e := range evs { events = append(events, fromDatabaseEvent(ctx, e, c)) @@ -65,6 +76,8 @@ func resolveType(ctx context.Context, t audit_model.ObjectType, id int64, c cach } switch t { + case audit_model.TypeSystem: + td, has = typeToDescription(&systemObject), true case audit_model.TypeRepository: td, has = getTypeDescriptorByID[repository_model.Repository](ctx, id) case audit_model.TypeUser: diff --git a/services/audit/helper.go b/services/audit/helper.go deleted file mode 100644 index 0ff8b1087f5c0..0000000000000 --- a/services/audit/helper.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package audit - -import ( - user_model "code.gitea.io/gitea/models/user" -) - -func NewCLIUser() *user_model.User { - return &user_model.User{ - ID: -1, - Name: "CLI", - LowerName: "cli", - } -} - -func NewAuthenticationSourceUser() *user_model.User { - return &user_model.User{ - ID: -1, - Name: "AuthenticationSource", - LowerName: "authenticationsource", - } -} diff --git a/services/audit/record.go b/services/audit/record.go index 121ffa0638557..df7d6d6109608 100644 --- a/services/audit/record.go +++ b/services/audit/record.go @@ -205,7 +205,7 @@ func RecordUserAccessTokenRemove(ctx context.Context, doer, user *user_model.Use func RecordOAuth2ApplicationAdd(ctx context.Context, doer, user *user_model.User, app *auth_model.OAuth2Application) { if user == nil { - record(ctx, audit_model.SystemOAuth2ApplicationAdd, doer, nil, app, "Created instance-wide OAuth2 application %s", app.Name) + record(ctx, audit_model.SystemOAuth2ApplicationAdd, doer, &systemObject, app, "Created instance-wide OAuth2 application %s", 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) } else { @@ -215,7 +215,7 @@ func RecordOAuth2ApplicationAdd(ctx context.Context, doer, user *user_model.User func RecordOAuth2ApplicationUpdate(ctx context.Context, doer, user *user_model.User, app *auth_model.OAuth2Application) { if user == nil { - record(ctx, audit_model.SystemOAuth2ApplicationUpdate, doer, nil, app, "Updated instance-wide OAuth2 application %s", app.Name) + record(ctx, audit_model.SystemOAuth2ApplicationUpdate, doer, &systemObject, app, "Updated instance-wide OAuth2 application %s", 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) } else { @@ -225,7 +225,7 @@ func RecordOAuth2ApplicationUpdate(ctx context.Context, doer, user *user_model.U func RecordOAuth2ApplicationSecret(ctx context.Context, doer, user *user_model.User, app *auth_model.OAuth2Application) { if user == nil { - record(ctx, audit_model.SystemOAuth2ApplicationSecret, doer, nil, app, "Regenerated secret for instance-wide OAuth2 application %s", app.Name) + record(ctx, audit_model.SystemOAuth2ApplicationSecret, doer, &systemObject, app, "Regenerated secret for instance-wide OAuth2 application %s", 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) } else { @@ -243,7 +243,7 @@ func RecordUserOAuth2ApplicationRevoke(ctx context.Context, doer, owner *user_mo func RecordOAuth2ApplicationRemove(ctx context.Context, doer, user *user_model.User, app *auth_model.OAuth2Application) { if user == nil { - record(ctx, audit_model.SystemOAuth2ApplicationRemove, doer, nil, app, "Removed instance-wide OAuth2 application %s", app.Name) + record(ctx, audit_model.SystemOAuth2ApplicationRemove, doer, &systemObject, app, "Removed instance-wide OAuth2 application %s", 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) } else { @@ -307,7 +307,7 @@ func RecordSecretRemove(ctx context.Context, doer, owner *user_model.User, repo 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, nil, hook, "Added instance-wide webhook %s.", hook.URL) + record(ctx, audit_model.SystemWebhookAdd, doer, &systemObject, hook, "Added instance-wide webhook %s.", hook.URL) } else if repo != nil { record(ctx, audit_model.RepositoryWebhookAdd, doer, repo, hook, "Added webhook %s for repository %s.", hook.URL, repo.Name) } else if owner.IsOrganization() { @@ -319,7 +319,7 @@ func RecordWebhookAdd(ctx context.Context, doer, owner *user_model.User, repo *r 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, nil, hook, "Updated instance-wide webhook %s.", hook.URL) + record(ctx, audit_model.SystemWebhookUpdate, doer, &systemObject, hook, "Updated instance-wide webhook %s.", hook.URL) } else if repo != nil { record(ctx, audit_model.RepositoryWebhookUpdate, doer, repo, hook, "Updated webhook %s of repository %s.", hook.URL, repo.Name) } else if owner.IsOrganization() { @@ -331,7 +331,7 @@ func RecordWebhookUpdate(ctx context.Context, doer, owner *user_model.User, repo 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, nil, hook, "Removed instance-wide webhook %s.", hook.URL) + record(ctx, audit_model.SystemWebhookRemove, doer, &systemObject, hook, "Removed instance-wide webhook %s.", hook.URL) } else if repo != nil { record(ctx, audit_model.RepositoryWebhookRemove, doer, repo, hook, "Removed webhook %s of repository %s.", hook.URL, repo.Name) } else if owner.IsOrganization() { @@ -490,14 +490,22 @@ func RecordRepositoryDeployKeyRemove(ctx context.Context, doer *user_model.User, record(ctx, audit_model.RepositoryDeployKeyRemove, doer, repo, deployKey, "Removed deploy key %s from repository %s.", deployKey.Name, repo.FullName()) } +func RecordSystemStartup(ctx context.Context, doer *user_model.User, version string) { + record(ctx, audit_model.SystemStartup, doer, &systemObject, &systemObject, "System started [Gitea %s]", version) +} + +func RecordSystemShutdown(ctx context.Context, doer *user_model.User) { + record(ctx, audit_model.SystemShutdown, doer, &systemObject, &systemObject, "System shutdown") +} + func RecordSystemAuthenticationSourceAdd(ctx context.Context, doer *user_model.User, authSource *auth_model.Source) { - record(ctx, audit_model.SystemAuthenticationSourceAdd, doer, nil, authSource, "Created authentication source %s of type %s.", authSource.Name, authSource.Type.String()) + record(ctx, audit_model.SystemAuthenticationSourceAdd, doer, &systemObject, authSource, "Created authentication source %s of type %s.", authSource.Name, authSource.Type.String()) } func RecordSystemAuthenticationSourceUpdate(ctx context.Context, doer *user_model.User, authSource *auth_model.Source) { - record(ctx, audit_model.SystemAuthenticationSourceUpdate, doer, nil, authSource, "Updated authentication source %s.", authSource.Name) + record(ctx, audit_model.SystemAuthenticationSourceUpdate, doer, &systemObject, authSource, "Updated authentication source %s.", authSource.Name) } func RecordSystemAuthenticationSourceRemove(ctx context.Context, doer *user_model.User, authSource *auth_model.Source) { - record(ctx, audit_model.SystemAuthenticationSourceRemove, doer, nil, authSource, "Removed authentication source %s.", authSource.Name) + record(ctx, audit_model.SystemAuthenticationSourceRemove, doer, &systemObject, authSource, "Removed authentication source %s.", authSource.Name) } diff --git a/services/auth/reverseproxy.go b/services/auth/reverseproxy.go index af0be36632cd0..60f019fd83dad 100644 --- a/services/auth/reverseproxy.go +++ b/services/auth/reverseproxy.go @@ -171,7 +171,7 @@ func (r *ReverseProxy) newUser(req *http.Request) *user_model.User { return nil } - audit.RecordUserCreate(req.Context(), audit.NewAuthenticationSourceUser(), user) + audit.RecordUserCreate(req.Context(), user_model.NewAuthenticationSourceUser(), user) return user } diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index 2a4b454838127..87785bb4362bc 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -61,7 +61,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u opts.IsRestricted = optional.Some(sr.IsRestricted) } if opts.IsAdmin.Has() || opts.IsRestricted.Has() { - if err := user_service.UpdateUser(ctx, audit.NewAuthenticationSourceUser(), user, opts); err != nil { + if err := user_service.UpdateUser(ctx, user_model.NewAuthenticationSourceUser(), user, opts); err != nil { return nil, err } } @@ -72,10 +72,10 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u if isAttributeSSHPublicKeySet { if addedKeys, deletedKeys := asymkey_model.SynchronizePublicKeys(ctx, user, source.authSource, sr.SSHPublicKey); len(addedKeys) > 0 || len(deletedKeys) > 0 { for _, key := range addedKeys { - audit.RecordUserKeySSHAdd(ctx, audit.NewAuthenticationSourceUser(), user, key) + audit.RecordUserKeySSHAdd(ctx, user_model.NewAuthenticationSourceUser(), user, key) } for _, key := range deletedKeys { - audit.RecordUserKeySSHRemove(ctx, audit.NewAuthenticationSourceUser(), user, key) + audit.RecordUserKeySSHRemove(ctx, user_model.NewAuthenticationSourceUser(), user, key) } if err := asymkey_model.RewriteAllPublicKeys(ctx); err != nil { @@ -104,12 +104,12 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u return user, err } - audit.RecordUserCreate(ctx, audit.NewAuthenticationSourceUser(), user) + audit.RecordUserCreate(ctx, user_model.NewAuthenticationSourceUser(), user) if isAttributeSSHPublicKeySet { if addedKeys := asymkey_model.AddPublicKeysBySource(ctx, user, source.authSource, sr.SSHPublicKey); len(addedKeys) > 0 { for _, key := range addedKeys { - audit.RecordUserKeySSHAdd(ctx, audit.NewAuthenticationSourceUser(), user, key) + audit.RecordUserKeySSHAdd(ctx, user_model.NewAuthenticationSourceUser(), user, key) } if err := asymkey_model.RewriteAllPublicKeys(ctx); err != nil { diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index baac4d9b37c5d..3f90307dc5394 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -134,7 +134,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { if err != nil { log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.authSource.Name, su.Username, err) } else { - audit.RecordUserCreate(ctx, audit.NewAuthenticationSourceUser(), usr) + audit.RecordUserCreate(ctx, user_model.NewAuthenticationSourceUser(), usr) if isAttributeSSHPublicKeySet { log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", source.authSource.Name, usr.Name) @@ -142,7 +142,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { sshKeysNeedUpdate = true for _, key := range addedKeys { - audit.RecordUserKeySSHAdd(ctx, audit.NewAuthenticationSourceUser(), usr, key) + audit.RecordUserKeySSHAdd(ctx, user_model.NewAuthenticationSourceUser(), usr, key) } } } @@ -158,10 +158,10 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { sshKeysNeedUpdate = true for _, key := range addedKeys { - audit.RecordUserKeySSHAdd(ctx, audit.NewAuthenticationSourceUser(), usr, key) + audit.RecordUserKeySSHAdd(ctx, user_model.NewAuthenticationSourceUser(), usr, key) } for _, key := range deletedKeys { - audit.RecordUserKeySSHRemove(ctx, audit.NewAuthenticationSourceUser(), usr, key) + audit.RecordUserKeySSHRemove(ctx, user_model.NewAuthenticationSourceUser(), usr, key) } } } @@ -187,11 +187,11 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { opts.IsRestricted = optional.Some(su.IsRestricted) } - if err := user_service.UpdateUser(ctx, audit.NewAuthenticationSourceUser(), usr, opts); err != nil { + if err := user_service.UpdateUser(ctx, user_model.NewAuthenticationSourceUser(), usr, opts); err != nil { log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", source.authSource.Name, usr.Name, err) } - if err := user_service.ReplacePrimaryEmailAddress(ctx, audit.NewAuthenticationSourceUser(), usr, su.Mail); err != nil { + if err := user_service.ReplacePrimaryEmailAddress(ctx, user_model.NewAuthenticationSourceUser(), usr, su.Mail); err != nil { log.Error("SyncExternalUsers[%s]: Error updating user %s primary email %s: %v", source.authSource.Name, usr.Name, su.Mail, err) } } @@ -237,7 +237,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { opts := &user_service.UpdateOptions{ IsActive: optional.Some(false), } - if err := user_service.UpdateUser(ctx, audit.NewAuthenticationSourceUser(), usr, opts); err != nil { + if err := user_service.UpdateUser(ctx, user_model.NewAuthenticationSourceUser(), usr, opts); err != nil { log.Error("SyncExternalUsers[%s]: Error deactivating user %s: %v", source.authSource.Name, usr.Name, err) } } diff --git a/services/auth/source/pam/source_authenticate.go b/services/auth/source/pam/source_authenticate.go index 44bcaffc9417a..ad2eefd09fa82 100644 --- a/services/auth/source/pam/source_authenticate.go +++ b/services/auth/source/pam/source_authenticate.go @@ -68,7 +68,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u return user, err } - audit.RecordUserCreate(ctx, audit.NewAuthenticationSourceUser(), user) + audit.RecordUserCreate(ctx, user_model.NewAuthenticationSourceUser(), user) return user, nil } diff --git a/services/auth/source/smtp/source_authenticate.go b/services/auth/source/smtp/source_authenticate.go index 355521c91c33d..86a47b10c1806 100644 --- a/services/auth/source/smtp/source_authenticate.go +++ b/services/auth/source/smtp/source_authenticate.go @@ -83,7 +83,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u return user, err } - audit.RecordUserCreate(ctx, audit.NewAuthenticationSourceUser(), user) + audit.RecordUserCreate(ctx, user_model.NewAuthenticationSourceUser(), user) return user, nil } diff --git a/services/auth/source/source_group_sync.go b/services/auth/source/source_group_sync.go index fe13a5db2a427..a31ff8fd1788d 100644 --- a/services/auth/source/source_group_sync.go +++ b/services/auth/source/source_group_sync.go @@ -106,14 +106,14 @@ func syncGroupsToTeamsCached(ctx context.Context, user *user_model.User, orgTeam return err } - audit.RecordOrganizationTeamMemberAdd(ctx, audit.NewAuthenticationSourceUser(), org, team, user) + audit.RecordOrganizationTeamMemberAdd(ctx, user_model.NewAuthenticationSourceUser(), org, team, user) } else if action == syncRemove && isMember { if err := models.RemoveTeamMember(ctx, team, user.ID); err != nil { log.Error("group sync: Could not remove user from team: %v", err) return err } - audit.RecordOrganizationTeamMemberRemove(ctx, audit.NewAuthenticationSourceUser(), org, team, user) + audit.RecordOrganizationTeamMemberRemove(ctx, user_model.NewAuthenticationSourceUser(), org, team, user) } } } diff --git a/services/auth/sspi.go b/services/auth/sspi.go index 4ccc9474603a5..e05ec53a78b9c 100644 --- a/services/auth/sspi.go +++ b/services/auth/sspi.go @@ -181,7 +181,7 @@ func (s *SSPI) newUser(ctx context.Context, username string, cfg *sspi.Source) ( return nil, err } - audit.RecordUserCreate(ctx, audit.NewAuthenticationSourceUser(), user) + audit.RecordUserCreate(ctx, user_model.NewAuthenticationSourceUser(), user) return user, nil } diff --git a/templates/shared/audit/list.tmpl b/templates/shared/audit/list.tmpl index 88fc3fe44eb5a..002de61a9cfde 100644 --- a/templates/shared/audit/list.tmpl +++ b/templates/shared/audit/list.tmpl @@ -15,7 +15,11 @@ {{if .Actor.Object}} - {{.Actor.DisplayName}} + {{if gt .Actor.ID 0}} + {{.Actor.DisplayName}} + {{else}} + {{.Actor.DisplayName}} + {{end}} {{else}} {{ctx.Locale.Tr "admin.monitor.audit.deleted.actor"}} {{end}} @@ -34,7 +38,7 @@ {{.Message}} - {{if .Target.Object}} + {{if or .Target.Object (eq .Scope.Type "system")}} {{$url := .Target.HTMLURL}} {{if $url}} {{.Target.DisplayName}} From d09081091b86f9bd3ab1d75c171182069402094a Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Thu, 8 Feb 2024 12:13:00 +0100 Subject: [PATCH 36/44] Add audit logs to user settings. --- routers/web/repo/setting/webhook.go | 24 ----------- routers/web/user/setting/audit.go | 55 +++++++++++++++++++++++++ routers/web/web.go | 15 +++++-- services/audit/audit_test.go | 12 +++--- templates/user/settings/audit_logs.tmpl | 6 +++ templates/user/settings/navbar.tmpl | 5 +++ 6 files changed, 84 insertions(+), 33 deletions(-) create mode 100644 routers/web/user/setting/audit.go create mode 100644 templates/user/settings/audit_logs.tmpl diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go index 7944d22421fc5..3d2f3beb3f8f8 100644 --- a/routers/web/repo/setting/webhook.go +++ b/routers/web/repo/setting/webhook.go @@ -12,7 +12,6 @@ import ( "path" "strings" - audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" @@ -70,29 +69,6 @@ type ownerRepoCtx struct { NewTemplate base.TplName } -func (ctx *ownerRepoCtx) auditActionSwitch(user, org, repo, system audit_model.Action) audit_model.Action { - if ctx.IsAdmin { - return system - } - if ctx.Repo != nil { - return repo - } - if ctx.Owner.IsOrganization() { - return org - } - return user -} - -func (ctx *ownerRepoCtx) auditScopeSwitch() any { - if ctx.IsAdmin { - return nil - } - if ctx.Repo != nil { - return ctx.Repo - } - return ctx.Owner -} - // getOwnerRepoCtx determines whether this is a repo, owner, or admin (both default and system) context. func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) { if ctx.Data["PageIsRepoSettings"] == true { diff --git a/routers/web/user/setting/audit.go b/routers/web/user/setting/audit.go new file mode 100644 index 0000000000000..d412b2ea71d4d --- /dev/null +++ b/routers/web/user/setting/audit.go @@ -0,0 +1,55 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "net/http" + + audit_model "code.gitea.io/gitea/models/audit" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/audit" +) + +const ( + tplAuditLogs base.TplName = "user/settings/audit_logs" +) + +func ViewAuditLogs(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("admin.monitor.audit.title") + ctx.Data["PageIsSettingsAudit"] = true + + page := ctx.FormInt("page") + if page < 1 { + page = 1 + } + + opts := &audit_model.EventSearchOptions{ + Sort: ctx.FormString("sort"), + ScopeType: audit_model.TypeUser, + ScopeID: ctx.Doer.ID, + Paginator: &db.ListOptions{ + Page: page, + PageSize: setting.UI.Admin.NoticePagingNum, + }, + } + + ctx.Data["AuditSort"] = opts.Sort + + evs, total, err := audit.FindEvents(ctx, opts) + if err != nil { + ctx.ServerError("", err) + return + } + + ctx.Data["AuditEvents"] = evs + + pager := context.NewPagination(int(total), setting.UI.Admin.NoticePagingNum, page, 5) + pager.AddParamString("sort", opts.Sort) + ctx.Data["Page"] = pager + + ctx.HTML(http.StatusOK, tplAuditLogs) +} diff --git a/routers/web/web.go b/routers/web/web.go index 0a0269bb5dae2..859ec4a83c52b 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -381,6 +381,13 @@ func registerRoutes(m *web.Route) { } } + auditLogsEnabled := func(ctx *context.Context) { + if !setting.Audit.Enabled { + ctx.Error(http.StatusNotFound) + return + } + } + reqUnitAccess := func(unitType unit.Type, accessMode perm.AccessMode, ignoreGlobal bool) func(ctx *context.Context) { return func(ctx *context.Context) { // only check global disabled units when ignoreGlobal is false @@ -647,6 +654,8 @@ func registerRoutes(m *web.Route) { }) addWebhookEditRoutes() }, webhooksEnabled) + + m.Get("/audit_logs", auditLogsEnabled, user_setting.ViewAuditLogs) }, reqSignIn, ctxDataSet("PageIsUserSettings", true, "AllThemes", setting.UI.Themes, "EnablePackages", setting.Packages.Enabled)) m.Group("/user", func() { @@ -687,7 +696,7 @@ func registerRoutes(m *web.Route) { }) m.Group("/monitor", func() { - m.Get("/audit_logs", admin.ViewAuditLogs) + m.Get("/audit_logs", auditLogsEnabled, admin.ViewAuditLogs) m.Get("/stats", admin.MonitorStats) m.Get("/cron", admin.CronTasks) m.Get("/stacktrace", admin.Stacktrace) @@ -924,7 +933,7 @@ func registerRoutes(m *web.Route) { addSettingsVariablesRoutes() }, actions.MustEnableActions) - m.Get("/audit_logs", org_setting.ViewAuditLogs) + m.Get("/audit_logs", auditLogsEnabled, org_setting.ViewAuditLogs) m.Methods("GET,POST", "/delete", org.SettingsDelete) @@ -1104,7 +1113,7 @@ func registerRoutes(m *web.Route) { addSettingsSecretsRoutes() addSettingsVariablesRoutes() }, actions.MustEnableActions) - m.Get("/audit_logs", repo_setting.ViewAuditLogs) + m.Get("/audit_logs", auditLogsEnabled, repo_setting.ViewAuditLogs) // the follow handler must be under "settings", otherwise this incomplete repo can't be accessed m.Group("/migrate", func() { m.Post("/retry", repo.MigrateRetryPost) diff --git a/services/audit/audit_test.go b/services/audit/audit_test.go index de65d334b4646..efd20495ae1a1 100644 --- a/services/audit/audit_test.go +++ b/services/audit/audit_test.go @@ -121,7 +121,9 @@ func TestScopeToDescription(t *testing.T) { }, } for _, c := range cases { - c.Expected.Object = c.Scope + if c.Scope != &systemObject { + c.Expected.Object = c.Scope + } if c.ShouldPanic { assert.Panics(t, func() { @@ -131,10 +133,6 @@ func TestScopeToDescription(t *testing.T) { assert.Equal(t, c.Expected, scopeToDescription(c.Scope), "Unexpected descriptor for scope: %T", c.Scope) } } - - systemScope := scopeToDescription(nil) - assert.Equal(t, "System", systemScope.DisplayName()) - assert.Empty(t, systemScope.HTMLURL()) } func TestTypeToDescription(t *testing.T) { @@ -290,7 +288,9 @@ func TestTypeToDescription(t *testing.T) { }, } for _, c := range cases { - c.Expected.TypeDescriptor.Object = c.Type + if c.Type != &systemObject { + c.Expected.TypeDescriptor.Object = c.Type + } if c.ShouldPanic { assert.Panics(t, func() { diff --git a/templates/user/settings/audit_logs.tmpl b/templates/user/settings/audit_logs.tmpl new file mode 100644 index 0000000000000..dfcaadeffa835 --- /dev/null +++ b/templates/user/settings/audit_logs.tmpl @@ -0,0 +1,6 @@ +{{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings audit")}} +
+

{{ctx.Locale.Tr "admin.monitor.audit.title"}}

+ {{template "shared/audit/list" .}} +
+{{template "user/settings/layout_footer" .}} diff --git a/templates/user/settings/navbar.tmpl b/templates/user/settings/navbar.tmpl index a690d003524c7..b533551b7c056 100644 --- a/templates/user/settings/navbar.tmpl +++ b/templates/user/settings/navbar.tmpl @@ -13,6 +13,11 @@ {{ctx.Locale.Tr "settings.security"}} + {{if .EnableAuditLogs}} + + {{ctx.Locale.Tr "admin.monitor.audit.title"}} + + {{end}} {{ctx.Locale.Tr "settings.applications"}} From 558fd1aa2a5853c5cce5a72e32ad0d5fcb88856c Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Thu, 8 Feb 2024 12:14:00 +0100 Subject: [PATCH 37/44] Update docs. --- docs/content/administration/config-cheat-sheet.en-us.md | 3 +-- tests/mssql.ini.tmpl | 1 - tests/mysql.ini.tmpl | 1 - tests/pgsql.ini.tmpl | 1 - tests/sqlite.ini.tmpl | 4 +++- 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index 3d96de9b2c578..d8d8f5a899520 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -905,13 +905,12 @@ Default templates for project boards: ## Audit Log (`audit`) - `ENABLED`: **false**: Enable logging of audit events -- `APPENDER`: **\**: Comma-separated list of destinations to output to (possible values: `log` or `file`). ## File Audit Log (`audit.file`) - `FILENAME`: **\**: Set the file name for the logger. If this is a relative path this will be relative to `log.ROOT_PATH`. Defaults to `log.ROOT_PATH/audit.log`. - `ROTATE`: **true**: This enables automated audit log rotate, default is true -- `MAXIMUM_SIZE`: **false**: Maximum file size in bytes before rotating takes place (format `1000`, `1 MB`, `1 GiB`) +- `MAXIMUM_SIZE`: **256 MB**: Maximum file size in bytes before rotating takes place (format `1000`, `1 MB`, `1 GiB`) - `ROTATE_DAILY`: **true**: Rotate audit log daily, default is true - `KEEP_DAYS`: **7**: Delete the audit log file after n days, default is 7 - `COMPRESS`: **true**: Compress audit logs with gzip diff --git a/tests/mssql.ini.tmpl b/tests/mssql.ini.tmpl index 0de9759b5c1a3..f97ecb2639122 100644 --- a/tests/mssql.ini.tmpl +++ b/tests/mssql.ini.tmpl @@ -113,4 +113,3 @@ ENABLED = true [audit] ENABLED = true -APPENDER = log diff --git a/tests/mysql.ini.tmpl b/tests/mysql.ini.tmpl index 8b0abf602190b..b5d144817a4f2 100644 --- a/tests/mysql.ini.tmpl +++ b/tests/mysql.ini.tmpl @@ -122,4 +122,3 @@ ENABLED = true [audit] ENABLED = true -APPENDER = log diff --git a/tests/pgsql.ini.tmpl b/tests/pgsql.ini.tmpl index 92ea2497417a9..1afd662092957 100644 --- a/tests/pgsql.ini.tmpl +++ b/tests/pgsql.ini.tmpl @@ -134,4 +134,3 @@ ENABLED = true [audit] ENABLED = true -APPENDER = log diff --git a/tests/sqlite.ini.tmpl b/tests/sqlite.ini.tmpl index 86f46d9741b5c..389a0482de628 100644 --- a/tests/sqlite.ini.tmpl +++ b/tests/sqlite.ini.tmpl @@ -120,4 +120,6 @@ ENABLED = true [audit] ENABLED = true -APPENDER = log + +[audit.file] +FILENAME = audit.log From 745be632e633255b5e71aa3592de379246136000 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 24 Feb 2024 19:10:28 +0000 Subject: [PATCH 38/44] Add suggestions. --- docs/content/administration/audit-logging.en-us.md | 2 +- routers/web/user/setting/security/openid.go | 2 +- services/audit/record.go | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/content/administration/audit-logging.en-us.md b/docs/content/administration/audit-logging.en-us.md index 5af37304e4114..2fb001c71eadf 100644 --- a/docs/content/administration/audit-logging.en-us.md +++ b/docs/content/administration/audit-logging.en-us.md @@ -155,7 +155,7 @@ Audit events are grouped by `user`, `organization`, `repository` and `system`. | Event | Description | | - | - | | `system:startup` | System startup | -| `system:shutdown` | System shutdown | +| `system:shutdown` | Normal system shutdown (unexpected shutdowns may not be logged) | | `system:webhook:add` | Added webhook | | `system:webhook:update` | Updated webhook | | `system:webhook:remove` | Removed webhook | diff --git a/routers/web/user/setting/security/openid.go b/routers/web/user/setting/security/openid.go index fffaf138c5bb6..451599d755db4 100644 --- a/routers/web/user/setting/security/openid.go +++ b/routers/web/user/setting/security/openid.go @@ -120,7 +120,7 @@ func DeleteOpenID(ctx *context.Context) { return } - audit.RecordUserOpenIDAdd(ctx, ctx.Doer, ctx.Doer, oid) + audit.RecordUserOpenIDRemove(ctx, ctx.Doer, ctx.Doer, oid) log.Trace("OpenID address deleted: %s", ctx.Doer.Name) diff --git a/services/audit/record.go b/services/audit/record.go index df7d6d6109608..819eb3c94f6f0 100644 --- a/services/audit/record.go +++ b/services/audit/record.go @@ -491,6 +491,7 @@ func RecordRepositoryDeployKeyRemove(ctx context.Context, doer *user_model.User, } 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) } From cfebe22e35aaf2bc6031042d65bbff01eb0205f7 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 1 Mar 2024 13:30:52 +0100 Subject: [PATCH 39/44] Apply suggestions from code review Co-authored-by: delvh --- services/audit/record.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/services/audit/record.go b/services/audit/record.go index 819eb3c94f6f0..8512a593113d0 100644 --- a/services/audit/record.go +++ b/services/audit/record.go @@ -55,7 +55,7 @@ func record(ctx context.Context, action audit_model.Action, actor *user_model.Us log.Error("Error writing audit event to file: %v", err) } if err := writeToDatabase(ctx, e); err != nil { - log.Error("Error writing audit event to database: %v", err) + log.Error("Error writing audit event %+v to database: %v", e, err) } } @@ -277,7 +277,7 @@ func RecordUserKeyGPGRemove(ctx context.Context, doer, user *user_model.User, ke 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.Name) + record(ctx, audit_model.RepositorySecretAdd, doer, repo, secret, "Added secret %s for repository %s.", 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) } else { @@ -287,7 +287,7 @@ func RecordSecretAdd(ctx context.Context, doer, owner *user_model.User, repo *re 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.Name) + record(ctx, audit_model.RepositorySecretUpdate, doer, repo, secret, "Updated secret %s of repository %s.", 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) } else { @@ -297,7 +297,7 @@ func RecordSecretUpdate(ctx context.Context, doer, owner *user_model.User, repo 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.Name) + record(ctx, audit_model.RepositorySecretRemove, doer, repo, secret, "Removed secret %s of repository %s.", 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) } else { @@ -309,7 +309,7 @@ func RecordWebhookAdd(ctx context.Context, doer, owner *user_model.User, repo *r if owner == nil && repo == nil { record(ctx, audit_model.SystemWebhookAdd, doer, &systemObject, hook, "Added instance-wide webhook %s.", hook.URL) } else if repo != nil { - record(ctx, audit_model.RepositoryWebhookAdd, doer, repo, hook, "Added webhook %s for repository %s.", hook.URL, repo.Name) + record(ctx, audit_model.RepositoryWebhookAdd, doer, repo, hook, "Added webhook %s for repository %s.", 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) } else { @@ -321,7 +321,7 @@ func RecordWebhookUpdate(ctx context.Context, doer, owner *user_model.User, repo if owner == nil && repo == nil { record(ctx, audit_model.SystemWebhookUpdate, doer, &systemObject, hook, "Updated instance-wide webhook %s.", hook.URL) } else if repo != nil { - record(ctx, audit_model.RepositoryWebhookUpdate, doer, repo, hook, "Updated webhook %s of repository %s.", hook.URL, repo.Name) + record(ctx, audit_model.RepositoryWebhookUpdate, doer, repo, hook, "Updated webhook %s of repository %s.", 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) } else { @@ -333,7 +333,7 @@ func RecordWebhookRemove(ctx context.Context, doer, owner *user_model.User, repo if owner == nil && repo == nil { record(ctx, audit_model.SystemWebhookRemove, doer, &systemObject, hook, "Removed instance-wide webhook %s.", hook.URL) } else if repo != nil { - record(ctx, audit_model.RepositoryWebhookRemove, doer, repo, hook, "Removed webhook %s of repository %s.", hook.URL, repo.Name) + record(ctx, audit_model.RepositoryWebhookRemove, doer, repo, hook, "Removed webhook %s of repository %s.", 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) } else { @@ -403,15 +403,15 @@ func RecordRepositoryConvertFork(ctx context.Context, doer *user_model.User, rep } 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 mirror to regular repository.", repo.FullName()) + record(ctx, audit_model.RepositoryConvertMirror, doer, repo, repo, "Converted repository %s from pull mirror to regular repository.", 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 for repository %s.", repo.FullName()) + record(ctx, audit_model.RepositoryMirrorPushAdd, doer, repo, mirror, "Added push mirror to %s for repository %s.", 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 for repository %s.", repo.FullName()) + record(ctx, audit_model.RepositoryMirrorPushRemove, doer, repo, mirror, "Removed push mirror to %s for repository %s.", mirror.RemoteAddress, repo.FullName()) } func RecordRepositorySigningVerification(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { @@ -419,11 +419,11 @@ func RecordRepositorySigningVerification(ctx context.Context, doer *user_model.U } 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 from %s to %s.", repo.OwnerName, newOwner.Name) + record(ctx, audit_model.RepositoryTransferStart, doer, repo, repo, "Started repository transfer of %s to %s.", repo.FullName(), newOwner.Name) } func RecordRepositoryTransferAccept(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, oldOwner *user_model.User) { - record(ctx, audit_model.RepositoryTransferAccept, doer, repo, repo, "Accepted repository transfer from %s to %s.", oldOwner.Name, repo.OwnerName) + record(ctx, audit_model.RepositoryTransferAccept, doer, repo, repo, "Accepted repository transfer of %s from %s to %s.", repo.Fullname(), oldOwner.Name, repo.OwnerName) } func RecordRepositoryTransferReject(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { From b813aba2f701e12f3a530429eb964ef4f20c7068 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 2 Mar 2024 16:50:30 +0000 Subject: [PATCH 40/44] Fix migration. Change some method names and messages. --- .../administration/audit-logging.en-us.md | 6 +++--- models/audit/action.go | 6 +++--- models/migrations/v1_22/v287.go | 20 +++++++++---------- modules/setting/audit.go | 4 ++++ routers/api/v1/repo/transfer.go | 2 +- routers/web/auth/password.go | 2 +- routers/web/repo/repo.go | 2 +- routers/web/repo/setting/setting.go | 2 +- services/audit/record.go | 16 +++++++-------- services/repository/transfer.go | 4 ++-- 10 files changed, 34 insertions(+), 30 deletions(-) diff --git a/docs/content/administration/audit-logging.en-us.md b/docs/content/administration/audit-logging.en-us.md index 2fb001c71eadf..f4852e166af4c 100644 --- a/docs/content/administration/audit-logging.en-us.md +++ b/docs/content/administration/audit-logging.en-us.md @@ -48,7 +48,7 @@ Audit events are grouped by `user`, `organization`, `repository` and `system`. | `user:admin` | Changed admin status of user | | `user:name` | Changed user name | | `user:password` | Changed password of user | -| `user:password:reset` | Requested a password reset | +| `user:password:resetrequest` | Requested a password reset | | `user:visibility` | Changed visibility of user | | `user:email:primary` | Changed primary email of user | | `user:email:add` | Added email to user | @@ -126,8 +126,8 @@ Audit events are grouped by `user`, `organization`, `repository` and `system`. | `repository:mirror:push:remove` | Removed push mirror from repository | | `repository:signingverification` | Changed signing verification of repository | | `repository:transfer:start` | Started repository transfer | -| `repository:transfer:accept` | Accepted repository transfer | -| `repository:transfer:reject` | Rejected repository transfer | +| `repository:transfer:finish` | Transferred repository to new owner | +| `repository:transfer:cancel` | Canceled repository transfer | | `repository:wiki:delete` | Deleted wiki of repository | | `repository:collaborator:add` | Added user as collaborator for repository | | `repository:collaborator:access` | Changed access mode of collaborator | diff --git a/models/audit/action.go b/models/audit/action.go index caf89c15346ab..d92130f158df5 100644 --- a/models/audit/action.go +++ b/models/audit/action.go @@ -16,7 +16,7 @@ const ( UserAdmin Action = "user:admin" UserName Action = "user:name" UserPassword Action = "user:password" - UserPasswordReset Action = "user:password:reset" + UserPasswordResetRequest Action = "user:password:resetrequest" UserVisibility Action = "user:visibility" UserEmailPrimaryChange Action = "user:email:primary" UserEmailAdd Action = "user:email:add" @@ -86,8 +86,8 @@ const ( RepositoryMirrorPushRemove Action = "repository:mirror:push:remove" RepositorySigningVerification Action = "repository:signingverification" RepositoryTransferStart Action = "repository:transfer:start" - RepositoryTransferAccept Action = "repository:transfer:accept" - RepositoryTransferReject Action = "repository:transfer:reject" + RepositoryTransferFinish Action = "repository:transfer:finish" + RepositoryTransferCancel Action = "repository:transfer:cancel" RepositoryWikiDelete Action = "repository:wiki:delete" RepositoryCollaboratorAdd Action = "repository:collaborator:add" RepositoryCollaboratorAccess Action = "repository:collaborator:access" diff --git a/models/migrations/v1_22/v287.go b/models/migrations/v1_22/v287.go index bd0563fc49dfb..68aeb4af861fb 100644 --- a/models/migrations/v1_22/v287.go +++ b/models/migrations/v1_22/v287.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 - Timestamp 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"` + Message string + IPAddress string + TimestampUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL"` } return x.Sync(&AuditEvent{}) diff --git a/modules/setting/audit.go b/modules/setting/audit.go index 56fc153a09024..ee765919bf9e4 100644 --- a/modules/setting/audit.go +++ b/modules/setting/audit.go @@ -25,6 +25,10 @@ func loadAuditFrom(rootCfg ConfigProvider) { sec, err := rootCfg.GetSection("audit.file") if err == nil { + if !sec.Key("ENABLED").MustBool(false) { + return + } + opts := &log.WriterFileOption{ FileName: path.Join(Log.RootPath, "audit.log"), LogRotate: true, diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go index 0a30b3faf4ea4..4d62f0ac853aa 100644 --- a/routers/api/v1/repo/transfer.go +++ b/routers/api/v1/repo/transfer.go @@ -235,7 +235,7 @@ func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error { return err } - audit.RecordRepositoryTransferReject(ctx, ctx.Doer, ctx.Repo.Repository) + audit.RecordRepositoryTransferCancel(ctx, ctx.Doer, ctx.Repo.Repository) return nil } diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go index 9a3be99d125ee..b2f69fc7db49c 100644 --- a/routers/web/auth/password.go +++ b/routers/web/auth/password.go @@ -90,7 +90,7 @@ func ForgotPasswdPost(ctx *context.Context) { mailer.SendResetPasswordMail(u) - audit.RecordUserPasswordReset(ctx, u) + audit.RecordUserPasswordResetRequest(ctx, user_model.NewGhostUser(), u) if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { log.Error("Set cache(MailResendLimit) fail: %v", err) diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index c6591e7195669..a6ea412981d02 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -399,7 +399,7 @@ func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) error { return err } - audit.RecordRepositoryTransferReject(ctx, ctx.Doer, ctx.Repo.Repository) + audit.RecordRepositoryTransferCancel(ctx, ctx.Doer, ctx.Repo.Repository) ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected")) } diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index d5200d1126501..bd970c979121a 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -835,7 +835,7 @@ func SettingsPost(ctx *context.Context) { return } - audit.RecordRepositoryTransferReject(ctx, ctx.Doer, ctx.Repo.Repository) + audit.RecordRepositoryTransferCancel(ctx, ctx.Doer, ctx.Repo.Repository) log.Trace("Repository transfer process was cancelled: %s/%s ", ctx.Repo.Owner.Name, repo.Name) ctx.Flash.Success(ctx.Tr("repo.settings.transfer_abort_success", repoTransfer.Recipient.Name)) diff --git a/services/audit/record.go b/services/audit/record.go index 8512a593113d0..b6e82ea65c01c 100644 --- a/services/audit/record.go +++ b/services/audit/record.go @@ -126,8 +126,8 @@ 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) } -func RecordUserPasswordReset(ctx context.Context, doer *user_model.User) { - record(ctx, audit_model.UserPasswordReset, doer, doer, doer, "Requested passwort reset for user %s.", doer.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) } func RecordUserVisibility(ctx context.Context, doer, user *user_model.User) { @@ -385,8 +385,8 @@ func RecordRepositoryDelete(ctx context.Context, doer *user_model.User, repo *re record(ctx, audit_model.RepositoryDelete, doer, repo, repo, "Deleted repository %s.", repo.FullName()) } -func RecordRepositoryName(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { - record(ctx, audit_model.RepositoryName, doer, repo, repo, "Changed repository name to %s.", 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()) } func RecordRepositoryVisibility(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { @@ -422,12 +422,12 @@ func RecordRepositoryTransferStart(ctx context.Context, doer *user_model.User, r record(ctx, audit_model.RepositoryTransferStart, doer, repo, repo, "Started repository transfer of %s to %s.", repo.FullName(), newOwner.Name) } -func RecordRepositoryTransferAccept(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, oldOwner *user_model.User) { - record(ctx, audit_model.RepositoryTransferAccept, doer, repo, repo, "Accepted repository transfer of %s from %s to %s.", repo.Fullname(), oldOwner.Name, repo.OwnerName) +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) } -func RecordRepositoryTransferReject(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { - record(ctx, audit_model.RepositoryTransferReject, doer, repo, repo, "Rejected transfer of repository %s.", repo.FullName()) +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()) } func RecordRepositoryWikiDelete(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) { diff --git a/services/repository/transfer.go b/services/repository/transfer.go index f95ef3099110c..8865fee7707f0 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -54,7 +54,7 @@ func TransferOwnership(ctx context.Context, doer, newOwner *user_model.User, rep return err } - audit.RecordRepositoryTransferAccept(ctx, doer, newRepo, oldOwner) + audit.RecordRepositoryTransferFinish(ctx, doer, newRepo, oldOwner) for _, team := range teams { if err := models.AddRepository(ctx, team, newRepo); err != nil { @@ -360,7 +360,7 @@ func ChangeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo repo.Name = newRepoName - audit.RecordRepositoryName(ctx, doer, repo) + audit.RecordRepositoryName(ctx, doer, repo, oldRepoName) notify_service.RenameRepository(ctx, doer, repo, oldRepoName) From 4e41c8befbb1cf11d7b8c85e468c2c8ef1fd0296 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 2 Mar 2024 21:52:12 +0000 Subject: [PATCH 41/44] Use `ConfigSectionKeyBool`. --- modules/setting/audit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/setting/audit.go b/modules/setting/audit.go index ee765919bf9e4..13de905ae13a4 100644 --- a/modules/setting/audit.go +++ b/modules/setting/audit.go @@ -25,7 +25,7 @@ func loadAuditFrom(rootCfg ConfigProvider) { sec, err := rootCfg.GetSection("audit.file") if err == nil { - if !sec.Key("ENABLED").MustBool(false) { + if !ConfigSectionKeyBool(sec, "ENABLED") { return } From c4d5fd20788bb35c0690a46fe6a64cfcb1b6e34e Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 2 Mar 2024 22:17:24 +0000 Subject: [PATCH 42/44] Fix context imports. --- docs/content/administration/config-cheat-sheet.en-us.md | 1 + routers/private/hook_post_receive.go | 1 - routers/web/admin/audit.go | 2 +- routers/web/org/setting/audit.go | 2 +- routers/web/repo/setting/audit.go | 2 +- routers/web/user/setting/audit.go | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index 71bff9f3ed56f..add679162f2a0 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -912,6 +912,7 @@ Default templates for project boards: ## File Audit Log (`audit.file`) +- `ENABLED`: **false**: Enable logging of audit events to file - `FILENAME`: **\**: Set the file name for the logger. If this is a relative path this will be relative to `log.ROOT_PATH`. Defaults to `log.ROOT_PATH/audit.log`. - `ROTATE`: **true**: This enables automated audit log rotate, default is true - `MAXIMUM_SIZE`: **256 MB**: Maximum file size in bytes before rotating takes place (format `1000`, `1 MB`, `1 GiB`) diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index 99b3c207cc930..c43d8c7b5e8c8 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -11,7 +11,6 @@ import ( issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" - gitea_context "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" diff --git a/routers/web/admin/audit.go b/routers/web/admin/audit.go index 3b7158675aef3..65ce54052c4fb 100644 --- a/routers/web/admin/audit.go +++ b/routers/web/admin/audit.go @@ -9,9 +9,9 @@ import ( audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/audit" + "code.gitea.io/gitea/services/context" ) const ( diff --git a/routers/web/org/setting/audit.go b/routers/web/org/setting/audit.go index 7981cb0610cb2..ea9af92e97d2f 100644 --- a/routers/web/org/setting/audit.go +++ b/routers/web/org/setting/audit.go @@ -9,9 +9,9 @@ import ( audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/audit" + "code.gitea.io/gitea/services/context" ) const ( diff --git a/routers/web/repo/setting/audit.go b/routers/web/repo/setting/audit.go index c452d5a43df99..9c42500759126 100644 --- a/routers/web/repo/setting/audit.go +++ b/routers/web/repo/setting/audit.go @@ -9,9 +9,9 @@ import ( audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/audit" + gitea_context "code.gitea.io/gitea/services/context" ) const ( diff --git a/routers/web/user/setting/audit.go b/routers/web/user/setting/audit.go index d412b2ea71d4d..83acc4e4b3a69 100644 --- a/routers/web/user/setting/audit.go +++ b/routers/web/user/setting/audit.go @@ -9,9 +9,9 @@ import ( audit_model "code.gitea.io/gitea/models/audit" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/audit" + "code.gitea.io/gitea/services/context" ) const ( From d6fd3481ad6be9c85a9885ff4a6306732a666aa4 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 2 Mar 2024 22:30:37 +0000 Subject: [PATCH 43/44] Fix imports. --- routers/web/admin/emails.go | 1 - routers/web/admin/hooks.go | 1 - routers/web/repo/setting/audit.go | 2 +- routers/web/user/setting/security/security.go | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go index 136a628415860..119a7c12f504a 100644 --- a/routers/web/admin/emails.go +++ b/routers/web/admin/emails.go @@ -14,7 +14,6 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/context" ) diff --git a/routers/web/admin/hooks.go b/routers/web/admin/hooks.go index 75122a17ad698..1e93649f63963 100644 --- a/routers/web/admin/hooks.go +++ b/routers/web/admin/hooks.go @@ -10,7 +10,6 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/context" ) diff --git a/routers/web/repo/setting/audit.go b/routers/web/repo/setting/audit.go index 9c42500759126..17d767fcd279e 100644 --- a/routers/web/repo/setting/audit.go +++ b/routers/web/repo/setting/audit.go @@ -11,7 +11,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/audit" - gitea_context "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/services/context" ) const ( diff --git a/routers/web/user/setting/security/security.go b/routers/web/user/setting/security/security.go index ae7a55b9ab640..0d48e5e1230ba 100644 --- a/routers/web/user/setting/security/security.go +++ b/routers/web/user/setting/security/security.go @@ -14,7 +14,6 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/audit" "code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/context" From 3a919d2387f963f05ad1107004815fc4b988a809 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Mon, 4 Mar 2024 20:01:10 +0000 Subject: [PATCH 44/44] Fix typo. --- services/audit/record.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/audit/record.go b/services/audit/record.go index b6e82ea65c01c..ff0e3f474cd24 100644 --- a/services/audit/record.go +++ b/services/audit/record.go @@ -127,7 +127,7 @@ func RecordUserPassword(ctx context.Context, doer, user *user_model.User) { } 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, "Requested password reset for user %s.", user.Name) } func RecordUserVisibility(ctx context.Context, doer, user *user_model.User) {