mirror of
https://github.com/go-gitea/gitea
synced 2024-06-01 19:06:52 +02:00
Merge 7ae645e591
into 821d2fc2a3
This commit is contained in:
commit
4f537cf704
|
@ -4,6 +4,7 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
@ -11,6 +12,8 @@ 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"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
@ -90,6 +93,26 @@ func runListAuth(c *cli.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func createSource(ctx context.Context, source *auth_model.Source) error {
|
||||
if err := auth_model.CreateSource(ctx, source); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
audit.RecordSystemAuthenticationSourceAdd(ctx, user_model.NewCLIUser(), source)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateSource(ctx context.Context, source *auth_model.Source) error {
|
||||
if err := auth_model.UpdateSource(ctx, source); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
audit.RecordSystemAuthenticationSourceUpdate(ctx, user_model.NewCLIUser(), source)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runDeleteAuth(c *cli.Context) error {
|
||||
if !c.IsSet("id") {
|
||||
return errors.New("--id flag is missing")
|
||||
|
@ -107,5 +130,5 @@ func runDeleteAuth(c *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
return auth_service.DeleteSource(ctx, source)
|
||||
return auth_service.DeleteSource(ctx, user_model.NewCLIUser(), source)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ 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"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
@ -308,58 +310,26 @@ func (a *authService) getAuthSource(ctx context.Context, c *cli.Context, authTyp
|
|||
|
||||
// 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(ctx, authSource)
|
||||
return a.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(ctx, 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(ctx, authSource)
|
||||
return a.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 a.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 a.updateLdapSource(c, auth.DLDAP)
|
||||
}
|
||||
|
||||
func (a *authService) addLdapSource(c *cli.Context, authType auth.Type, args ...string) error {
|
||||
if err := argsSet(c, args...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -369,9 +339,12 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
|
|||
if err := a.initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := audit.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authSource := &auth.Source{
|
||||
Type: auth.DLDAP,
|
||||
Type: authType,
|
||||
IsActive: true, // active by default
|
||||
Cfg: &ldap.Source{
|
||||
Enabled: true, // always true
|
||||
|
@ -383,19 +356,27 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
return a.createAuthSource(ctx, authSource)
|
||||
if err := a.createAuthSource(ctx, authSource); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
audit.RecordSystemAuthenticationSourceAdd(ctx, user_model.NewCLIUser(), authSource)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateLdapBindDn updates a new LDAP (simple auth) authentication source.
|
||||
func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
|
||||
func (a *authService) updateLdapSource(c *cli.Context, authType auth.Type) error {
|
||||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
if err := a.initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := audit.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authSource, err := a.getAuthSource(ctx, c, auth.DLDAP)
|
||||
authSource, err := a.getAuthSource(ctx, c, authType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -405,5 +386,11 @@ func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
return a.updateAuthSource(ctx, authSource)
|
||||
if err := a.updateAuthSource(ctx, authSource); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
audit.RecordSystemAuthenticationSourceUpdate(ctx, user_model.NewCLIUser(), authSource)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -184,7 +184,7 @@ func runAddOauth(c *cli.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
return auth_model.CreateSource(ctx, &auth_model.Source{
|
||||
return createSource(ctx, &auth_model.Source{
|
||||
Type: auth_model.OAuth2,
|
||||
Name: c.String("name"),
|
||||
IsActive: true,
|
||||
|
@ -295,5 +295,5 @@ func runUpdateOauth(c *cli.Context) error {
|
|||
oAuth2Config.CustomURLMapping = customURLMapping
|
||||
source.Cfg = oAuth2Config
|
||||
|
||||
return auth_model.UpdateSource(ctx, source)
|
||||
return updateSource(ctx, source)
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ func runAddSMTP(c *cli.Context) error {
|
|||
smtpConfig.Auth = "PLAIN"
|
||||
}
|
||||
|
||||
return auth_model.CreateSource(ctx, &auth_model.Source{
|
||||
return createSource(ctx, &auth_model.Source{
|
||||
Type: auth_model.SMTP,
|
||||
Name: c.String("name"),
|
||||
IsActive: active,
|
||||
|
@ -196,5 +196,5 @@ func runUpdateSMTP(c *cli.Context) error {
|
|||
|
||||
source.Cfg = smtpConfig
|
||||
|
||||
return auth_model.UpdateSource(ctx, source)
|
||||
return updateSource(ctx, source)
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ func runChangePassword(c *cli.Context) error {
|
|||
Password: optional.Some(c.String("password")),
|
||||
MustChangePassword: optional.Some(c.Bool("must-change-password")),
|
||||
}
|
||||
if err := user_service.UpdateAuth(ctx, 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 characters", setting.MinPasswordLength)
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
pwd "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"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
@ -103,6 +104,9 @@ func runCreateUser(c *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
if err := audit.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var password string
|
||||
if c.IsSet("password") {
|
||||
|
@ -162,6 +166,8 @@ func runCreateUser(c *cli.Context) error {
|
|||
return fmt.Errorf("CreateUser: %w", err)
|
||||
}
|
||||
|
||||
audit.RecordUserCreate(ctx, user_model.NewCLIUser(), u)
|
||||
|
||||
if c.Bool("access-token") {
|
||||
t := &auth_model.AccessToken{
|
||||
Name: "gitea-admin",
|
||||
|
@ -172,6 +178,8 @@ func runCreateUser(c *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
audit.RecordUserAccessTokenAdd(ctx, user_model.NewCLIUser(), u, t)
|
||||
|
||||
fmt.Printf("Access token was successfully created... %s\n", t.Token)
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,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/v2"
|
||||
|
@ -52,7 +53,9 @@ func runDeleteUser(c *cli.Context) error {
|
|||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := audit.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := storage.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -77,5 +80,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, user_model.NewCLIUser(), user, c.Bool("purge"))
|
||||
}
|
||||
|
|
|
@ -9,6 +9,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/v2"
|
||||
)
|
||||
|
@ -52,6 +53,9 @@ func runGenerateAccessToken(c *cli.Context) error {
|
|||
if err := initDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := audit.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := user_model.GetUserByName(ctx, c.String("username"))
|
||||
if err != nil {
|
||||
|
@ -84,6 +88,8 @@ func runGenerateAccessToken(c *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
audit.RecordUserAccessTokenAdd(ctx, user_model.NewCLIUser(), user, t)
|
||||
|
||||
if c.Bool("raw") {
|
||||
fmt.Printf("%s\n", t.Token)
|
||||
} else {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -659,6 +659,32 @@ LEVEL = Info
|
|||
;; Host address
|
||||
;ADDR =
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
[audit]
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Enable logging of audit events
|
||||
;ENABLED = false
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;[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
|
||||
;FILE_NAME =
|
||||
;; This enables automated audit log rotate, default is 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
|
||||
;DAILY_ROTATE = true
|
||||
;; Delete the audit log file after n days, default is 7
|
||||
;MAX_DAYS = 7
|
||||
;; Compress audit logs with gzip
|
||||
;COMPRESS = true
|
||||
;; Compression level see godoc for compress/gzip
|
||||
;COMPRESSION_LEVEL = -1
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
[git]
|
||||
|
|
168
docs/content/administration/audit-logging.en-us.md
Normal file
168
docs/content/administration/audit-logging.en-us.md
Normal file
|
@ -0,0 +1,168 @@
|
|||
---
|
||||
date: "2023-04-21T00:00:00+00:00"
|
||||
title: "Audit Logging"
|
||||
slug: "audit-logging"
|
||||
sidebar_position: 43
|
||||
toc: false
|
||||
draft: false
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "administration"
|
||||
name: "Audit Logging"
|
||||
sidebar_position: 43
|
||||
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:
|
||||
|
||||
- `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` | Created user |
|
||||
| `user:delete` | Deleted user |
|
||||
| `user:authentication:fail:twofactor` | Failed two-factor authentication for 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: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 |
|
||||
| `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 |
|
||||
| `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:update` | Updated 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` | Created 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` | 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 |
|
||||
| `organization:oauth2application:remove` | Removed OAuth2 application |
|
||||
| `organization:secret:add` | Added secret |
|
||||
| `organization:secret:update` | Updated 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` | Crated repository |
|
||||
| `repository:create:fork` | Created fork of repository |
|
||||
| `repository:archive` | Archived repository |
|
||||
| `repository:unarchive` | Unarchived repository |
|
||||
| `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 |
|
||||
| `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: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 |
|
||||
| `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:update` | Updated secret |
|
||||
| `repository:secret:remove` | Removed secret |
|
||||
|
||||
### System Events
|
||||
|
||||
| Event | Description |
|
||||
| - | - |
|
||||
| `system:startup` | System startup |
|
||||
| `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 |
|
||||
| `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 |
|
|
@ -912,6 +912,21 @@ Default templates for project boards:
|
|||
- `PROTOCOL`: **tcp**: Set the protocol, either "tcp", "unix" or "udp".
|
||||
- `ADDR`: **:7020**: Sets the address to connect to.
|
||||
|
||||
## Audit Log (`audit`)
|
||||
|
||||
- `ENABLED`: **false**: Enable logging of audit events
|
||||
|
||||
## File Audit Log (`audit.file`)
|
||||
|
||||
- `ENABLED`: **false**: Enable logging of audit events to file
|
||||
- `FILENAME`: **\<empty\>**: 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`)
|
||||
- `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.
|
||||
|
|
|
@ -295,17 +295,17 @@ func PublicKeyIsExternallyManaged(ctx context.Context, id int64) (bool, error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
// deleteKeysMarkedForDeletion returns true if ssh keys needs update
|
||||
func deleteKeysMarkedForDeletion(ctx context.Context, keys []string) (bool, error) {
|
||||
// deleteKeysMarkedForDeletion returns the deleted keys
|
||||
func deleteKeysMarkedForDeletion(ctx context.Context, keys []string) ([]*PublicKey, error) {
|
||||
// Start session
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
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 {
|
||||
|
@ -316,19 +316,21 @@ func deleteKeysMarkedForDeletion(ctx context.Context, keys []string) (bool, erro
|
|||
log.Error("DeleteByID[PublicKey]: %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(ctx context.Context, 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(ctx context.Context, 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
|
||||
|
@ -346,28 +348,27 @@ func AddPublicKeysBySource(ctx context.Context, usr *user_model.User, s *auth.So
|
|||
marshalled = marshalled[:len(marshalled)-1]
|
||||
sshKeyName := fmt.Sprintf("%s-%s", s.Name, ssh.FingerprintSHA256(out))
|
||||
|
||||
if _, err := AddPublicKey(ctx, usr.ID, sshKeyName, marshalled, s.ID); err != nil {
|
||||
if pubKey, err := AddPublicKey(ctx, 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(ctx context.Context, usr *user_model.User, s *auth.Source, sshPublicKeys []string) bool {
|
||||
var sshKeysNeedUpdate bool
|
||||
|
||||
// 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
|
||||
|
@ -399,7 +400,7 @@ func SynchronizePublicKeys(ctx context.Context, usr *user_model.User, s *auth.So
|
|||
// 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))
|
||||
|
||||
|
@ -410,9 +411,8 @@ func SynchronizePublicKeys(ctx context.Context, usr *user_model.User, s *auth.So
|
|||
newKeys = append(newKeys, key)
|
||||
}
|
||||
}
|
||||
if AddPublicKeysBySource(ctx, usr, s, newKeys) {
|
||||
sshKeysNeedUpdate = true
|
||||
}
|
||||
|
||||
addedKeys = AddPublicKeysBySource(ctx, usr, s, newKeys)
|
||||
|
||||
// Mark keys from DB that no longer exist in the source for deletion
|
||||
var giteaKeysToDelete []string
|
||||
|
@ -424,13 +424,10 @@ func SynchronizePublicKeys(ctx context.Context, usr *user_model.User, s *auth.So
|
|||
}
|
||||
|
||||
// Delete keys from DB that no longer exist in the source
|
||||
needUpd, 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)
|
||||
}
|
||||
if needUpd {
|
||||
sshKeysNeedUpdate = true
|
||||
}
|
||||
|
||||
return sshKeysNeedUpdate
|
||||
return addedKeys, deletedKeys
|
||||
}
|
||||
|
|
125
models/audit/action.go
Normal file
125
models/audit/action.go
Normal file
|
@ -0,0 +1,125 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package audit
|
||||
|
||||
type Action string
|
||||
|
||||
const (
|
||||
UserImpersonation Action = "user:impersonation"
|
||||
UserCreate Action = "user:create"
|
||||
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"
|
||||
UserPasswordResetRequest Action = "user:password:resetrequest"
|
||||
UserVisibility Action = "user:visibility"
|
||||
UserEmailPrimaryChange Action = "user:email:primary"
|
||||
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"
|
||||
UserSecretUpdate Action = "user:secret:update"
|
||||
UserSecretRemove Action = "user:secret:remove"
|
||||
UserWebhookAdd Action = "user:webhook:add"
|
||||
UserWebhookUpdate Action = "user:webhook:update"
|
||||
UserWebhookRemove Action = "user:webhook:remove"
|
||||
|
||||
OrganizationCreate Action = "organization:create"
|
||||
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"
|
||||
OrganizationSecretUpdate Action = "organization:secret:update"
|
||||
OrganizationSecretRemove Action = "organization:secret:remove"
|
||||
OrganizationWebhookAdd Action = "organization:webhook:add"
|
||||
OrganizationWebhookUpdate Action = "organization:webhook:update"
|
||||
OrganizationWebhookRemove Action = "organization:webhook:remove"
|
||||
|
||||
RepositoryCreate Action = "repository:create"
|
||||
RepositoryCreateFork Action = "repository:create:fork"
|
||||
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"
|
||||
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"
|
||||
RepositoryCollaboratorRemove Action = "repository:collaborator:remove"
|
||||
RepositoryCollaboratorTeamAdd Action = "repository:collaborator:team:add"
|
||||
RepositoryCollaboratorTeamRemove Action = "repository:collaborator:team:remove"
|
||||
RepositoryBranchDefault Action = "repository:branch:default"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
)
|
101
models/audit/audit_event.go
Normal file
101
models/audit/audit_event.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
// Copyright 2024 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
|
||||
TimestampUnix timeutil.TimeStamp `xorm:"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 (
|
||||
SortTimestampAsc EventSort = "timestamp_asc"
|
||||
SortTimestampDesc EventSort = "timestamp_desc"
|
||||
)
|
||||
|
||||
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 SortTimestampAsc:
|
||||
e.Asc("timestamp_unix")
|
||||
default:
|
||||
e.Desc("timestamp_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
|
||||
}
|
30
models/audit/types.go
Normal file
30
models/audit/types.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2024 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"
|
||||
)
|
|
@ -222,6 +222,19 @@ func UpdateAccessToken(ctx context.Context, t *AccessToken) error {
|
|||
return err
|
||||
}
|
||||
|
||||
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{
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"code.gitea.io/gitea/models/migrations/v1_20"
|
||||
"code.gitea.io/gitea/models/migrations/v1_21"
|
||||
"code.gitea.io/gitea/models/migrations/v1_22"
|
||||
"code.gitea.io/gitea/models/migrations/v1_23"
|
||||
"code.gitea.io/gitea/models/migrations/v1_6"
|
||||
"code.gitea.io/gitea/models/migrations/v1_7"
|
||||
"code.gitea.io/gitea/models/migrations/v1_8"
|
||||
|
@ -587,6 +588,9 @@ var migrations = []Migration{
|
|||
NewMigration("Drop wrongly created table o_auth2_application", v1_22.DropWronglyCreatedTable),
|
||||
|
||||
// Gitea 1.22.0-rc1 ends at 299
|
||||
|
||||
// v299 -> v300
|
||||
NewMigration("Add audit_event table", v1_23.AddAuditEventTable),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current db version
|
||||
|
|
27
models/migrations/v1_23/v298.go
Normal file
27
models/migrations/v1_23/v298.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_23 //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
|
||||
TimestampUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL"`
|
||||
}
|
||||
|
||||
return x.Sync(&AuditEvent{})
|
||||
}
|
|
@ -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(ctx context.Context, t *organization.Team) (err error) {
|
||||
func AddAllRepositories(ctx context.Context, t *organization.Team) ([]*repo_model.Repository, error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
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
|
||||
|
@ -204,7 +207,7 @@ func NewTeam(ctx context.Context, 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)
|
||||
}
|
||||
|
@ -282,7 +285,7 @@ func UpdateTeam(ctx context.Context, t *organization.Team, authChanged, includeA
|
|||
|
||||
// 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)
|
||||
}
|
||||
|
|
|
@ -91,6 +91,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"
|
||||
|
|
|
@ -352,7 +352,7 @@ func ChangeInactivePrimaryEmail(ctx context.Context, uid int64, oldEmailAddr, ne
|
|||
}
|
||||
|
||||
// VerifyActiveEmailCode verifies active email code when active account
|
||||
func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddress {
|
||||
func VerifyActiveEmailCode(ctx context.Context, code, email string) (*User, *EmailAddress) {
|
||||
minutes := setting.Service.ActiveCodeLives
|
||||
|
||||
if user := GetVerifyUser(ctx, code); user != nil {
|
||||
|
@ -363,11 +363,11 @@ func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddres
|
|||
if base.VerifyTimeLimitCode(data, minutes, prefix) {
|
||||
emailAddress := &EmailAddress{UID: user.ID, Email: email}
|
||||
if has, _ := db.GetEngine(ctx).Get(emailAddress); has {
|
||||
return emailAddress
|
||||
return user, emailAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// SearchEmailOrderBy is used to sort the results from SearchEmails()
|
||||
|
@ -453,10 +453,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()
|
||||
|
||||
|
@ -464,48 +464,48 @@ 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()
|
||||
}
|
||||
|
||||
// validateEmailBasic checks whether the email complies with the rules
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -204,3 +204,14 @@ func TimeoutDialer(cTimeout time.Duration) func(ctx context.Context, net, addr s
|
|||
func (r *Request) GoString() string {
|
||||
return fmt.Sprintf("%s %s", r.req.Method, r.url)
|
||||
}
|
||||
|
||||
func TryGetIPAddress(ctx context.Context) string {
|
||||
if req, _ := ctx.Value(RequestContextKey).(*http.Request); req != nil {
|
||||
host, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||
if err != nil {
|
||||
return req.RemoteAddr
|
||||
}
|
||||
return host
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
61
modules/setting/audit.go
Normal file
61
modules/setting/audit.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
var Audit = struct {
|
||||
Enabled bool
|
||||
FileOptions *log.WriterFileOption `ini:"-"`
|
||||
}{
|
||||
Enabled: false,
|
||||
}
|
||||
|
||||
func loadAuditFrom(rootCfg ConfigProvider) {
|
||||
mustMapSetting(rootCfg, "audit", &Audit)
|
||||
|
||||
sec, err := rootCfg.GetSection("audit.file")
|
||||
if err == nil {
|
||||
if !ConfigSectionKeyBool(sec, "ENABLED") {
|
||||
return
|
||||
}
|
||||
|
||||
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.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)
|
||||
}
|
||||
|
||||
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.MaxSize = mustBytes(sec, "MAXIMUM_SIZE")
|
||||
if opts.MaxSize <= 0 {
|
||||
opts.MaxSize = 1 << 28
|
||||
}
|
||||
|
||||
Audit.FileOptions = opts
|
||||
}
|
||||
}
|
|
@ -153,6 +153,8 @@ func loadCommonSettingsFrom(cfg ConfigProvider) error {
|
|||
loadMirrorFrom(cfg)
|
||||
loadMarkupFrom(cfg)
|
||||
loadOtherFrom(cfg)
|
||||
loadQueueFrom(cfg)
|
||||
loadAuditFrom(cfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -217,7 +219,7 @@ func LoadSettings() {
|
|||
loadMigrationsFrom(CfgProvider)
|
||||
loadIndexerFrom(CfgProvider)
|
||||
loadTaskFrom(CfgProvider)
|
||||
LoadQueueSettings()
|
||||
loadQueueFrom(CfgProvider)
|
||||
loadProjectFrom(CfgProvider)
|
||||
loadMimeTypeMapFrom(CfgProvider)
|
||||
loadFederationFrom(CfgProvider)
|
||||
|
|
|
@ -3257,6 +3257,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
|
||||
|
|
|
@ -13,6 +13,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/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
)
|
||||
|
@ -74,6 +75,8 @@ func CreateOrg(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordUserCreate(ctx, ctx.Doer, org.AsUser())
|
||||
|
||||
ctx.JSON(http.StatusCreated, convert.ToOrganization(ctx, org))
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,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/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"code.gitea.io/gitea/services/mailer"
|
||||
|
@ -152,6 +153,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.RecordUserCreate(ctx, ctx.Doer, u)
|
||||
|
||||
log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name)
|
||||
|
||||
// Send email notification.
|
||||
|
@ -199,7 +202,7 @@ func EditUser(ctx *context.APIContext) {
|
|||
MustChangePassword: optional.FromPtr(form.MustChangePassword),
|
||||
ProhibitLogin: optional.FromPtr(form.ProhibitLogin),
|
||||
}
|
||||
if err := user_service.UpdateAuth(ctx, ctx.ContextUser, authOpts); err != nil {
|
||||
if err := user_service.UpdateAuth(ctx, ctx.Doer, ctx.ContextUser, authOpts); err != nil {
|
||||
switch {
|
||||
case errors.Is(err, password.ErrMinLength):
|
||||
ctx.Error(http.StatusBadRequest, "PasswordTooShort", fmt.Errorf("password must be at least %d characters", setting.MinPasswordLength))
|
||||
|
@ -214,7 +217,7 @@ func EditUser(ctx *context.APIContext) {
|
|||
}
|
||||
|
||||
if form.Email != nil {
|
||||
if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, ctx.ContextUser, *form.Email); err != nil {
|
||||
if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, ctx.Doer, ctx.ContextUser, *form.Email); err != nil {
|
||||
switch {
|
||||
case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err):
|
||||
ctx.Error(http.StatusBadRequest, "EmailInvalid", err)
|
||||
|
@ -246,17 +249,15 @@ func EditUser(ctx *context.APIContext) {
|
|||
IsRestricted: optional.FromPtr(form.Restricted),
|
||||
}
|
||||
|
||||
if err := user_service.UpdateUser(ctx, ctx.ContextUser, opts); err != nil {
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, ctx.ContextUser, opts); err != nil {
|
||||
if models.IsErrDeleteLastAdminUser(err) {
|
||||
ctx.Error(http.StatusBadRequest, "LastAdmin", err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateOrSetPrimaryEmail", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
|
@ -298,7 +299,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) ||
|
||||
|
@ -479,7 +480,7 @@ func RenameUser(ctx *context.APIContext) {
|
|||
newName := web.GetForm(ctx).(*api.RenameUserOption).NewName
|
||||
|
||||
// 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"))
|
||||
|
|
|
@ -94,6 +94,7 @@ import (
|
|||
"code.gitea.io/gitea/routers/api/v1/user"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/services/actions"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/auth"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
@ -122,6 +123,9 @@ func sudo() func(ctx *context.APIContext) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordUserImpersonation(ctx, ctx.Doer, user)
|
||||
|
||||
log.Trace("Sudo from (%s) to: %s", ctx.Doer.Name, user.Name)
|
||||
ctx.Doer = user
|
||||
} else {
|
||||
|
|
|
@ -106,7 +106,7 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
|
|||
|
||||
opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
|
||||
|
||||
_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"), opt.Data)
|
||||
_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Doer, ctx.Org.Organization.AsUser(), nil, ctx.Params("secretname"), opt.Data)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
|
||||
|
@ -153,7 +153,7 @@ func (Action) DeleteSecret(ctx *context.APIContext) {
|
|||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
err := secret_service.DeleteSecretByName(ctx, ctx.Org.Organization.ID, 0, ctx.Params("secretname"))
|
||||
err := secret_service.DeleteSecretByName(ctx, ctx.Doer, ctx.Org.Organization.AsUser(), nil, ctx.Params("secretname"))
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.Error(http.StatusBadRequest, "DeleteSecret", err)
|
||||
|
|
|
@ -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
|
||||
// ---
|
||||
|
|
|
@ -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/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"code.gitea.io/gitea/services/org"
|
||||
|
@ -277,6 +278,8 @@ func Create(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordUserCreate(ctx, ctx.Doer, org.AsUser())
|
||||
|
||||
ctx.JSON(http.StatusCreated, convert.ToOrganization(ctx, org))
|
||||
}
|
||||
|
||||
|
@ -342,8 +345,10 @@ func Edit(ctx *context.APIContext) {
|
|||
|
||||
form := web.GetForm(ctx).(*api.EditOrgOption)
|
||||
|
||||
org := ctx.Org.Organization
|
||||
|
||||
if form.Email != "" {
|
||||
if err := user_service.ReplacePrimaryEmailAddress(ctx, ctx.Org.Organization.AsUser(), form.Email); err != nil {
|
||||
if err := user_service.ReplacePrimaryEmailAddress(ctx, ctx.Doer, org.AsUser(), form.Email); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "ReplacePrimaryEmailAddress", err)
|
||||
return
|
||||
}
|
||||
|
@ -357,12 +362,12 @@ func Edit(ctx *context.APIContext) {
|
|||
Visibility: optional.FromNonDefault(api.VisibilityModes[form.Visibility]),
|
||||
RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, ctx.Org.Organization.AsUser(), opts); err != nil {
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, org.AsUser(), opts); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToOrganization(ctx, ctx.Org.Organization))
|
||||
ctx.JSON(http.StatusOK, convert.ToOrganization(ctx, org))
|
||||
}
|
||||
|
||||
// Delete an organization
|
||||
|
@ -384,7 +389,7 @@ func Delete(ctx *context.APIContext) {
|
|||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if err := org.DeleteOrganization(ctx, ctx.Org.Organization, false); err != nil {
|
||||
if err := org.DeleteOrganization(ctx, ctx.Doer, ctx.Org.Organization, false); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "DeleteOrganization", err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -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/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
org_service "code.gitea.io/gitea/services/org"
|
||||
|
@ -249,6 +250,8 @@ func CreateTeam(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordOrganizationTeamAdd(ctx, ctx.Doer, ctx.Org.Organization, team)
|
||||
|
||||
apiTeam, err := convert.ToTeam(ctx, team, true)
|
||||
if err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
|
@ -284,6 +287,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
|
||||
|
@ -336,6 +346,11 @@ func EditTeam(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordOrganizationTeamUpdate(ctx, ctx.Doer, org, team)
|
||||
if isAuthChanged {
|
||||
audit.RecordOrganizationTeamPermission(ctx, ctx.Doer, org, team)
|
||||
}
|
||||
|
||||
apiTeam, err := convert.ToTeam(ctx, team)
|
||||
if err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
|
@ -362,10 +377,19 @@ func DeleteTeam(ctx *context.APIContext) {
|
|||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
org, err := organization.GetOrgByID(ctx, ctx.Org.Team.OrgID)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetOrgByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := models.DeleteTeam(ctx, ctx.Org.Team); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "DeleteTeam", err)
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordOrganizationTeamRemove(ctx, ctx.Doer, org, ctx.Org.Team)
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
|
@ -504,6 +528,15 @@ func AddTeamMember(ctx *context.APIContext) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
org, err := organization.GetOrgByID(ctx, ctx.Org.Team.OrgID)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetOrgByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordOrganizationTeamMemberAdd(ctx, ctx.Doer, org, ctx.Org.Team, u)
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
|
@ -541,6 +574,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.RecordOrganizationTeamMemberRemove(ctx, ctx.Doer, org, ctx.Org.Team, u)
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
|
@ -700,7 +742,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, ctx.Org.Team, repo); err != nil {
|
||||
if err := org_service.TeamAddRepository(ctx, ctx.Doer, ctx.Org.Team, repo); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "TeamAddRepository", err)
|
||||
return
|
||||
}
|
||||
|
@ -752,10 +794,11 @@ func RemoveTeamRepository(ctx *context.APIContext) {
|
|||
ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository")
|
||||
return
|
||||
}
|
||||
if err := repo_service.RemoveRepositoryFromTeam(ctx, ctx.Org.Team, repo.ID); err != nil {
|
||||
if err := repo_service.RemoveRepositoryFromTeam(ctx, ctx.Doer, ctx.Org.Team, repo); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "RemoveRepository", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
|
|
|
@ -117,12 +117,9 @@ func (Action) CreateOrUpdateSecret(ctx *context.APIContext) {
|
|||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
owner := ctx.Repo.Owner
|
||||
repo := ctx.Repo.Repository
|
||||
|
||||
opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
|
||||
|
||||
_, created, err := secret_service.CreateOrUpdateSecret(ctx, owner.ID, repo.ID, ctx.Params("secretname"), opt.Data)
|
||||
_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Doer, ctx.Repo.Owner, ctx.Repo.Repository, ctx.Params("secretname"), opt.Data)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
|
||||
|
@ -174,10 +171,7 @@ func (Action) DeleteSecret(ctx *context.APIContext) {
|
|||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
owner := ctx.Repo.Owner
|
||||
repo := ctx.Repo.Repository
|
||||
|
||||
err := secret_service.DeleteSecretByName(ctx, owner.ID, repo.ID, ctx.Params("secretname"))
|
||||
err := secret_service.DeleteSecretByName(ctx, ctx.Doer, ctx.Repo.Owner, ctx.Repo.Repository, ctx.Params("secretname"))
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.Error(http.StatusBadRequest, "DeleteSecret", err)
|
||||
|
|
|
@ -16,6 +16,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/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
|
@ -186,11 +187,16 @@ func AddCollaborator(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordRepositoryCollaboratorAdd(ctx, ctx.Doer, ctx.Repo.Repository, collaborator)
|
||||
|
||||
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.RecordRepositoryCollaboratorAccess(ctx, ctx.Doer, ctx.Repo.Repository, collaborator, accessMode)
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
|
@ -241,6 +247,9 @@ func DeleteCollaborator(ctx *context.APIContext) {
|
|||
ctx.Error(http.StatusInternalServerError, "DeleteCollaboration", err)
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordRepositoryCollaboratorRemove(ctx, ctx.Doer, ctx.Repo.Repository, collaborator)
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,15 +7,11 @@ import (
|
|||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
webhook_service "code.gitea.io/gitea/services/webhook"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m, &unittest.TestOptions{
|
||||
SetUp: func() error {
|
||||
setting.LoadQueueSettings()
|
||||
return webhook_service.Init()
|
||||
},
|
||||
SetUp: webhook_service.Init,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"code.gitea.io/gitea/services/issue"
|
||||
|
@ -748,6 +749,10 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
|
|||
return err
|
||||
}
|
||||
|
||||
if visibilityChanged {
|
||||
audit.RecordRepositoryVisibility(ctx, ctx.Doer, repo)
|
||||
}
|
||||
|
||||
log.Trace("Repository basic settings updated: %s/%s", owner.Name, repo.Name)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -205,13 +205,13 @@ 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(ctx, team, ctx.Repo.Repository)
|
||||
err = org_service.TeamAddRepository(ctx, 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 = repo_service.RemoveRepositoryFromTeam(ctx, team, ctx.Repo.Repository.ID)
|
||||
err = repo_service.RemoveRepositoryFromTeam(ctx, ctx.Doer, team, ctx.Repo.Repository)
|
||||
}
|
||||
if err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
|
|
|
@ -17,6 +17,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/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
|
@ -235,5 +236,11 @@ func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error {
|
|||
return repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams)
|
||||
}
|
||||
|
||||
return repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository)
|
||||
if err := repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
audit.RecordRepositoryTransferCancel(ctx, ctx.Doer, ctx.Repo.Repository)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ func CreateOrUpdateSecret(ctx *context.APIContext) {
|
|||
|
||||
opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
|
||||
|
||||
_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Doer.ID, 0, ctx.Params("secretname"), opt.Data)
|
||||
_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Doer, ctx.Doer, nil, ctx.Params("secretname"), opt.Data)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.Error(http.StatusBadRequest, "CreateOrUpdateSecret", err)
|
||||
|
@ -91,7 +91,7 @@ func DeleteSecret(ctx *context.APIContext) {
|
|||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
err := secret_service.DeleteSecretByName(ctx, ctx.Doer.ID, 0, ctx.Params("secretname"))
|
||||
err := secret_service.DeleteSecretByName(ctx, ctx.Doer, ctx.Doer, nil, ctx.Params("secretname"))
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.Error(http.StatusBadRequest, "DeleteSecret", err)
|
||||
|
|
|
@ -14,8 +14,10 @@ import (
|
|||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
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/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
)
|
||||
|
@ -124,6 +126,9 @@ func CreateAccessToken(ctx *context.APIContext) {
|
|||
ctx.Error(http.StatusInternalServerError, "NewAccessToken", err)
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordUserAccessTokenAdd(ctx, ctx.Doer, ctx.Doer, t)
|
||||
|
||||
ctx.JSON(http.StatusCreated, &api.AccessToken{
|
||||
Name: t.Name,
|
||||
Token: t.Token,
|
||||
|
@ -163,6 +168,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 := db.Find[auth_model.AccessToken](ctx, auth_model.ListAccessTokensOptions{
|
||||
Name: token,
|
||||
|
@ -178,18 +184,25 @@ 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 {
|
||||
var err error
|
||||
t, _, err = db.GetByID[auth_model.AccessToken](ctx, tokenID)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetByID", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if tokenID == 0 {
|
||||
ctx.Error(http.StatusInternalServerError, "Invalid TokenID", nil)
|
||||
if t == nil {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if err := auth_model.DeleteAccessTokenByID(ctx, tokenID, ctx.ContextUser.ID); err != nil {
|
||||
if err := auth_model.DeleteAccessTokenByID(ctx, t.ID, ctx.ContextUser.ID); err != nil {
|
||||
if auth_model.IsErrAccessTokenNotExist(err) {
|
||||
ctx.NotFound()
|
||||
} else {
|
||||
|
@ -198,6 +211,8 @@ func DeleteAccessToken(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordUserAccessTokenRemove(ctx, ctx.Doer, ctx.Doer, t)
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
|
@ -239,6 +254,8 @@ func CreateOauth2Application(ctx *context.APIContext) {
|
|||
}
|
||||
app.ClientSecret = secret
|
||||
|
||||
audit.RecordOAuth2ApplicationAdd(ctx, ctx.Doer, ctx.Doer, app)
|
||||
|
||||
ctx.JSON(http.StatusCreated, convert.ToOAuth2Application(app))
|
||||
}
|
||||
|
||||
|
@ -300,8 +317,18 @@ func DeleteOauth2Application(ctx *context.APIContext) {
|
|||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
appID := ctx.ParamsInt64(":id")
|
||||
if err := auth_model.DeleteOAuth2Application(ctx, 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(ctx, app.ID, ctx.Doer.ID); err != nil {
|
||||
if auth_model.IsErrOAuthApplicationNotFound(err) {
|
||||
ctx.NotFound()
|
||||
} else {
|
||||
|
@ -310,6 +337,8 @@ func DeleteOauth2Application(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordOAuth2ApplicationRemove(ctx, ctx.Doer, ctx.Doer, app)
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
|
@ -401,5 +430,7 @@ func UpdateOauth2Application(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordOAuth2ApplicationUpdate(ctx, ctx.Doer, ctx.Doer, app)
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToOAuth2Application(app))
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ func AddEmail(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := user_service.AddEmailAddresses(ctx, ctx.Doer, form.Emails); err != nil {
|
||||
if err := user_service.AddEmailAddresses(ctx, ctx.Doer, ctx.Doer, form.Emails); err != nil {
|
||||
if user_model.IsErrEmailAlreadyUsed(err) {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "", "Email address has been used: "+err.(user_model.ErrEmailAlreadyUsed).Email)
|
||||
} else if user_model.IsErrEmailCharIsNotSupported(err) || user_model.IsErrEmailInvalid(err) {
|
||||
|
@ -120,7 +120,7 @@ func DeleteEmail(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := user_service.DeleteEmailAddresses(ctx, ctx.Doer, form.Emails); err != nil {
|
||||
if err := user_service.DeleteEmailAddresses(ctx, ctx.Doer, ctx.Doer, form.Emails); err != nil {
|
||||
if user_model.IsErrEmailAddressNotExist(err) {
|
||||
ctx.Error(http.StatusNotFound, "DeleteEmailAddresses", err)
|
||||
} else {
|
||||
|
|
|
@ -56,7 +56,7 @@ func UpdateUserSettings(ctx *context.APIContext) {
|
|||
KeepEmailPrivate: optional.FromPtr(form.HideEmail),
|
||||
KeepActivityPrivate: optional.FromPtr(form.HideActivity),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, ctx.Doer, opts); err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import (
|
|||
web_routers "code.gitea.io/gitea/routers/web"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
"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"
|
||||
|
@ -172,6 +173,8 @@ func InitWebInstalled(ctx context.Context) {
|
|||
|
||||
actions_service.Init()
|
||||
|
||||
mustInit(audit.Init)
|
||||
|
||||
// Finally start up the cron
|
||||
cron.NewContext(ctx)
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
auth_service "code.gitea.io/gitea/services/auth"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
@ -566,6 +567,8 @@ func SubmitInstall(ctx *context.Context) {
|
|||
u, _ = user_model.GetUserByName(ctx, u.Name)
|
||||
}
|
||||
|
||||
audit.RecordUserCreate(ctx, u, u)
|
||||
|
||||
nt, token, err := auth_service.CreateAuthTokenForUserID(ctx, u.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("CreateAuthTokenForUserID", err)
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
timeutil "code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
gitea_context "code.gitea.io/gitea/services/context"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
|
@ -228,6 +229,10 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
|
|||
})
|
||||
return
|
||||
}
|
||||
|
||||
if isPrivate.Has() {
|
||||
audit.RecordRepositoryVisibility(ctx, pusher, repo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ 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/modules/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
user_setting "code.gitea.io/gitea/routers/web/user/setting"
|
||||
|
@ -20,9 +21,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,
|
||||
|
@ -51,7 +53,7 @@ func ApplicationsPost(ctx *context.Context) {
|
|||
ctx.Data["Title"] = ctx.Tr("settings.applications")
|
||||
ctx.Data["PageIsAdminApplications"] = true
|
||||
|
||||
oa := newOAuth2CommonHandlers()
|
||||
oa := newOAuth2CommonHandlers(ctx.Doer)
|
||||
oa.AddApp(ctx)
|
||||
}
|
||||
|
||||
|
@ -59,7 +61,7 @@ func ApplicationsPost(ctx *context.Context) {
|
|||
func EditApplication(ctx *context.Context) {
|
||||
ctx.Data["PageIsAdminApplications"] = true
|
||||
|
||||
oa := newOAuth2CommonHandlers()
|
||||
oa := newOAuth2CommonHandlers(ctx.Doer)
|
||||
oa.EditShow(ctx)
|
||||
}
|
||||
|
||||
|
@ -68,7 +70,7 @@ func EditApplicationPost(ctx *context.Context) {
|
|||
ctx.Data["Title"] = ctx.Tr("settings.applications")
|
||||
ctx.Data["PageIsAdminApplications"] = true
|
||||
|
||||
oa := newOAuth2CommonHandlers()
|
||||
oa := newOAuth2CommonHandlers(ctx.Doer)
|
||||
oa.EditSave(ctx)
|
||||
}
|
||||
|
||||
|
@ -77,13 +79,13 @@ func ApplicationsRegenerateSecret(ctx *context.Context) {
|
|||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
53
routers/web/admin/audit.go
Normal file
53
routers/web/admin/audit.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2024 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/setting"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
|
@ -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"
|
||||
|
@ -301,13 +302,15 @@ func NewAuthSourcePost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := auth.CreateSource(ctx, &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(ctx, 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)
|
||||
|
@ -321,6 +324,8 @@ func NewAuthSourcePost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordSystemAuthenticationSourceAdd(ctx, ctx.Doer, source)
|
||||
|
||||
log.Trace("Authentication created by admin(%s): %s", ctx.Doer.Name, form.Name)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("admin.auths.new_success", form.Name))
|
||||
|
@ -434,6 +439,9 @@ func EditAuthSourcePost(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordSystemAuthenticationSourceUpdate(ctx, ctx.Doer, source)
|
||||
|
||||
log.Trace("Authentication changed by admin(%s): %d", ctx.Doer.Name, source.ID)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("admin.auths.update_success"))
|
||||
|
@ -448,7 +456,7 @@ func DeleteAuthSource(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if err = auth_service.DeleteSource(ctx, source); err != nil {
|
||||
if err = auth_service.DeleteSource(ctx, ctx.Doer, source); err != nil {
|
||||
if auth.IsErrSourceInUse(err) {
|
||||
ctx.Flash.Error(ctx.Tr("admin.auths.still_in_used"))
|
||||
} else {
|
||||
|
@ -457,6 +465,7 @@ func DeleteAuthSource(ctx *context.Context) {
|
|||
ctx.JSONRedirect(setting.AppSubURL + "/admin/auths/" + url.PathEscape(ctx.Params(":authid")))
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Authentication deleted by admin(%s): %d", ctx.Doer.Name, source.ID)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("admin.auths.deletion_success"))
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
|
@ -111,26 +112,34 @@ 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)
|
||||
|
||||
if 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"))
|
||||
} else {
|
||||
ctx.Flash.Error(ctx.Tr("admin.emails.not_updated", err))
|
||||
}
|
||||
u, err := user_model.GetUserByID(ctx, uid)
|
||||
if err != nil {
|
||||
ctx.Flash.Error(ctx.Tr("admin.emails.not_updated", err))
|
||||
} 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"))
|
||||
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"))
|
||||
} else {
|
||||
ctx.Flash.Error(ctx.Tr("admin.emails.not_updated", err))
|
||||
}
|
||||
} else {
|
||||
if activate {
|
||||
audit.RecordUserEmailActivate(ctx, ctx.Doer, u, email)
|
||||
}
|
||||
|
||||
log.Info("Activation for User ID: %d, email: %s changed to %v", uid, email, activate)
|
||||
ctx.Flash.Info(ctx.Tr("admin.emails.updated"))
|
||||
}
|
||||
}
|
||||
|
||||
redirect, _ := url.Parse(setting.AppSubURL + "/admin/emails")
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
|
@ -61,9 +62,17 @@ func DefaultOrSystemWebhooks(ctx *context.Context) {
|
|||
|
||||
// DeleteDefaultOrSystemWebhook handler to delete an admin-defined system or default webhook
|
||||
func DeleteDefaultOrSystemWebhook(ctx *context.Context) {
|
||||
hook, err := webhook.GetSystemOrDefaultWebhook(ctx, ctx.FormInt64("id"))
|
||||
if err != nil {
|
||||
ctx.ServerError("GetSystemOrDefaultWebhook", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := webhook.DeleteDefaultSystemWebhook(ctx, ctx.FormInt64("id")); err != nil {
|
||||
ctx.Flash.Error("DeleteDefaultWebhook: " + err.Error())
|
||||
} else {
|
||||
audit.RecordWebhookRemove(ctx, ctx.Doer, nil, nil, hook)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"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/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
"code.gitea.io/gitea/services/mailer"
|
||||
|
@ -207,6 +208,8 @@ func NewUserPost(ctx *context.Context) {
|
|||
ctx.Flash.Warning(ctx.Tr("form.email_domain_is_not_allowed", u.Email))
|
||||
}
|
||||
|
||||
audit.RecordUserCreate(ctx, ctx.Doer, u)
|
||||
|
||||
log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name)
|
||||
|
||||
// Send email notification.
|
||||
|
@ -348,7 +351,7 @@ func EditUserPost(ctx *context.Context) {
|
|||
}
|
||||
|
||||
if form.UserName != "" {
|
||||
if err := user_service.RenameUser(ctx, u, form.UserName); err != nil {
|
||||
if err := user_service.RenameUser(ctx, ctx.Doer, u, form.UserName); err != nil {
|
||||
switch {
|
||||
case user_model.IsErrUserIsNotLocal(err):
|
||||
ctx.Data["Err_UserName"] = true
|
||||
|
@ -391,7 +394,7 @@ func EditUserPost(ctx *context.Context) {
|
|||
authOpts.LoginSource = optional.Some(authSource)
|
||||
}
|
||||
|
||||
if err := user_service.UpdateAuth(ctx, u, authOpts); err != nil {
|
||||
if err := user_service.UpdateAuth(ctx, ctx.Doer, u, authOpts); err != nil {
|
||||
switch {
|
||||
case errors.Is(err, password.ErrMinLength):
|
||||
ctx.Data["Err_Password"] = true
|
||||
|
@ -406,13 +409,13 @@ func EditUserPost(ctx *context.Context) {
|
|||
ctx.Data["Err_Password"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplUserEdit, &form)
|
||||
default:
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
ctx.ServerError("UpdateAuth", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if form.Email != "" {
|
||||
if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, u, form.Email); err != nil {
|
||||
if err := user_service.AdminAddOrSetPrimaryEmailAddress(ctx, ctx.Doer, u, form.Email); err != nil {
|
||||
switch {
|
||||
case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err):
|
||||
ctx.Data["Err_Email"] = true
|
||||
|
@ -445,7 +448,7 @@ func EditUserPost(ctx *context.Context) {
|
|||
Language: optional.Some(form.Language),
|
||||
}
|
||||
|
||||
if err := user_service.UpdateUser(ctx, u, opts); err != nil {
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, u, opts); err != nil {
|
||||
if models.IsErrDeleteLastAdminUser(err) {
|
||||
ctx.RenderWithErr(ctx.Tr("auth.last_admin"), tplUserEdit, &form)
|
||||
} else {
|
||||
|
@ -499,7 +502,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"))
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/externalaccount"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
@ -52,6 +53,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(ctx, id)
|
||||
if err != nil {
|
||||
ctx.ServerError("UserSignIn", err)
|
||||
|
@ -67,11 +75,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, ctx.Session, u)
|
||||
|
@ -91,6 +94,8 @@ func TwoFactorPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordUserAuthenticationFailTwoFactor(ctx, u)
|
||||
|
||||
ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplTwofa, forms.TwoFactorAuthForm{})
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,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"
|
||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
|
@ -109,7 +110,7 @@ func resetLocale(ctx *context.Context, u *user_model.User) error {
|
|||
opts := &user_service.UpdateOptions{
|
||||
Language: optional.Some(ctx.Locale.Language()),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, u, opts); err != nil {
|
||||
if err := user_service.UpdateUser(ctx, u, u, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -347,7 +348,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
|
|||
opts := &user_service.UpdateOptions{
|
||||
Language: optional.Some(ctx.Locale.Language()),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, u, opts); err != nil {
|
||||
if err := user_service.UpdateUser(ctx, u, u, opts); err != nil {
|
||||
ctx.ServerError("UpdateUser Language", fmt.Errorf("Error updating user language [user: %d, locale: %s]", u.ID, ctx.Locale.Language()))
|
||||
return setting.AppSubURL + "/"
|
||||
}
|
||||
|
@ -363,7 +364,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
|
|||
ctx.Csrf.DeleteCookie(ctx)
|
||||
|
||||
// Register last login
|
||||
if err := user_service.UpdateUser(ctx, u, &user_service.UpdateOptions{SetLastLogin: true}); err != nil {
|
||||
if err := user_service.UpdateUser(ctx, u, u, &user_service.UpdateOptions{SetLastLogin: true}); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return setting.AppSubURL + "/"
|
||||
}
|
||||
|
@ -599,6 +600,9 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *us
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
audit.RecordUserCreate(ctx, user_model.NewAuthenticationSourceUser(), u)
|
||||
|
||||
log.Trace("Account created: %s", u.Name)
|
||||
return true
|
||||
}
|
||||
|
@ -614,7 +618,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
|
|||
IsAdmin: optional.Some(true),
|
||||
SetLastLogin: true,
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, u, opts); err != nil {
|
||||
if err := user_service.UpdateUser(ctx, u, u, opts); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return false
|
||||
}
|
||||
|
@ -785,12 +789,16 @@ 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.RecordUserActive(ctx, user, user)
|
||||
audit.RecordUserEmailActivate(ctx, user, user, email)
|
||||
|
||||
log.Trace("User activated: %s", user.Name)
|
||||
|
||||
if err := updateSession(ctx, nil, map[string]any{
|
||||
|
@ -807,7 +815,7 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := user_service.UpdateUser(ctx, user, &user_service.UpdateOptions{SetLastLogin: true}); err != nil {
|
||||
if err := user_service.UpdateUser(ctx, user, user, &user_service.UpdateOptions{SetLastLogin: true}); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
@ -828,7 +836,7 @@ func ActivateEmail(ctx *context.Context) {
|
|||
emailStr := ctx.FormString("email")
|
||||
|
||||
// Verify code.
|
||||
if email := user_model.VerifyActiveEmailCode(ctx, code, emailStr); email != nil {
|
||||
if user, email := user_model.VerifyActiveEmailCode(ctx, code, emailStr); user != nil && email != nil {
|
||||
if err := user_model.ActivateEmail(ctx, email); err != nil {
|
||||
ctx.ServerError("ActivateEmail", err)
|
||||
}
|
||||
|
@ -836,12 +844,10 @@ 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_" + u.LowerName)
|
||||
}
|
||||
// Allow user to validate more emails
|
||||
_ = ctx.Cache.Delete("MailResendLimit_" + user.LowerName)
|
||||
|
||||
audit.RecordUserEmailActivate(ctx, user, user, email)
|
||||
}
|
||||
|
||||
// FIXME: e-mail verification does not require the user to be logged in,
|
||||
|
|
|
@ -30,6 +30,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"
|
||||
|
@ -546,6 +547,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{
|
||||
|
@ -555,6 +567,9 @@ func GrantApplicationOAuth(ctx *context.Context) {
|
|||
}, form.RedirectURI)
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordUserOAuth2ApplicationGrant(ctx, ctx.Doer, owner, app, grant)
|
||||
|
||||
if len(form.Nonce) > 0 {
|
||||
err := grant.SetNonce(ctx, form.Nonce)
|
||||
if err != nil {
|
||||
|
@ -1141,7 +1156,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, u, opts); err != nil {
|
||||
if err := user_service.UpdateUser(ctx, user_model.NewAuthenticationSourceUser(), u, opts); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
@ -1178,7 +1193,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, u, opts); err != nil {
|
||||
if err := user_service.UpdateUser(ctx, user_model.NewAuthenticationSourceUser(), u, opts); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -15,6 +15,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"
|
||||
"code.gitea.io/gitea/services/auth"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
@ -280,6 +281,8 @@ func ConnectOpenIDPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordUserOpenIDAdd(ctx, u, u, userOID)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.add_openid_success"))
|
||||
|
||||
remember, _ := ctx.Session.Get("openid_signin_remember").(bool)
|
||||
|
@ -384,6 +387,8 @@ func RegisterOpenIDPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordUserOpenIDAdd(ctx, u, u, userOID)
|
||||
|
||||
remember, _ := ctx.Session.Get("openid_signin_remember").(bool)
|
||||
log.Trace("Session stored openid-remember: %t", remember)
|
||||
handleSignIn(ctx, u, remember)
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
"code.gitea.io/gitea/services/mailer"
|
||||
|
@ -88,6 +89,8 @@ func ForgotPasswdPost(ctx *context.Context) {
|
|||
|
||||
mailer.SendResetPasswordMail(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)
|
||||
}
|
||||
|
@ -185,6 +188,8 @@ func ResetPasswdPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
if !ok || twofa.LastUsedPasscode == passcode {
|
||||
audit.RecordUserAuthenticationFailTwoFactor(ctx, u)
|
||||
|
||||
ctx.Data["IsResetForm"] = true
|
||||
ctx.Data["Err_Passcode"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplResetPassword, nil)
|
||||
|
@ -203,7 +208,7 @@ func ResetPasswdPost(ctx *context.Context) {
|
|||
Password: optional.Some(ctx.FormString("password")),
|
||||
MustChangePassword: optional.Some(false),
|
||||
}
|
||||
if err := user_service.UpdateAuth(ctx, u, opts); err != nil {
|
||||
if err := user_service.UpdateAuth(ctx, ctx.Doer, u, opts); err != nil {
|
||||
ctx.Data["IsResetForm"] = true
|
||||
ctx.Data["Err_Password"] = true
|
||||
switch {
|
||||
|
@ -221,7 +226,6 @@ func ResetPasswdPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
log.Trace("User password reset: %s", u.Name)
|
||||
ctx.Data["IsResetFailed"] = true
|
||||
remember := len(ctx.FormString("remember")) != 0
|
||||
|
||||
|
@ -285,7 +289,7 @@ func MustChangePasswordPost(ctx *context.Context) {
|
|||
Password: optional.Some(form.Password),
|
||||
MustChangePassword: optional.Some(false),
|
||||
}
|
||||
if err := user_service.UpdateAuth(ctx, ctx.Doer, opts); err != nil {
|
||||
if err := user_service.UpdateAuth(ctx, ctx.Doer, ctx.Doer, opts); err != nil {
|
||||
switch {
|
||||
case errors.Is(err, password.ErrMinLength):
|
||||
ctx.Data["Err_Password"] = true
|
||||
|
@ -307,8 +311,6 @@ func MustChangePasswordPost(ctx *context.Context) {
|
|||
|
||||
ctx.Flash.Success(ctx.Tr("settings.change_password_success"))
|
||||
|
||||
log.Trace("User updated password: %s", ctx.Doer.Name)
|
||||
|
||||
if redirectTo := ctx.GetSiteCookie("redirect_to"); redirectTo != "" {
|
||||
middleware.DeleteRedirectToCookie(ctx.Resp)
|
||||
ctx.RedirectToCurrentSite(redirectTo)
|
||||
|
|
|
@ -15,6 +15,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/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
)
|
||||
|
@ -74,6 +75,9 @@ func CreatePost(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordUserCreate(ctx, ctx.Doer, org.AsUser())
|
||||
|
||||
log.Trace("Organization created: %s", org.Name)
|
||||
|
||||
ctx.Redirect(org.AsUser().DashboardLink())
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/web"
|
||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||
user_setting "code.gitea.io/gitea/routers/web/user/setting"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
org_service "code.gitea.io/gitea/services/org"
|
||||
|
@ -73,7 +74,7 @@ func SettingsPost(ctx *context.Context) {
|
|||
org := ctx.Org.Organization
|
||||
|
||||
if org.Name != form.Name {
|
||||
if err := user_service.RenameUser(ctx, org.AsUser(), form.Name); err != nil {
|
||||
if err := user_service.RenameUser(ctx, ctx.Doer, org.AsUser(), form.Name); err != nil {
|
||||
if user_model.IsErrUserAlreadyExist(err) {
|
||||
ctx.Data["Err_Name"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplSettingsOptions, &form)
|
||||
|
@ -93,13 +94,15 @@ func SettingsPost(ctx *context.Context) {
|
|||
}
|
||||
|
||||
if form.Email != "" {
|
||||
if err := user_service.ReplacePrimaryEmailAddress(ctx, org.AsUser(), form.Email); err != nil {
|
||||
if err := user_service.ReplacePrimaryEmailAddress(ctx, ctx.Doer, org.AsUser(), form.Email); err != nil {
|
||||
ctx.Data["Err_Email"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplSettingsOptions, &form)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
oldVisibility := org.Visibility
|
||||
|
||||
opts := &user_service.UpdateOptions{
|
||||
FullName: optional.Some(form.FullName),
|
||||
Description: optional.Some(form.Description),
|
||||
|
@ -111,16 +114,13 @@ func SettingsPost(ctx *context.Context) {
|
|||
if ctx.Doer.IsAdmin {
|
||||
opts.MaxRepoCreation = optional.Some(form.MaxRepoCreation)
|
||||
}
|
||||
|
||||
visibilityChanged := org.Visibility != form.Visibility
|
||||
|
||||
if err := user_service.UpdateUser(ctx, org.AsUser(), opts); err != nil {
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, org.AsUser(), opts); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
// update forks visibility
|
||||
if visibilityChanged {
|
||||
if org.Visibility != oldVisibility {
|
||||
repos, _, err := repo_model.GetUserRepositories(ctx, &repo_model.SearchRepoOptions{
|
||||
Actor: org.AsUser(), Private: true, ListOptions: db.ListOptions{Page: 1, PageSize: org.NumRepos},
|
||||
})
|
||||
|
@ -177,7 +177,7 @@ func SettingsDelete(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := org_service.DeleteOrganization(ctx, ctx.Org.Organization, false); err != nil {
|
||||
if err := org_service.DeleteOrganization(ctx, ctx.Doer, ctx.Org.Organization, false); err != nil {
|
||||
if models.IsErrUserOwnRepos(err) {
|
||||
ctx.Flash.Error(ctx.Tr("form.org_still_own_repo"))
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/settings/delete")
|
||||
|
@ -230,9 +230,17 @@ func Webhooks(ctx *context.Context) {
|
|||
|
||||
// DeleteWebhook response for delete webhook
|
||||
func DeleteWebhook(ctx *context.Context) {
|
||||
hook, err := webhook.GetWebhookByOwnerID(ctx, ctx.Org.Organization.ID, ctx.FormInt64("id"))
|
||||
if err != nil {
|
||||
ctx.ServerError("GetWebhookByOwnerID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := webhook.DeleteWebhookByOwnerID(ctx, ctx.Org.Organization.ID, ctx.FormInt64("id")); err != nil {
|
||||
ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error())
|
||||
} else {
|
||||
audit.RecordWebhookRemove(ctx, ctx.Doer, ctx.Org.Organization.AsUser(), nil, hook)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
|
||||
}
|
||||
|
||||
|
|
56
routers/web/org/setting/audit.go
Normal file
56
routers/web/org/setting/audit.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
// 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/setting"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
|
@ -9,6 +9,8 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
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/setting"
|
||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||
|
@ -21,11 +23,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,
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +63,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)
|
||||
}
|
||||
|
||||
|
@ -69,7 +72,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)
|
||||
}
|
||||
|
||||
|
@ -79,7 +82,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)
|
||||
}
|
||||
|
||||
|
@ -89,13 +92,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)
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
@ -79,6 +80,9 @@ func TeamsAction(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
err = models.AddTeamMember(ctx, ctx.Org.Team, ctx.Doer)
|
||||
if err == nil {
|
||||
audit.RecordOrganizationTeamMemberAdd(ctx, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, ctx.Doer)
|
||||
}
|
||||
case "leave":
|
||||
err = models.RemoveTeamMember(ctx, ctx.Org.Team, ctx.Doer)
|
||||
if err != nil {
|
||||
|
@ -92,6 +96,8 @@ func TeamsAction(ctx *context.Context) {
|
|||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
audit.RecordOrganizationTeamMemberRemove(ctx, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, ctx.Doer)
|
||||
}
|
||||
checkIsOrgMemberAndRedirect(ctx, ctx.Org.OrgLink+"/teams/")
|
||||
return
|
||||
|
@ -119,6 +125,8 @@ func TeamsAction(ctx *context.Context) {
|
|||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
audit.RecordOrganizationTeamMemberRemove(ctx, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, user)
|
||||
}
|
||||
checkIsOrgMemberAndRedirect(ctx, ctx.Org.OrgLink+"/teams/"+url.PathEscape(ctx.Org.Team.LowerName))
|
||||
return
|
||||
|
@ -163,6 +171,9 @@ func TeamsAction(ctx *context.Context) {
|
|||
ctx.Flash.Error(ctx.Tr("org.teams.add_duplicate_users"))
|
||||
} else {
|
||||
err = models.AddTeamMember(ctx, ctx.Org.Team, u)
|
||||
if err == nil {
|
||||
audit.RecordOrganizationTeamMemberAdd(ctx, ctx.Doer, ctx.Org.Organization, ctx.Org.Team, u)
|
||||
}
|
||||
}
|
||||
|
||||
page = "team"
|
||||
|
@ -233,13 +244,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, ctx.Org.Organization.ID, repoName)
|
||||
repo, err := repo_model.GetRepositoryByName(ctx, 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"))
|
||||
|
@ -249,19 +257,44 @@ func TeamsRepoAction(ctx *context.Context) {
|
|||
ctx.ServerError("GetRepositoryByName", err)
|
||||
return
|
||||
}
|
||||
err = org_service.TeamAddRepository(ctx, ctx.Org.Team, repo)
|
||||
if err := org_service.TeamAddRepository(ctx, ctx.Doer, ctx.Org.Team, repo); err != nil {
|
||||
ctx.ServerError("TeamAddRepository "+ctx.Org.Team.Name, err)
|
||||
return
|
||||
}
|
||||
case "remove":
|
||||
err = repo_service.RemoveRepositoryFromTeam(ctx, ctx.Org.Team, ctx.FormInt64("repoid"))
|
||||
repo, err := repo_model.GetRepositoryByID(ctx, ctx.FormInt64("repoid"))
|
||||
if err != nil {
|
||||
ctx.ServerError("GetRepositoryByID", err)
|
||||
return
|
||||
}
|
||||
if err := repo_service.RemoveRepositoryFromTeam(ctx, ctx.Doer, ctx.Org.Team, repo); err != nil {
|
||||
ctx.ServerError("RemoveRepositoryFromTeam "+ctx.Org.Team.Name, err)
|
||||
return
|
||||
}
|
||||
case "addall":
|
||||
err = models.AddAllRepositories(ctx, ctx.Org.Team)
|
||||
case "removeall":
|
||||
err = models.RemoveAllRepositories(ctx, ctx.Org.Team)
|
||||
}
|
||||
added, err := models.AddAllRepositories(ctx, ctx.Org.Team)
|
||||
if err != nil {
|
||||
ctx.ServerError("AddAllRepositories "+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
|
||||
for _, repo := range added {
|
||||
audit.RecordRepositoryCollaboratorTeamAdd(ctx, ctx.Doer, repo, ctx.Org.Team)
|
||||
}
|
||||
case "removeall":
|
||||
if err := ctx.Org.Team.LoadRepositories(ctx); err != nil {
|
||||
ctx.ServerError("LoadRepositories "+ctx.Org.Team.Name, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := models.RemoveAllRepositories(ctx, ctx.Org.Team); err != nil {
|
||||
ctx.ServerError("RemoveAllRepositories "+ctx.Org.Team.Name, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, repo := range ctx.Org.Team.Repos {
|
||||
audit.RecordRepositoryCollaboratorTeamRemove(ctx, ctx.Doer, repo, ctx.Org.Team)
|
||||
}
|
||||
}
|
||||
|
||||
if action == "addall" || action == "removeall" {
|
||||
|
@ -368,6 +401,9 @@ func NewTeamPost(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
@ -546,6 +582,12 @@ func EditTeamPost(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordOrganizationTeamUpdate(ctx, ctx.Doer, ctx.Org.Organization, t)
|
||||
if isAuthChanged {
|
||||
audit.RecordOrganizationTeamPermission(ctx, ctx.Doer, ctx.Org.Organization, t)
|
||||
}
|
||||
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(t.LowerName))
|
||||
}
|
||||
|
||||
|
@ -554,6 +596,8 @@ func DeleteTeam(ctx *context.Context) {
|
|||
if err := models.DeleteTeam(ctx, ctx.Org.Team); err != nil {
|
||||
ctx.Flash.Error("DeleteTeam: " + err.Error())
|
||||
} else {
|
||||
audit.RecordOrganizationTeamRemove(ctx, ctx.Doer, ctx.Org.Organization, ctx.Org.Team)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("org.teams.delete_team_success"))
|
||||
}
|
||||
|
||||
|
@ -598,6 +642,8 @@ func TeamInvitePost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ func SetDiffViewStyle(ctx *context.Context) {
|
|||
opts := &user_service.UpdateOptions{
|
||||
DiffViewStyle: optional.Some(style),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, ctx.Doer, opts); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,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/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
@ -402,6 +403,9 @@ func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) error {
|
|||
if err := repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
audit.RecordRepositoryTransferCancel(ctx, ctx.Doer, ctx.Repo.Repository)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected"))
|
||||
}
|
||||
|
||||
|
|
55
routers/web/repo/setting/audit.go
Normal file
55
routers/web/repo/setting/audit.go
Normal file
|
@ -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/setting"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
|
@ -16,6 +16,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/log"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/mailer"
|
||||
org_service "code.gitea.io/gitea/services/org"
|
||||
|
@ -114,19 +115,32 @@ func CollaborationPost(ctx *context.Context) {
|
|||
mailer.SendCollaboratorMail(u, ctx.Doer, ctx.Repo.Repository)
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
accessMode := perm.AccessMode(ctx.FormInt("mode"))
|
||||
|
||||
if err := repo_model.ChangeCollaborationAccessMode(
|
||||
ctx,
|
||||
ctx.Repo.Repository,
|
||||
ctx.FormInt64("uid"),
|
||||
perm.AccessMode(ctx.FormInt("mode"))); err != nil {
|
||||
u.ID,
|
||||
accessMode); err != nil {
|
||||
log.Error("ChangeCollaborationAccessMode: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordRepositoryCollaboratorAccess(ctx, ctx.Doer, ctx.Repo.Repository, u, accessMode)
|
||||
}
|
||||
|
||||
// DeleteCollaboration delete a collaboration for a repository
|
||||
|
@ -142,6 +156,8 @@ func DeleteCollaboration(ctx *context.Context) {
|
|||
if err := repo_service.DeleteCollaboration(ctx, ctx.Repo.Repository, collaborator); err != nil {
|
||||
ctx.Flash.Error("DeleteCollaboration: " + err.Error())
|
||||
} else {
|
||||
audit.RecordRepositoryCollaboratorRemove(ctx, ctx.Doer, ctx.Repo.Repository, collaborator)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success"))
|
||||
}
|
||||
}
|
||||
|
@ -186,7 +202,7 @@ func AddTeamPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if err = org_service.TeamAddRepository(ctx, team, ctx.Repo.Repository); err != nil {
|
||||
if err = org_service.TeamAddRepository(ctx, ctx.Doer, team, ctx.Repo.Repository); err != nil {
|
||||
ctx.ServerError("TeamAddRepository", err)
|
||||
return
|
||||
}
|
||||
|
@ -209,11 +225,13 @@ func DeleteTeam(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if err = repo_service.RemoveRepositoryFromTeam(ctx, team, ctx.Repo.Repository.ID); err != nil {
|
||||
if err = repo_service.RemoveRepositoryFromTeam(ctx, ctx.Doer, team, ctx.Repo.Repository); err != nil {
|
||||
ctx.ServerError("team.RemoveRepositorys", err)
|
||||
return
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ func SetDefaultBranchPost(ctx *context.Context) {
|
|||
}
|
||||
|
||||
branch := ctx.FormString("branch")
|
||||
if err := repo_service.SetRepoDefaultBranch(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, branch); err != nil {
|
||||
if err := repo_service.SetRepoDefaultBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branch); err != nil {
|
||||
switch {
|
||||
case git_model.IsErrBranchNotExist(err):
|
||||
ctx.Status(http.StatusNotFound)
|
||||
|
|
|
@ -12,6 +12,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/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
)
|
||||
|
@ -92,6 +93,8 @@ func DeployKeysPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
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))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys")
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/web/repo"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
|
@ -234,6 +235,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,
|
||||
|
@ -247,6 +250,12 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if isNewProtectedBranch {
|
||||
audit.RecordRepositoryBranchProtectionAdd(ctx, ctx.Doer, ctx.Repo.Repository, protectBranch)
|
||||
} else {
|
||||
audit.RecordRepositoryBranchProtectionUpdate(ctx, ctx.Doer, ctx.Repo.Repository, protectBranch)
|
||||
}
|
||||
|
||||
// FIXME: since we only need to recheck files protected rules, we could improve this
|
||||
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, protectBranch.RuleName)
|
||||
if err != nil {
|
||||
|
@ -292,6 +301,8 @@ func DeleteProtectedBranchRulePost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
)
|
||||
|
@ -63,6 +64,8 @@ func NewProtectedTagPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
@ -116,6 +119,8 @@ func EditProtectedTagPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
@ -132,6 +137,8 @@ func DeleteProtectedTagPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
shared "code.gitea.io/gitea/routers/web/shared/secrets"
|
||||
|
@ -22,8 +24,8 @@ const (
|
|||
)
|
||||
|
||||
type secretsCtx struct {
|
||||
OwnerID int64
|
||||
RepoID int64
|
||||
Owner *user_model.User
|
||||
Repo *repo_model.Repository
|
||||
IsRepo bool
|
||||
IsOrg bool
|
||||
IsUser bool
|
||||
|
@ -34,8 +36,8 @@ type secretsCtx struct {
|
|||
func getSecretsCtx(ctx *context.Context) (*secretsCtx, error) {
|
||||
if ctx.Data["PageIsRepoSettings"] == true {
|
||||
return &secretsCtx{
|
||||
OwnerID: 0,
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
Owner: nil,
|
||||
Repo: ctx.Repo.Repository,
|
||||
IsRepo: true,
|
||||
SecretsTemplate: tplRepoSecrets,
|
||||
RedirectLink: ctx.Repo.RepoLink + "/settings/actions/secrets",
|
||||
|
@ -49,8 +51,8 @@ func getSecretsCtx(ctx *context.Context) (*secretsCtx, error) {
|
|||
return nil, nil
|
||||
}
|
||||
return &secretsCtx{
|
||||
OwnerID: ctx.ContextUser.ID,
|
||||
RepoID: 0,
|
||||
Owner: ctx.ContextUser,
|
||||
Repo: nil,
|
||||
IsOrg: true,
|
||||
SecretsTemplate: tplOrgSecrets,
|
||||
RedirectLink: ctx.Org.OrgLink + "/settings/actions/secrets",
|
||||
|
@ -59,8 +61,8 @@ func getSecretsCtx(ctx *context.Context) (*secretsCtx, error) {
|
|||
|
||||
if ctx.Data["PageIsUserSettings"] == true {
|
||||
return &secretsCtx{
|
||||
OwnerID: ctx.Doer.ID,
|
||||
RepoID: 0,
|
||||
Owner: ctx.Doer,
|
||||
Repo: nil,
|
||||
IsUser: true,
|
||||
SecretsTemplate: tplUserSecrets,
|
||||
RedirectLink: setting.AppSubURL + "/user/settings/actions/secrets",
|
||||
|
@ -85,7 +87,7 @@ func Secrets(ctx *context.Context) {
|
|||
ctx.Data["DisableSSH"] = setting.SSH.Disabled
|
||||
}
|
||||
|
||||
shared.SetSecretsContext(ctx, sCtx.OwnerID, sCtx.RepoID)
|
||||
shared.SetSecretsContext(ctx, sCtx.Owner, sCtx.Repo)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
@ -106,8 +108,9 @@ func SecretsPost(ctx *context.Context) {
|
|||
|
||||
shared.PerformSecretsPost(
|
||||
ctx,
|
||||
sCtx.OwnerID,
|
||||
sCtx.RepoID,
|
||||
ctx.Doer,
|
||||
sCtx.Owner,
|
||||
sCtx.Repo,
|
||||
sCtx.RedirectLink,
|
||||
)
|
||||
}
|
||||
|
@ -120,8 +123,9 @@ func SecretsDelete(ctx *context.Context) {
|
|||
}
|
||||
shared.PerformSecretsDelete(
|
||||
ctx,
|
||||
sCtx.OwnerID,
|
||||
sCtx.RepoID,
|
||||
ctx.Doer,
|
||||
sCtx.Owner,
|
||||
sCtx.Repo,
|
||||
sCtx.RedirectLink,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/web"
|
||||
actions_service "code.gitea.io/gitea/services/actions"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
"code.gitea.io/gitea/services/migrations"
|
||||
|
@ -182,6 +183,11 @@ func SettingsPost(ctx *context.Context) {
|
|||
ctx.ServerError("UpdateRepository", err)
|
||||
return
|
||||
}
|
||||
|
||||
if visibilityChanged {
|
||||
audit.RecordRepositoryVisibility(ctx, ctx.Doer, repo)
|
||||
}
|
||||
|
||||
log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
|
||||
|
@ -371,6 +377,8 @@ func SettingsPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordRepositoryMirrorPushRemove(ctx, ctx.Doer, repo, m)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
|
||||
|
@ -434,6 +442,8 @@ func SettingsPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordRepositoryMirrorPushAdd(ctx, ctx.Doer, repo, m)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
|
||||
|
@ -619,6 +629,7 @@ func SettingsPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Trace("Repository advanced settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
|
||||
|
@ -637,6 +648,8 @@ func SettingsPost(ctx *context.Context) {
|
|||
ctx.ServerError("UpdateRepository", err)
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordRepositorySigningVerification(ctx, ctx.Doer, repo)
|
||||
}
|
||||
log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||
|
||||
|
@ -714,6 +727,9 @@ func SettingsPost(ctx *context.Context) {
|
|||
ctx.ServerError("DeleteMirrorByRepoID", err)
|
||||
return
|
||||
}
|
||||
|
||||
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"))
|
||||
ctx.Redirect(repo.Link())
|
||||
|
@ -745,7 +761,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
|
||||
|
@ -798,7 +814,7 @@ func SettingsPost(ctx *context.Context) {
|
|||
} else if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.RenderWithErr(ctx.Tr("repo.settings.transfer.blocked_user"), tplSettingsOptions, nil)
|
||||
} else {
|
||||
ctx.ServerError("TransferOwnership", err)
|
||||
ctx.ServerError("StartRepositoryTransfer", err)
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -841,6 +857,8 @@ func SettingsPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
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))
|
||||
ctx.Redirect(repo.Link() + "/settings")
|
||||
|
@ -879,10 +897,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"))
|
||||
|
@ -911,6 +930,8 @@ func SettingsPost(ctx *context.Context) {
|
|||
log.Error("CleanRepoScheduleTasks for archived repo %s/%s: %v", ctx.Repo.Owner.Name, repo.Name, err)
|
||||
}
|
||||
|
||||
audit.RecordRepositoryArchive(ctx, ctx.Doer, repo)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.archive.success"))
|
||||
|
||||
log.Trace("Repository was archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||
|
@ -935,6 +956,8 @@ func SettingsPost(ctx *context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
audit.RecordRepositoryUnarchive(ctx, ctx.Doer, repo)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.unarchive.success"))
|
||||
|
||||
log.Trace("Repository was un-archived: %s/%s", ctx.Repo.Owner.Name, repo.Name)
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
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"
|
||||
|
@ -25,6 +26,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/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
@ -58,8 +60,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
|
||||
|
@ -71,7 +73,7 @@ type ownerRepoCtx struct {
|
|||
func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) {
|
||||
if ctx.Data["PageIsRepoSettings"] == true {
|
||||
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,
|
||||
|
@ -80,7 +82,7 @@ func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) {
|
|||
|
||||
if ctx.Data["PageIsOrgSettings"] == true {
|
||||
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,
|
||||
|
@ -89,7 +91,7 @@ func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) {
|
|||
|
||||
if ctx.Data["PageIsUserSettings"] == true {
|
||||
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,
|
||||
|
@ -229,8 +231,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,
|
||||
|
@ -239,7 +250,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)
|
||||
|
@ -255,6 +266,8 @@ func createWebhook(ctx *context.Context, params webhookParams) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordWebhookAdd(ctx, ctx.Doer, orCtx.Owner, orCtx.Repo, w)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
|
||||
ctx.Redirect(orCtx.Link)
|
||||
}
|
||||
|
@ -307,6 +320,8 @@ func editWebhook(ctx *context.Context, params webhookParams) {
|
|||
return
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
@ -591,10 +606,10 @@ func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) {
|
|||
ctx.Data["BaseLinkNew"] = orCtx.LinkNew
|
||||
|
||||
var w *webhook.Webhook
|
||||
if orCtx.RepoID > 0 {
|
||||
w, err = webhook.GetWebhookByRepoID(ctx, orCtx.RepoID, ctx.ParamsInt64(":id"))
|
||||
} else if orCtx.OwnerID > 0 {
|
||||
w, err = webhook.GetWebhookByOwnerID(ctx, orCtx.OwnerID, ctx.ParamsInt64(":id"))
|
||||
if orCtx.Repo != nil {
|
||||
w, err = webhook.GetWebhookByRepoID(ctx, orCtx.Repo.ID, ctx.ParamsInt64(":id"))
|
||||
} else if orCtx.Owner != nil {
|
||||
w, err = webhook.GetWebhookByOwnerID(ctx, orCtx.Owner.ID, ctx.ParamsInt64(":id"))
|
||||
} else if orCtx.IsAdmin {
|
||||
w, err = webhook.GetSystemOrDefaultWebhook(ctx, ctx.ParamsInt64(":id"))
|
||||
}
|
||||
|
@ -728,9 +743,17 @@ func ReplayWebhook(ctx *context.Context) {
|
|||
|
||||
// DeleteWebhook delete a webhook
|
||||
func DeleteWebhook(ctx *context.Context) {
|
||||
hook, err := webhook.GetWebhookByRepoID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id"))
|
||||
if err != nil {
|
||||
ctx.ServerError("GetWebhookByRepoID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := webhook.DeleteWebhookByRepoID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil {
|
||||
ctx.Flash.Error("DeleteWebhookByRepoID: " + err.Error())
|
||||
} else {
|
||||
audit.RecordWebhookRemove(ctx, ctx.Doer, nil, ctx.Repo.Repository, hook)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,9 @@ package secrets
|
|||
|
||||
import (
|
||||
"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/log"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
|
@ -14,7 +16,16 @@ import (
|
|||
secret_service "code.gitea.io/gitea/services/secrets"
|
||||
)
|
||||
|
||||
func SetSecretsContext(ctx *context.Context, ownerID, repoID int64) {
|
||||
func SetSecretsContext(ctx *context.Context, owner *user_model.User, repo *repo_model.Repository) {
|
||||
ownerID := int64(0)
|
||||
if owner != nil {
|
||||
ownerID = owner.ID
|
||||
}
|
||||
repoID := int64(0)
|
||||
if repo != nil {
|
||||
repoID = repo.ID
|
||||
}
|
||||
|
||||
secrets, err := db.Find[secret_model.Secret](ctx, secret_model.FindSecretsOptions{OwnerID: ownerID, RepoID: repoID})
|
||||
if err != nil {
|
||||
ctx.ServerError("FindSecrets", err)
|
||||
|
@ -24,10 +35,10 @@ 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_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, util.ReserveLineBreakForTextarea(form.Data))
|
||||
s, _, err := secret_service.CreateOrUpdateSecret(ctx, doer, owner, repo, form.Name, util.ReserveLineBreakForTextarea(form.Data))
|
||||
if err != nil {
|
||||
log.Error("CreateOrUpdateSecret failed: %v", err)
|
||||
ctx.JSONError(ctx.Tr("secrets.creation.failed"))
|
||||
|
@ -38,10 +49,10 @@ func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL
|
|||
ctx.JSONRedirect(redirectURL)
|
||||
}
|
||||
|
||||
func PerformSecretsDelete(ctx *context.Context, ownerID, repoID int64, redirectURL string) {
|
||||
func PerformSecretsDelete(ctx *context.Context, doer, owner *user_model.User, repo *repo_model.Repository, redirectURL string) {
|
||||
id := ctx.FormInt64("id")
|
||||
|
||||
err := secret_service.DeleteSecretByID(ctx, ownerID, repoID, id)
|
||||
err := secret_service.DeleteSecretByID(ctx, doer, owner, repo, id)
|
||||
if err != nil {
|
||||
log.Error("DeleteSecretByID(%d) failed: %v", id, err)
|
||||
ctx.JSONError(ctx.Tr("secrets.deletion.failed"))
|
||||
|
|
|
@ -65,7 +65,7 @@ func AccountPost(ctx *context.Context) {
|
|||
Password: optional.Some(form.Password),
|
||||
MustChangePassword: optional.Some(false),
|
||||
}
|
||||
if err := user.UpdateAuth(ctx, ctx.Doer, opts); err != nil {
|
||||
if err := user.UpdateAuth(ctx, ctx.Doer, ctx.Doer, opts); err != nil {
|
||||
switch {
|
||||
case errors.Is(err, password.ErrMinLength):
|
||||
ctx.Flash.Error(ctx.Tr("auth.password_too_short", setting.MinPasswordLength))
|
||||
|
@ -165,7 +165,7 @@ func EmailPost(ctx *context.Context) {
|
|||
opts := &user.UpdateOptions{
|
||||
EmailNotificationsPreference: optional.Some(preference),
|
||||
}
|
||||
if err := user.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||
if err := user.UpdateUser(ctx, ctx.Doer, ctx.Doer, opts); err != nil {
|
||||
log.Error("Set Email Notifications failed: %v", err)
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
|
@ -183,7 +183,7 @@ func EmailPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := user.AddEmailAddresses(ctx, ctx.Doer, []string{form.Email}); err != nil {
|
||||
if err := user.AddEmailAddresses(ctx, ctx.Doer, ctx.Doer, []string{form.Email}); err != nil {
|
||||
if user_model.IsErrEmailAlreadyUsed(err) {
|
||||
loadAccountData(ctx)
|
||||
|
||||
|
@ -210,7 +210,6 @@ func EmailPost(ctx *context.Context) {
|
|||
ctx.Flash.Success(ctx.Tr("settings.add_email_success"))
|
||||
}
|
||||
|
||||
log.Trace("Email address added: %s", form.Email)
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
|
||||
}
|
||||
|
||||
|
@ -222,11 +221,10 @@ func DeleteEmail(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := user.DeleteEmailAddresses(ctx, ctx.Doer, []string{email.Email}); err != nil {
|
||||
if err := user.DeleteEmailAddresses(ctx, ctx.Doer, ctx.Doer, []string{email.Email}); err != nil {
|
||||
ctx.ServerError("DeleteEmailAddresses", err)
|
||||
return
|
||||
}
|
||||
log.Trace("Email address deleted: %s", ctx.Doer.Name)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.email_deletion_success"))
|
||||
ctx.JSONRedirect(setting.AppSubURL + "/user/settings/account")
|
||||
|
@ -273,7 +271,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"))
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
)
|
||||
|
@ -70,6 +71,8 @@ func ApplicationsPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordUserAccessTokenAdd(ctx, ctx.Doer, ctx.Doer, t)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.generate_token_success"))
|
||||
ctx.Flash.Info(t.Token)
|
||||
|
||||
|
@ -78,9 +81,17 @@ func ApplicationsPost(ctx *context.Context) {
|
|||
|
||||
// DeleteApplication response for delete user access token
|
||||
func DeleteApplication(ctx *context.Context) {
|
||||
if err := auth_model.DeleteAccessTokenByID(ctx, ctx.FormInt64("id"), ctx.Doer.ID); err != nil {
|
||||
t, err := auth_model.GetAccessTokenByID(ctx, ctx.FormInt64("id"), ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetAccessTokenByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := auth_model.DeleteAccessTokenByID(ctx, t.ID, ctx.Doer.ID); err != nil {
|
||||
ctx.Flash.Error("DeleteAccessTokenByID: " + err.Error())
|
||||
} else {
|
||||
audit.RecordUserAccessTokenRemove(ctx, ctx.Doer, ctx.Doer, t)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.delete_token_success"))
|
||||
}
|
||||
|
||||
|
|
55
routers/web/user/setting/audit.go
Normal file
55
routers/web/user/setting/audit.go
Normal file
|
@ -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/setting"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
|
@ -15,6 +15,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/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
)
|
||||
|
@ -63,7 +64,8 @@ func KeysPost(ctx *context.Context) {
|
|||
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
|
||||
return
|
||||
}
|
||||
if _, err = asymkey_service.AddPrincipalKey(ctx, ctx.Doer.ID, content, 0); err != nil {
|
||||
key, err := asymkey_service.AddPrincipalKey(ctx, ctx.Doer.ID, content, 0)
|
||||
if err != nil {
|
||||
ctx.Data["HasPrincipalError"] = true
|
||||
switch {
|
||||
case asymkey_model.IsErrKeyAlreadyExist(err), asymkey_model.IsErrKeyNameAlreadyUsed(err):
|
||||
|
@ -76,6 +78,9 @@ func KeysPost(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
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")
|
||||
case "gpg":
|
||||
|
@ -124,6 +129,11 @@ func KeysPost(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
audit.RecordUserKeyGPGAdd(ctx, ctx.Doer, ctx.Doer, key)
|
||||
}
|
||||
|
||||
keyIDs := ""
|
||||
for _, key := range keys {
|
||||
keyIDs += key.KeyID
|
||||
|
@ -180,7 +190,8 @@ func KeysPost(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if _, err = asymkey_model.AddPublicKey(ctx, ctx.Doer.ID, form.Title, content, 0); err != nil {
|
||||
key, err := asymkey_model.AddPublicKey(ctx, ctx.Doer.ID, form.Title, content, 0)
|
||||
if err != nil {
|
||||
ctx.Data["HasSSHError"] = true
|
||||
switch {
|
||||
case asymkey_model.IsErrKeyAlreadyExist(err):
|
||||
|
@ -201,6 +212,9 @@ func KeysPost(ctx *context.Context) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
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")
|
||||
case "verify_ssh":
|
||||
|
@ -245,10 +259,23 @@ func DeleteKey(ctx *context.Context) {
|
|||
ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
|
||||
return
|
||||
}
|
||||
if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.FormInt64("id")); err != nil {
|
||||
ctx.Flash.Error("DeleteGPGKey: " + err.Error())
|
||||
} else {
|
||||
|
||||
key, err := asymkey_model.GetGPGKeyForUserByID(ctx, ctx.Doer.ID, ctx.FormInt64("id"))
|
||||
if err != nil && !asymkey_model.IsErrGPGKeyNotExist(err) {
|
||||
ctx.ServerError("GetGPGKeyForUserByID", err)
|
||||
return
|
||||
}
|
||||
if key != nil {
|
||||
if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, key.ID); err != nil {
|
||||
ctx.ServerError("DeleteGPGKey", err)
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordUserKeyGPGRemove(ctx, ctx.Doer, ctx.Doer, key)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.gpg_key_deletion_success"))
|
||||
} else {
|
||||
ctx.Flash.Error(ctx.Tr("error.occurred"))
|
||||
}
|
||||
case "ssh":
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
|
||||
|
@ -259,7 +286,7 @@ func DeleteKey(ctx *context.Context) {
|
|||
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 {
|
||||
|
|
|
@ -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/setting"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -4,23 +4,34 @@
|
|||
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/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"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) renderEditPage(ctx *context.Context) {
|
||||
|
@ -51,7 +62,7 @@ func (oa *OAuth2CommonHandlers) AddApp(ctx *context.Context) {
|
|||
app, err := auth.CreateOAuth2Application(ctx, auth.CreateOAuth2ApplicationOptions{
|
||||
Name: form.Name,
|
||||
RedirectURIs: util.SplitTrimSpace(form.RedirectURIs, "\n"),
|
||||
UserID: oa.OwnerID,
|
||||
UserID: oa.ownerID(),
|
||||
ConfidentialClient: form.ConfidentialClient,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -59,6 +70,8 @@ func (oa *OAuth2CommonHandlers) AddApp(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
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)
|
||||
ctx.Data["App"] = app
|
||||
|
@ -82,7 +95,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
|
||||
}
|
||||
|
@ -100,17 +113,22 @@ func (oa *OAuth2CommonHandlers) EditSave(ctx *context.Context) {
|
|||
}
|
||||
|
||||
// TODO validate redirect URI
|
||||
var err error
|
||||
if ctx.Data["App"], err = auth.UpdateOAuth2Application(ctx, auth.UpdateOAuth2ApplicationOptions{
|
||||
app, err := auth.UpdateOAuth2Application(ctx, auth.UpdateOAuth2ApplicationOptions{
|
||||
ID: ctx.ParamsInt64("id"),
|
||||
Name: form.Name,
|
||||
RedirectURIs: util.SplitTrimSpace(form.RedirectURIs, "\n"),
|
||||
UserID: oa.OwnerID,
|
||||
UserID: oa.ownerID(),
|
||||
ConfidentialClient: form.ConfidentialClient,
|
||||
}); err != nil {
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("UpdateOAuth2Application", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["App"] = app
|
||||
|
||||
audit.RecordOAuth2ApplicationUpdate(ctx, oa.Doer, oa.Owner, app)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.update_oauth2_application_success"))
|
||||
ctx.Redirect(oa.BasePathList)
|
||||
}
|
||||
|
@ -126,7 +144,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
|
||||
}
|
||||
|
@ -136,28 +154,65 @@ func (oa *OAuth2CommonHandlers) RegenerateSecret(ctx *context.Context) {
|
|||
ctx.ServerError("GenerateClientSecret", err)
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordOAuth2ApplicationSecret(ctx, oa.Doer, oa.Owner, app)
|
||||
|
||||
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, 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(ctx, app.ID, oa.ownerID()); err != nil {
|
||||
ctx.ServerError("DeleteOAuth2Application", err)
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordOAuth2ApplicationRemove(ctx, oa.Doer, oa.Owner, app)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.remove_oauth2_application_success"))
|
||||
ctx.JSONRedirect(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.RecordUserOAuth2ApplicationRevoke(ctx, oa.Doer, oa.Owner, app, grant)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.revoke_oauth2_grant_success"))
|
||||
ctx.JSONRedirect(oa.BasePathList)
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ func ProfilePost(ctx *context.Context) {
|
|||
form := web.GetForm(ctx).(*forms.UpdateProfileForm)
|
||||
|
||||
if form.Name != "" {
|
||||
if err := user_service.RenameUser(ctx, ctx.Doer, form.Name); err != nil {
|
||||
if err := user_service.RenameUser(ctx, ctx.Doer, ctx.Doer, form.Name); err != nil {
|
||||
switch {
|
||||
case user_model.IsErrUserIsNotLocal(err):
|
||||
ctx.Flash.Error(ctx.Tr("form.username_change_not_local_user"))
|
||||
|
@ -96,12 +96,11 @@ func ProfilePost(ctx *context.Context) {
|
|||
Visibility: optional.Some(form.Visibility),
|
||||
KeepActivityPrivate: optional.Some(form.KeepActivityPrivate),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, ctx.Doer, opts); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("User settings updated: %s", ctx.Doer.Name)
|
||||
ctx.Flash.Success(ctx.Tr("settings.update_profile_success"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings")
|
||||
}
|
||||
|
@ -363,7 +362,7 @@ func UpdateUIThemePost(ctx *context.Context) {
|
|||
opts := &user_service.UpdateOptions{
|
||||
Theme: optional.Some(form.Theme),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, ctx.Doer, opts); err != nil {
|
||||
ctx.Flash.Error(ctx.Tr("settings.theme_update_error"))
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("settings.theme_update_success"))
|
||||
|
@ -389,7 +388,7 @@ func UpdateUserLang(ctx *context.Context) {
|
|||
opts := &user_service.UpdateOptions{
|
||||
Language: optional.Some(form.Language),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, ctx.Doer, opts); err != nil {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -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/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
||||
|
@ -49,6 +50,8 @@ func RegenerateScratchTwoFactor(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
@ -78,6 +81,8 @@ func DisableTwoFactor(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
audit.RecordUserTwoFactorDisable(ctx, ctx.Doer, ctx.Doer, t)
|
||||
|
||||
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.RecordUserTwoFactorEnable(ctx, ctx.Doer, ctx.Doer)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.twofa_enrolled", token))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
}
|
||||
|
|
|
@ -11,6 +11,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/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
)
|
||||
|
@ -97,6 +98,9 @@ func settingsOpenIDVerify(ctx *context.Context) {
|
|||
ctx.ServerError("AddUserOpenID", err)
|
||||
return
|
||||
}
|
||||
|
||||
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"))
|
||||
|
||||
|
@ -105,10 +109,19 @@ func settingsOpenIDVerify(ctx *context.Context) {
|
|||
|
||||
// DeleteOpenID response for delete user's openid
|
||||
func DeleteOpenID(ctx *context.Context) {
|
||||
if err := user_model.DeleteUserOpenID(ctx, &user_model.UserOpenID{ID: ctx.FormInt64("id"), UID: ctx.Doer.ID}); err != nil {
|
||||
oid, err := user_model.GetUserOpenID(ctx, ctx.FormInt64("id"), ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserOpenID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := user_model.DeleteUserOpenID(ctx, oid); err != nil {
|
||||
ctx.ServerError("DeleteUserOpenID", err)
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordUserOpenIDRemove(ctx, ctx.Doer, ctx.Doer, oid)
|
||||
|
||||
log.Trace("OpenID address deleted: %s", ctx.Doer.Name)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.openid_deletion_success"))
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
@ -40,17 +41,24 @@ 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, ctx.Doer, id); err != nil {
|
||||
ctx.Flash.Error("RemoveAccountLink: " + err.Error())
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success"))
|
||||
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: user.UserID, LoginSourceID: user.LoginSourceID}
|
||||
}
|
||||
ctx.ServerError("RemoveAccountLink", err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := user_model.RemoveAccountLink(ctx, ctx.Doer, user.LoginSourceID); err != nil {
|
||||
ctx.ServerError("RemoveAccountLink", err)
|
||||
return
|
||||
}
|
||||
|
||||
audit.RecordUserExternalLoginRemove(ctx, ctx.Doer, ctx.Doer, user)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.remove_account_link_success"))
|
||||
|
||||
ctx.JSONRedirect(setting.AppSubURL + "/user/settings/security")
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,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/context"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
||||
|
@ -99,22 +100,34 @@ func WebauthnRegisterPost(ctx *context.Context) {
|
|||
}
|
||||
|
||||
// Create the credential
|
||||
_, err = auth.CreateCredential(ctx, ctx.Doer.ID, name, cred)
|
||||
dbCred, err = auth.CreateCredential(ctx, ctx.Doer.ID, name, cred)
|
||||
if err != nil {
|
||||
ctx.ServerError("CreateCredential", err)
|
||||
return
|
||||
}
|
||||
_ = ctx.Session.Delete("webauthnName")
|
||||
|
||||
audit.RecordUserWebAuthAdd(ctx, ctx.Doer, ctx.Doer, dbCred)
|
||||
|
||||
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(ctx, form.ID, ctx.Doer.ID); err != nil {
|
||||
|
||||
cred, err := auth.GetWebAuthnCredentialByID(ctx, form.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetWebAuthnCredentialByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if ok, err := auth.DeleteCredential(ctx, form.ID, ctx.Doer.ID); err != nil {
|
||||
ctx.ServerError("DeleteCredential", err)
|
||||
return
|
||||
} else if ok {
|
||||
audit.RecordUserWebAuthRemove(ctx, ctx.Doer, ctx.Doer, cred)
|
||||
}
|
||||
|
||||
ctx.JSONRedirect(setting.AppSubURL + "/user/settings/security")
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"code.gitea.io/gitea/models/webhook"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
|
@ -37,9 +38,17 @@ func Webhooks(ctx *context.Context) {
|
|||
|
||||
// DeleteWebhook response for delete webhook
|
||||
func DeleteWebhook(ctx *context.Context) {
|
||||
if err := webhook.DeleteWebhookByOwnerID(ctx, ctx.Doer.ID, ctx.FormInt64("id")); err != nil {
|
||||
hook, err := webhook.GetWebhookByOwnerID(ctx, ctx.Doer.ID, ctx.FormInt64("id"))
|
||||
if err != nil {
|
||||
ctx.ServerError("GetWebhookByOwnerID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := webhook.DeleteWebhookByOwnerID(ctx, ctx.Doer.ID, hook.ID); err != nil {
|
||||
ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error())
|
||||
} else {
|
||||
audit.RecordWebhookRemove(ctx, ctx.Doer, ctx.Doer, nil, hook)
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
|
||||
}
|
||||
|
||||
|
|
|
@ -380,6 +380,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
|
||||
|
@ -649,6 +656,8 @@ func registerRoutes(m *web.Route) {
|
|||
addWebhookEditRoutes()
|
||||
}, webhooksEnabled)
|
||||
|
||||
m.Get("/audit_logs", auditLogsEnabled, user_setting.ViewAuditLogs)
|
||||
|
||||
m.Group("/blocked_users", func() {
|
||||
m.Get("", user_setting.BlockedUsers)
|
||||
m.Post("", web.Bind(forms.BlockUserForm{}), user_setting.BlockedUsersPost)
|
||||
|
@ -696,6 +705,7 @@ func registerRoutes(m *web.Route) {
|
|||
})
|
||||
|
||||
m.Group("/monitor", func() {
|
||||
m.Get("/audit_logs", auditLogsEnabled, admin.ViewAuditLogs)
|
||||
m.Get("/stats", admin.MonitorStats)
|
||||
m.Get("/cron", admin.CronTasks)
|
||||
m.Get("/stacktrace", admin.Stacktrace)
|
||||
|
@ -933,6 +943,8 @@ func registerRoutes(m *web.Route) {
|
|||
addSettingsVariablesRoutes()
|
||||
}, actions.MustEnableActions)
|
||||
|
||||
m.Get("/audit_logs", auditLogsEnabled, org_setting.ViewAuditLogs)
|
||||
|
||||
m.Methods("GET,POST", "/delete", org.SettingsDelete)
|
||||
|
||||
m.Group("/packages", func() {
|
||||
|
@ -1111,6 +1123,7 @@ func registerRoutes(m *web.Route) {
|
|||
addSettingsSecretsRoutes()
|
||||
addSettingsVariablesRoutes()
|
||||
}, actions.MustEnableActions)
|
||||
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)
|
||||
|
|
|
@ -5,10 +5,16 @@ package asymkey
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"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/modules/util"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
)
|
||||
|
||||
// DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed.
|
||||
|
@ -19,6 +25,16 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error
|
|||
}
|
||||
defer committer.Close()
|
||||
|
||||
key, err := asymkey_model.GetDeployKeyByID(dbCtx, id)
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
return fmt.Errorf("GetDeployKeyByID: %w", err)
|
||||
}
|
||||
|
||||
repo, err := repo_model.GetRepositoryByID(dbCtx, key.RepoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepositoryByID: %w", err)
|
||||
}
|
||||
|
||||
if err := models.DeleteDeployKey(dbCtx, doer, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -26,5 +42,7 @@ func DeleteDeployKey(ctx context.Context, doer *user_model.User, id int64) error
|
|||
return err
|
||||
}
|
||||
|
||||
audit.RecordRepositoryDeployKeyRemove(ctx, doer, repo, key)
|
||||
|
||||
return RewriteAllPublicKeys(ctx)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,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.
|
||||
|
@ -18,6 +19,11 @@ func DeletePublicKey(ctx context.Context, doer *user_model.User, id int64) (err
|
|||
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{
|
||||
|
@ -43,8 +49,12 @@ func DeletePublicKey(ctx context.Context, doer *user_model.User, id int64) (err
|
|||
committer.Close()
|
||||
|
||||
if key.Type == asymkey_model.KeyTypePrincipal {
|
||||
audit.RecordUserKeyPrincipalRemove(ctx, doer, owner, key)
|
||||
|
||||
return RewriteAllPrincipalKeys(ctx)
|
||||
}
|
||||
|
||||
audit.RecordUserKeySSHRemove(ctx, doer, owner, key)
|
||||
|
||||
return RewriteAllPublicKeys(ctx)
|
||||
}
|
||||
|
|
153
services/audit/audit.go
Normal file
153
services/audit/audit.go
Normal file
|
@ -0,0 +1,153 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package audit
|
||||
|
||||
import (
|
||||
"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"
|
||||
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/setting"
|
||||
)
|
||||
|
||||
type TypeDescriptor struct {
|
||||
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 ""
|
||||
}
|
||||
|
||||
func Init() error {
|
||||
if !setting.Audit.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
return initAuditFile()
|
||||
}
|
||||
|
||||
var systemObject struct{}
|
||||
|
||||
func scopeToDescription(scope any) TypeDescriptor {
|
||||
if scope == &systemObject {
|
||||
return TypeDescriptor{audit_model.TypeSystem, 0, 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 {
|
||||
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}
|
||||
case *user_model.User:
|
||||
if t.IsOrganization() {
|
||||
return TypeDescriptor{audit_model.TypeOrganization, t.ID, val}
|
||||
}
|
||||
return TypeDescriptor{audit_model.TypeUser, t.ID, val}
|
||||
case *organization_model.Organization:
|
||||
return TypeDescriptor{audit_model.TypeOrganization, t.ID, val}
|
||||
case *user_model.EmailAddress:
|
||||
return TypeDescriptor{audit_model.TypeEmailAddress, t.ID, val}
|
||||
case *organization_model.Team:
|
||||
return TypeDescriptor{audit_model.TypeTeam, t.ID, val}
|
||||
case *auth_model.WebAuthnCredential:
|
||||
return TypeDescriptor{audit_model.TypeWebAuthnCredential, t.ID, val}
|
||||
case *user_model.UserOpenID:
|
||||
return TypeDescriptor{audit_model.TypeOpenID, t.ID, val}
|
||||
case *auth_model.AccessToken:
|
||||
return TypeDescriptor{audit_model.TypeAccessToken, t.ID, val}
|
||||
case *auth_model.OAuth2Application:
|
||||
return TypeDescriptor{audit_model.TypeOAuth2Application, t.ID, val}
|
||||
case *auth_model.Source:
|
||||
return TypeDescriptor{audit_model.TypeAuthenticationSource, t.ID, val}
|
||||
case *asymkey_model.PublicKey:
|
||||
return TypeDescriptor{audit_model.TypePublicKey, t.ID, val}
|
||||
case *asymkey_model.GPGKey:
|
||||
return TypeDescriptor{audit_model.TypeGPGKey, t.ID, val}
|
||||
case *secret_model.Secret:
|
||||
return TypeDescriptor{audit_model.TypeSecret, t.ID, val}
|
||||
case *webhook_model.Webhook:
|
||||
return TypeDescriptor{audit_model.TypeWebhook, t.ID, val}
|
||||
case *git_model.ProtectedTag:
|
||||
return TypeDescriptor{audit_model.TypeProtectedTag, t.ID, val}
|
||||
case *git_model.ProtectedBranch:
|
||||
return TypeDescriptor{audit_model.TypeProtectedBranch, t.ID, val}
|
||||
case *repository_model.PushMirror:
|
||||
return TypeDescriptor{audit_model.TypePushMirror, t.ID, val}
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported type: %T", t))
|
||||
}
|
||||
}
|
307
services/audit/audit_test.go
Normal file
307
services/audit/audit_test.go
Normal file
|
@ -0,0 +1,307 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package audit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
"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"
|
||||
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/httplib"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"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)
|
||||
}
|
||||
|
||||
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}
|
||||
doer := &user_model.User{ID: 2, Name: "Doer"}
|
||||
|
||||
equal(
|
||||
&Event{
|
||||
Action: audit_model.UserCreate,
|
||||
Actor: TypeDescriptor{Type: "user", ID: 2, Object: doer},
|
||||
Scope: TypeDescriptor{Type: "user", ID: 1, Object: u},
|
||||
Target: TypeDescriptor{Type: "user", ID: 1, Object: u},
|
||||
Message: "Created user TestUser.",
|
||||
},
|
||||
buildEvent(
|
||||
ctx,
|
||||
audit_model.UserCreate,
|
||||
doer,
|
||||
u,
|
||||
u,
|
||||
"Created user %s.",
|
||||
u.Name,
|
||||
),
|
||||
)
|
||||
equal(
|
||||
&Event{
|
||||
Action: audit_model.RepositoryMirrorPushAdd,
|
||||
Actor: TypeDescriptor{Type: "user", ID: 2, Object: doer},
|
||||
Scope: TypeDescriptor{Type: "repository", ID: 3, Object: r},
|
||||
Target: TypeDescriptor{Type: "push_mirror", ID: 4, Object: m},
|
||||
Message: "Added push mirror for repository TestUser/TestRepo.",
|
||||
},
|
||||
buildEvent(
|
||||
ctx,
|
||||
audit_model.RepositoryMirrorPushAdd,
|
||||
doer,
|
||||
r,
|
||||
m,
|
||||
"Added push mirror for repository %s.",
|
||||
r.FullName(),
|
||||
),
|
||||
)
|
||||
|
||||
e := buildEvent(ctx, audit_model.UserCreate, doer, u, u, "")
|
||||
assert.Empty(t, e.IPAddress)
|
||||
|
||||
ctx = context.WithValue(ctx, httplib.RequestContextKey, &http.Request{RemoteAddr: "127.0.0.1:1234"})
|
||||
|
||||
e = buildEvent(ctx, audit_model.UserCreate, doer, u, u, "")
|
||||
assert.Equal(t, "127.0.0.1", e.IPAddress)
|
||||
}
|
||||
|
||||
func TestScopeToDescription(t *testing.T) {
|
||||
cases := []struct {
|
||||
ShouldPanic bool
|
||||
Scope any
|
||||
Expected TypeDescriptor
|
||||
}{
|
||||
{
|
||||
Scope: nil,
|
||||
ShouldPanic: true,
|
||||
},
|
||||
{
|
||||
Scope: &systemObject,
|
||||
Expected: TypeDescriptor{Type: audit_model.TypeSystem, ID: 0},
|
||||
},
|
||||
{
|
||||
Scope: &user_model.User{ID: 1, Name: "TestUser"},
|
||||
Expected: TypeDescriptor{Type: audit_model.TypeUser, ID: 1},
|
||||
},
|
||||
{
|
||||
Scope: &organization_model.Organization{ID: 2, Name: "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, ID: 3},
|
||||
},
|
||||
{
|
||||
ShouldPanic: true,
|
||||
Scope: &organization_model.Team{ID: 345, Name: "Team"},
|
||||
},
|
||||
{
|
||||
ShouldPanic: true,
|
||||
Scope: 1234,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if c.Scope != &systemObject {
|
||||
c.Expected.Object = 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) {
|
||||
setting.AppURL = "http://localhost:3000/"
|
||||
|
||||
type Expected struct {
|
||||
TypeDescriptor TypeDescriptor
|
||||
DisplayName string
|
||||
HTMLURL string
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
ShouldPanic bool
|
||||
Type any
|
||||
Expected Expected
|
||||
}{
|
||||
{
|
||||
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"},
|
||||
Expected: Expected{
|
||||
TypeDescriptor: TypeDescriptor{Type: audit_model.TypeUser, ID: 1},
|
||||
DisplayName: "TestUser",
|
||||
HTMLURL: "http://localhost:3000/TestUser",
|
||||
},
|
||||
},
|
||||
{
|
||||
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: &user_model.EmailAddress{ID: 3, Email: "user@gitea.com"},
|
||||
Expected: Expected{
|
||||
TypeDescriptor: TypeDescriptor{Type: audit_model.TypeEmailAddress, ID: 3},
|
||||
DisplayName: "user@gitea.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
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: &organization_model.Team{ID: 4, Name: "TestTeam"},
|
||||
Expected: Expected{
|
||||
TypeDescriptor: TypeDescriptor{Type: audit_model.TypeTeam, ID: 4},
|
||||
DisplayName: "TestTeam",
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &auth_model.WebAuthnCredential{ID: 6, Name: "TestCredential"},
|
||||
Expected: Expected{
|
||||
TypeDescriptor: TypeDescriptor{Type: audit_model.TypeWebAuthnCredential, ID: 6},
|
||||
DisplayName: "TestCredential",
|
||||
},
|
||||
},
|
||||
{
|
||||
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.AccessToken{ID: 8, Name: "TestToken"},
|
||||
Expected: Expected{
|
||||
TypeDescriptor: TypeDescriptor{Type: audit_model.TypeAccessToken, ID: 8},
|
||||
DisplayName: "TestToken",
|
||||
},
|
||||
},
|
||||
{
|
||||
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: Expected{
|
||||
TypeDescriptor: TypeDescriptor{Type: audit_model.TypeAuthenticationSource, ID: 11},
|
||||
DisplayName: "TestSource",
|
||||
},
|
||||
},
|
||||
{
|
||||
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: Expected{
|
||||
TypeDescriptor: TypeDescriptor{Type: audit_model.TypeGPGKey, ID: 14},
|
||||
DisplayName: "TestGPGKey",
|
||||
},
|
||||
},
|
||||
{
|
||||
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: Expected{
|
||||
TypeDescriptor: TypeDescriptor{Type: audit_model.TypeWebhook, ID: 16},
|
||||
DisplayName: "test://webhook",
|
||||
},
|
||||
},
|
||||
{
|
||||
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: Expected{
|
||||
TypeDescriptor: TypeDescriptor{Type: audit_model.TypeProtectedBranch, ID: 18},
|
||||
DisplayName: "TestProtectedBranch",
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: &repository_model.PushMirror{ID: 19},
|
||||
Expected: Expected{
|
||||
TypeDescriptor: TypeDescriptor{Type: audit_model.TypePushMirror, ID: 19},
|
||||
DisplayName: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
ShouldPanic: true,
|
||||
Type: 1234,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if c.Type != &systemObject {
|
||||
c.Expected.TypeDescriptor.Object = c.Type
|
||||
}
|
||||
|
||||
if c.ShouldPanic {
|
||||
assert.Panics(t, func() {
|
||||
_ = typeToDescription(c.Type)
|
||||
})
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
26
services/audit/database.go
Normal file
26
services/audit/database.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package audit
|
||||
|
||||
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,
|
||||
TimestampUnix: timeutil.TimeStamp(e.Time.Unix()),
|
||||
})
|
||||
return err
|
||||
}
|
134
services/audit/display.go
Normal file
134
services/audit/display.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
// Copyright 2024 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{}
|
||||
|
||||
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))
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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:
|
||||
td, has = getTypeDescriptorByID[user_model.User](ctx, id)
|
||||
case audit_model.TypeOrganization:
|
||||
td, has = getTypeDescriptorByID[organization_model.Organization](ctx, id)
|
||||
case audit_model.TypeEmailAddress:
|
||||
td, has = getTypeDescriptorByID[user_model.EmailAddress](ctx, id)
|
||||
case audit_model.TypeTeam:
|
||||
td, has = getTypeDescriptorByID[organization_model.Team](ctx, id)
|
||||
case audit_model.TypeWebAuthnCredential:
|
||||
td, has = getTypeDescriptorByID[auth_model.WebAuthnCredential](ctx, id)
|
||||
case audit_model.TypeOpenID:
|
||||
td, has = getTypeDescriptorByID[user_model.UserOpenID](ctx, id)
|
||||
case audit_model.TypeAccessToken:
|
||||
td, has = getTypeDescriptorByID[auth_model.AccessToken](ctx, id)
|
||||
case audit_model.TypeOAuth2Application:
|
||||
td, has = getTypeDescriptorByID[auth_model.OAuth2Application](ctx, id)
|
||||
case audit_model.TypeAuthenticationSource:
|
||||
td, has = getTypeDescriptorByID[auth_model.Source](ctx, id)
|
||||
case audit_model.TypePublicKey:
|
||||
td, has = getTypeDescriptorByID[asymkey_model.PublicKey](ctx, id)
|
||||
case audit_model.TypeGPGKey:
|
||||
td, has = getTypeDescriptorByID[asymkey_model.GPGKey](ctx, id)
|
||||
case audit_model.TypeSecret:
|
||||
td, has = getTypeDescriptorByID[secret_model.Secret](ctx, id)
|
||||
case audit_model.TypeWebhook:
|
||||
td, has = getTypeDescriptorByID[webhook_model.Webhook](ctx, id)
|
||||
case audit_model.TypeProtectedTag:
|
||||
td, has = getTypeDescriptorByID[git_model.ProtectedTag](ctx, id)
|
||||
case audit_model.TypeProtectedBranch:
|
||||
td, has = getTypeDescriptorByID[git_model.ProtectedBranch](ctx, id)
|
||||
case audit_model.TypePushMirror:
|
||||
td, has = getTypeDescriptorByID[repository_model.PushMirror](ctx, id)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported type: %v", t))
|
||||
}
|
||||
|
||||
if !has {
|
||||
td = TypeDescriptor{t, id, nil}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
59
services/audit/file.go
Normal file
59
services/audit/file.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
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 (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)
|
||||
}
|
46
services/audit/file_test.go
Normal file
46
services/audit/file_test.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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/httplib"
|
||||
|
||||
"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"}
|
||||
|
||||
ctx := context.WithValue(context.Background(), httplib.RequestContextKey, &http.Request{RemoteAddr: "127.0.0.1:1234"})
|
||||
|
||||
e := buildEvent(
|
||||
ctx,
|
||||
audit_model.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","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(),
|
||||
)
|
||||
}
|
513
services/audit/record.go
Normal file
513
services/audit/record.go
Normal file
|
@ -0,0 +1,513 @@
|
|||
// 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/httplib"
|
||||
"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: httplib.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 %+v to database: %v", e, 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 RecordUserPasswordResetRequest(ctx context.Context, doer, user *user_model.User) {
|
||||
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) {
|
||||
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, &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 {
|
||||
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, &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 {
|
||||
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, &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 {
|
||||
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, &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 {
|
||||
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.FullName())
|
||||
} 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.FullName())
|
||||
} 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.FullName())
|
||||
} 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, &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.FullName())
|
||||
} 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, &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.FullName())
|
||||
} 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, &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.FullName())
|
||||
} 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.ToString())
|
||||
}
|
||||
|
||||
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, 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) {
|
||||
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 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 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 to %s for repository %s.", mirror.RemoteAddress, repo.FullName())
|
||||
}
|
||||
|
||||
func RecordRepositorySigningVerification(ctx context.Context, doer *user_model.User, repo *repository_model.Repository) {
|
||||
record(ctx, audit_model.RepositorySigningVerification, doer, repo, repo, "Changed signing verification of repository %s to %s.", repo.FullName(), repo.TrustModel.String())
|
||||
}
|
||||
|
||||
func RecordRepositoryTransferStart(ctx context.Context, doer *user_model.User, repo *repository_model.Repository, newOwner *user_model.User) {
|
||||
record(ctx, audit_model.RepositoryTransferStart, doer, repo, repo, "Started repository transfer of %s to %s.", repo.FullName(), newOwner.Name)
|
||||
}
|
||||
|
||||
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 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) {
|
||||
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.ToString())
|
||||
}
|
||||
|
||||
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 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)
|
||||
}
|
||||
|
||||
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, &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, &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, &systemObject, authSource, "Removed authentication source %s.", authSource.Name)
|
||||
}
|
|
@ -95,7 +95,7 @@ func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore
|
|||
opts := &user_service.UpdateOptions{
|
||||
Language: optional.Some(lc.Language()),
|
||||
}
|
||||
if err := user_service.UpdateUser(req.Context(), user, opts); err != nil {
|
||||
if err := user_service.UpdateUser(req.Context(), user, user, opts); err != nil {
|
||||
log.Error(fmt.Sprintf("Error updating user language [user: %d, locale: %s]", user.ID, user.Language))
|
||||
return
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/services/audit"
|
||||
)
|
||||
|
||||
// Ensure the struct implements the interface.
|
||||
|
@ -155,6 +156,8 @@ 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.RecordUserAuthenticationFailTwoFactor(req.Context(), u)
|
||||
|
||||
return util.NewInvalidArgumentErrorf("invalid provided OTP")
|
||||
}
|
||||
return nil
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user