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:
@@ -7,12 +7,14 @@ package admin
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
audit_model "gitea.dev/models/audit"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/organization"
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/audit"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
@@ -74,6 +76,8 @@ func CreateOrg(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
audit.Record(ctx, audit_model.OrganizationCreate, org.AsUser())
|
||||
|
||||
ctx.JSON(http.StatusCreated, convert.ToOrganization(ctx, org))
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
asymkey_model "gitea.dev/models/asymkey"
|
||||
audit_model "gitea.dev/models/audit"
|
||||
"gitea.dev/models/auth"
|
||||
"gitea.dev/models/db"
|
||||
org_model "gitea.dev/models/organization"
|
||||
@@ -26,6 +27,7 @@ import (
|
||||
"gitea.dev/routers/api/v1/user"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
asymkey_service "gitea.dev/services/asymkey"
|
||||
"gitea.dev/services/audit"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
"gitea.dev/services/mailer"
|
||||
@@ -152,6 +154,8 @@ func CreateUser(ctx *context.APIContext) {
|
||||
ctx.Resp.Header().Add("X-Gitea-Warning", fmt.Sprintf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", u.Email))
|
||||
}
|
||||
|
||||
audit.Record(ctx, audit_model.UserCreate, u)
|
||||
|
||||
log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name)
|
||||
|
||||
// Send email notification.
|
||||
|
||||
@@ -68,6 +68,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
audit_model "gitea.dev/models/audit"
|
||||
auth_model "gitea.dev/models/auth"
|
||||
"gitea.dev/models/organization"
|
||||
"gitea.dev/models/perm"
|
||||
@@ -95,6 +96,7 @@ import (
|
||||
"gitea.dev/routers/api/v1/user"
|
||||
"gitea.dev/routers/common"
|
||||
"gitea.dev/services/actions"
|
||||
"gitea.dev/services/audit"
|
||||
"gitea.dev/services/auth"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/forms"
|
||||
@@ -124,7 +126,13 @@ func sudo() func(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
log.Trace("Sudo from (%s) to: %s", ctx.Doer.Name, user.Name)
|
||||
|
||||
audit.Record(ctx, audit_model.UserImpersonation, user)
|
||||
|
||||
ctx.Doer = user
|
||||
// keep the audit actor in step with the effective doer, and keep the admin attached to it
|
||||
ctx.Data[middleware.ContextDataKeyImpersonator] = ctx.Data[middleware.ContextDataKeySignedUser]
|
||||
ctx.Data[middleware.ContextDataKeySignedUser] = user
|
||||
} else {
|
||||
ctx.JSON(http.StatusForbidden, map[string]string{
|
||||
"message": "Only administrators allowed to sudo.",
|
||||
@@ -1021,6 +1029,7 @@ func Routes() *web.Router {
|
||||
}
|
||||
|
||||
m.AfterRouting(context.APIContexter())
|
||||
m.AfterRouting(common.AuditOrigin(audit_model.OriginAPI))
|
||||
m.AfterRouting(checkDeprecatedAuthMethods)
|
||||
|
||||
// Get user from session if logged in.
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"gitea.dev/routers/api/v1/shared"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
actions_service "gitea.dev/services/actions"
|
||||
"gitea.dev/services/audit"
|
||||
"gitea.dev/services/context"
|
||||
secret_service "gitea.dev/services/secrets"
|
||||
)
|
||||
@@ -108,12 +109,18 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
|
||||
|
||||
opt := web.GetForm[*api.CreateOrUpdateSecretOption](ctx)
|
||||
|
||||
_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("secretname"), opt.Data, opt.Description)
|
||||
s, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("secretname"), opt.Data, opt.Description)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
actions := audit.SecretUpdate
|
||||
if created {
|
||||
actions = audit.SecretAdd
|
||||
}
|
||||
audit.RecordScoped(ctx, ctx.Org.Organization.AsUser(), nil, actions, "secret", s.Name)
|
||||
|
||||
if created {
|
||||
ctx.Status(http.StatusCreated)
|
||||
} else {
|
||||
@@ -149,12 +156,14 @@ func (Action) DeleteSecret(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
err := secret_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("secretname"))
|
||||
s, err := secret_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.PathParam("secretname"))
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordScoped(ctx, ctx.Org.Organization.AsUser(), nil, audit.SecretRemove, "secret", s.Name)
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
activities_model "gitea.dev/models/activities"
|
||||
audit_model "gitea.dev/models/audit"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/organization"
|
||||
"gitea.dev/models/perm"
|
||||
@@ -25,6 +26,7 @@ import (
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/user"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/audit"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
feed_service "gitea.dev/services/feed"
|
||||
@@ -296,6 +298,8 @@ func Create(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
audit.Record(ctx, audit_model.OrganizationCreate, org.AsUser())
|
||||
|
||||
ctx.JSON(http.StatusCreated, convert.ToOrganization(ctx, org))
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"gitea.dev/routers/api/v1/shared"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
actions_service "gitea.dev/services/actions"
|
||||
"gitea.dev/services/audit"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
secret_service "gitea.dev/services/secrets"
|
||||
@@ -137,12 +138,18 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
|
||||
|
||||
opt := web.GetForm[*api.CreateOrUpdateSecretOption](ctx)
|
||||
|
||||
_, created, err := secret_service.CreateOrUpdateSecret(ctx, 0, repo.ID, ctx.PathParam("secretname"), opt.Data, opt.Description)
|
||||
s, created, err := secret_service.CreateOrUpdateSecret(ctx, 0, repo.ID, ctx.PathParam("secretname"), opt.Data, opt.Description)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
actions := audit.SecretUpdate
|
||||
if created {
|
||||
actions = audit.SecretAdd
|
||||
}
|
||||
audit.RecordScoped(ctx, nil, repo, actions, "secret", s.Name)
|
||||
|
||||
if created {
|
||||
ctx.Status(http.StatusCreated)
|
||||
} else {
|
||||
@@ -185,12 +192,14 @@ func (Action) DeleteSecret(ctx *context.APIContext) {
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
|
||||
err := secret_service.DeleteSecretByName(ctx, 0, repo.ID, ctx.PathParam("secretname"))
|
||||
s, err := secret_service.DeleteSecretByName(ctx, 0, repo.ID, ctx.PathParam("secretname"))
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordScoped(ctx, nil, repo, audit.SecretRemove, "secret", s.Name)
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"time"
|
||||
|
||||
activities_model "gitea.dev/models/activities"
|
||||
audit_model "gitea.dev/models/audit"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/organization"
|
||||
"gitea.dev/models/perm"
|
||||
@@ -33,6 +34,7 @@ import (
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
actions_service "gitea.dev/services/actions"
|
||||
"gitea.dev/services/audit"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
feed_service "gitea.dev/services/feed"
|
||||
@@ -729,6 +731,10 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
|
||||
return err
|
||||
}
|
||||
|
||||
if visibilityChanged {
|
||||
audit.Record(ctx, audit_model.RepositoryVisibility, repo, "visibility", repo.IsPrivate)
|
||||
}
|
||||
|
||||
if updateRepoLicense {
|
||||
if err := repo_service.AddRepoToLicenseUpdaterQueue(&repo_service.LicenseUpdaterOptions{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"gitea.dev/routers/api/v1/shared"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
actions_service "gitea.dev/services/actions"
|
||||
"gitea.dev/services/audit"
|
||||
"gitea.dev/services/context"
|
||||
secret_service "gitea.dev/services/secrets"
|
||||
)
|
||||
@@ -50,12 +51,18 @@ func CreateOrUpdateSecret(ctx *context.APIContext) {
|
||||
|
||||
opt := web.GetForm[*api.CreateOrUpdateSecretOption](ctx)
|
||||
|
||||
_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Doer.ID, 0, ctx.PathParam("secretname"), opt.Data, opt.Description)
|
||||
s, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Doer.ID, 0, ctx.PathParam("secretname"), opt.Data, opt.Description)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
actions := audit.SecretUpdate
|
||||
if created {
|
||||
actions = audit.SecretAdd
|
||||
}
|
||||
audit.RecordScoped(ctx, ctx.Doer, nil, actions, "secret", s.Name)
|
||||
|
||||
if created {
|
||||
ctx.Status(http.StatusCreated)
|
||||
} else {
|
||||
@@ -86,12 +93,14 @@ func DeleteSecret(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
err := secret_service.DeleteSecretByName(ctx, ctx.Doer.ID, 0, ctx.PathParam("secretname"))
|
||||
s, err := secret_service.DeleteSecretByName(ctx, ctx.Doer.ID, 0, ctx.PathParam("secretname"))
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordScoped(ctx, ctx.Doer, nil, audit.SecretRemove, "secret", s.Name)
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,13 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
audit_model "gitea.dev/models/audit"
|
||||
auth_model "gitea.dev/models/auth"
|
||||
"gitea.dev/models/db"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/audit"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
"gitea.dev/services/forms"
|
||||
@@ -149,6 +151,9 @@ func CreateAccessToken(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
audit.Record(ctx, audit_model.UserAccessTokenAdd, ctx.ContextUser, "token", t.Name, "token_scope", t.Scope)
|
||||
|
||||
ctx.JSON(http.StatusCreated, &api.AccessToken{
|
||||
Name: t.Name,
|
||||
Token: t.Token,
|
||||
@@ -211,11 +216,19 @@ func DeleteAccessToken(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := auth_model.DeleteAccessTokenByID(ctx, tokenID, ctx.ContextUser.ID); err != nil {
|
||||
t, err := auth_model.GetAccessTokenByID(ctx, tokenID, ctx.ContextUser.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := auth_model.DeleteAccessTokenByID(ctx, t.ID, ctx.ContextUser.ID); err != nil {
|
||||
ctx.APIErrorAuto(err)
|
||||
return
|
||||
}
|
||||
|
||||
audit.Record(ctx, audit_model.UserAccessTokenRemove, ctx.ContextUser, "token", t.Name)
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
@@ -261,6 +274,8 @@ func CreateOauth2Application(ctx *context.APIContext) {
|
||||
}
|
||||
app.ClientSecret = secret
|
||||
|
||||
audit.Record(ctx, audit_model.UserOAuth2ApplicationAdd, ctx.Doer, "oauth2_application", app.Name)
|
||||
|
||||
ctx.JSON(http.StatusCreated, convert.ToOAuth2Application(app))
|
||||
}
|
||||
|
||||
@@ -323,6 +338,15 @@ func DeleteOauth2Application(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
appID := ctx.PathParamInt64("id")
|
||||
app, err := auth_model.GetOAuth2ApplicationByID(ctx, appID)
|
||||
if err != nil {
|
||||
if auth_model.IsErrOAuthApplicationNotFound(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := auth_model.DeleteOAuth2Application(ctx, appID, ctx.Doer.ID); err != nil {
|
||||
if auth_model.IsErrOAuthApplicationNotFound(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
@@ -332,6 +356,8 @@ func DeleteOauth2Application(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
audit.Record(ctx, audit_model.UserOAuth2ApplicationRemove, ctx.Doer, "oauth2_application", app.Name)
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
@@ -430,5 +456,7 @@ func UpdateOauth2Application(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
audit.Record(ctx, audit_model.UserOAuth2ApplicationUpdate, ctx.Doer, "oauth2_application", app.Name)
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToOAuth2Application(app))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user