mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-14 19:22:09 +09:00
feat: Add audit logging (#38189)
Co-authored-by: bircni <bircni@users.noreply.github.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
+14
-2
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
actions_model "gitea.dev/models/actions"
|
||||
activities_model "gitea.dev/models/activities"
|
||||
audit_model "gitea.dev/models/audit"
|
||||
"gitea.dev/models/db"
|
||||
org_model "gitea.dev/models/organization"
|
||||
packages_model "gitea.dev/models/packages"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
"gitea.dev/modules/storage"
|
||||
"gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/services/audit"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
)
|
||||
|
||||
@@ -85,6 +87,8 @@ func DeleteOrganization(ctx context.Context, org *org_model.Organization, purge
|
||||
return err
|
||||
}
|
||||
|
||||
audit.Record(ctx, audit_model.OrganizationDelete, org.AsUser())
|
||||
|
||||
// FIXME: system notice
|
||||
// Note: There are something just cannot be roll back,
|
||||
// so just keep error logs of those operations.
|
||||
@@ -158,9 +162,10 @@ func ChangeOrganizationVisibility(ctx context.Context, org *org_model.Organizati
|
||||
return nil
|
||||
}
|
||||
|
||||
oldVisibility := org.Visibility
|
||||
org.Visibility = visibility
|
||||
// FIXME: If it's a big forks network(forks and sub forks), the database transaction will be too long to fail.
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err := user_model.UpdateUserColsNoAutoTime(ctx, org.AsUser(), "visibility"); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -177,7 +182,14 @@ func ChangeOrganizationVisibility(ctx context.Context, org *org_model.Organizati
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
audit.Record(ctx, audit_model.OrganizationVisibility, org.AsUser(),
|
||||
"old_visibility", oldVisibility.String(), "new_visibility", visibility.String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateOrgEmailAddress validates and updates the organization's contact email.
|
||||
|
||||
+50
-8
@@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
audit_model "gitea.dev/models/audit"
|
||||
"gitea.dev/models/db"
|
||||
git_model "gitea.dev/models/git"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
@@ -20,11 +21,23 @@ import (
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/services/audit"
|
||||
repo_service "gitea.dev/services/repository"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// recordTeamAudit emits a team-related audit event scoped to the owning organization.
|
||||
func recordTeamAudit(ctx context.Context, action audit_model.Action, team *organization.Team, metadata ...any) {
|
||||
audit.Record(ctx, action, audit.ScopeFromUserID(ctx, team.OrgID), metadata...)
|
||||
}
|
||||
|
||||
// recordTeamMemberAudit emits a team membership audit event scoped to the
|
||||
// owning organization.
|
||||
func recordTeamMemberAudit(ctx context.Context, action audit_model.Action, team *organization.Team, member *user_model.User) {
|
||||
recordTeamAudit(ctx, action, team, "team", team.Name, "member", member.Name)
|
||||
}
|
||||
|
||||
// NewTeam creates a record of new team.
|
||||
// It's caller's responsibility to assign organization ID.
|
||||
func NewTeam(ctx context.Context, t *organization.Team) (err error) {
|
||||
@@ -56,7 +69,7 @@ func NewTeam(ctx context.Context, t *organization.Team) (err error) {
|
||||
return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName}
|
||||
}
|
||||
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err = db.Insert(ctx, t); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -82,7 +95,13 @@ func NewTeam(ctx context.Context, t *organization.Team) (err error) {
|
||||
// Update organization number of teams.
|
||||
_, err = db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID)
|
||||
return err
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordTeamAudit(ctx, audit_model.OrganizationTeamAdd, t, "team", t.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateTeam updates information of team.
|
||||
@@ -95,7 +114,7 @@ func UpdateTeam(ctx context.Context, t *organization.Team, authChanged, includeA
|
||||
t.Description = t.Description[:255]
|
||||
}
|
||||
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||
t.LowerName = strings.ToLower(t.Name)
|
||||
has, err := db.Exist[organization.Team](ctx, builder.Eq{
|
||||
"org_id": t.OrgID,
|
||||
@@ -155,13 +174,22 @@ func UpdateTeam(ctx context.Context, t *organization.Team, authChanged, includeA
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordTeamAudit(ctx, audit_model.OrganizationTeamUpdate, t, "team", t.Name)
|
||||
if authChanged {
|
||||
recordTeamAudit(ctx, audit_model.OrganizationTeamPermission, t, "team", t.Name, "permission", t.AccessMode.ToString())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteTeam deletes given team.
|
||||
// It's caller's responsibility to assign organization ID.
|
||||
func DeleteTeam(ctx context.Context, t *organization.Team) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err := t.LoadMembers(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -205,7 +233,13 @@ func DeleteTeam(ctx context.Context, t *organization.Team) error {
|
||||
// Update organization number of teams.
|
||||
_, err := db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID)
|
||||
return err
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordTeamAudit(ctx, audit_model.OrganizationTeamRemove, t, "team", t.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddTeamMember adds new membership of given team to given organization,
|
||||
@@ -250,6 +284,8 @@ func AddTeamMember(ctx context.Context, team *organization.Team, user *user_mode
|
||||
return err
|
||||
}
|
||||
|
||||
recordTeamMemberAudit(ctx, audit_model.OrganizationTeamMemberAdd, team, user)
|
||||
|
||||
// this behaviour may spend much time so run it in a goroutine
|
||||
// FIXME: Update watch repos batchly
|
||||
if setting.Service.AutoWatchNewRepos {
|
||||
@@ -347,7 +383,13 @@ func removeInvalidOrgUser(ctx context.Context, orgID int64, user *user_model.Use
|
||||
|
||||
// RemoveTeamMember removes member from given team of given organization.
|
||||
func RemoveTeamMember(ctx context.Context, team *organization.Team, user *user_model.User) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
return removeTeamMember(ctx, team, user)
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordTeamMemberAudit(ctx, audit_model.OrganizationTeamMemberRemove, team, user)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,11 +8,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
audit_model "gitea.dev/models/audit"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/organization"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/services/audit"
|
||||
)
|
||||
|
||||
// RemoveOrgUser removes user from given organization.
|
||||
@@ -47,7 +49,7 @@ func RemoveOrgUser(ctx context.Context, org *organization.Organization, user *us
|
||||
}
|
||||
}
|
||||
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if err := db.WithTx(ctx, func(ctx context.Context) error {
|
||||
if _, err := db.DeleteByID[organization.OrgUser](ctx, ou.ID); err != nil {
|
||||
return err
|
||||
} else if _, err = db.Exec(ctx, "UPDATE `user` SET num_members=num_members-1 WHERE id=?", org.ID); err != nil {
|
||||
@@ -94,5 +96,10 @@ func RemoveOrgUser(ctx context.Context, org *organization.Organization, user *us
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
audit.Record(ctx, audit_model.OrganizationMemberRemove, audit.ScopeFromUserID(ctx, org.ID), "member", user.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,9 +6,12 @@ package org
|
||||
import (
|
||||
"testing"
|
||||
|
||||
audit_model "gitea.dev/models/audit"
|
||||
"gitea.dev/models/organization"
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -71,3 +74,20 @@ func TestRemoveOrgUser(t *testing.T) {
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{OrgID: org7.ID, UID: user5.ID})
|
||||
unittest.CheckConsistencyFor(t, &user_model.User{}, &organization.Team{})
|
||||
}
|
||||
|
||||
func TestRemoveOrgUserRecordsAudit(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
defer test.MockVariableValue(&setting.Audit.RecordOutput, setting.AuditRecordOutputDatabase)()
|
||||
|
||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
unittest.AssertExistsAndLoadBean(t, &organization.OrgUser{OrgID: org.ID, UID: user.ID})
|
||||
|
||||
assert.NoError(t, RemoveOrgUser(t.Context(), org, user))
|
||||
|
||||
unittest.AssertExistsAndLoadBean(t, &audit_model.Event{
|
||||
Action: audit_model.OrganizationMemberRemove,
|
||||
ScopeType: audit_model.ScopeOrganization,
|
||||
ScopeID: org.ID,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user