aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmd/admin_auth_ldap.go17
-rw-r--r--cmd/admin_auth_oauth.go13
-rw-r--r--cmd/admin_auth_stmp.go14
-rw-r--r--custom/conf/app.example.ini4
-rw-r--r--models/actions/artifact.go19
-rw-r--r--models/auth/source.go43
-rw-r--r--models/auth/source_test.go2
-rw-r--r--models/auth/twofactor.go10
-rw-r--r--models/fixtures/action_artifact.yml18
-rw-r--r--models/migrations/migrations.go1
-rw-r--r--models/migrations/v1_24/v320.go57
-rw-r--r--models/perm/access/repo_permission.go4
-rw-r--r--modules/session/key.go11
-rw-r--r--modules/setting/security.go10
-rw-r--r--modules/structs/user_app.go4
-rw-r--r--options/locale/locale_en-US.ini1
-rw-r--r--routers/api/actions/artifacts.go11
-rw-r--r--routers/api/actions/artifactsv4.go20
-rw-r--r--routers/api/v1/api.go26
-rw-r--r--routers/web/admin/auths.go26
-rw-r--r--routers/web/auth/2fa.go2
-rw-r--r--routers/web/auth/auth.go28
-rw-r--r--routers/web/auth/oauth.go13
-rw-r--r--routers/web/user/setting/security/2fa.go11
-rw-r--r--routers/web/user/setting/security/webauthn.go3
-rw-r--r--services/auth/basic.go6
-rw-r--r--services/auth/interface.go5
-rw-r--r--services/auth/source/db/source.go4
-rw-r--r--services/auth/source/ldap/assert_interface_test.go2
-rw-r--r--services/auth/source/ldap/source.go10
-rw-r--r--services/auth/source/ldap/source_authenticate.go15
-rw-r--r--services/auth/source/ldap/source_sync.go42
-rw-r--r--services/auth/source/oauth2/assert_interface_test.go1
-rw-r--r--services/auth/source/oauth2/source.go11
-rw-r--r--services/auth/source/oauth2/source_callout.go4
-rw-r--r--services/auth/source/oauth2/source_register.go6
-rw-r--r--services/auth/source/oauth2/source_sync.go12
-rw-r--r--services/auth/source/oauth2/source_sync_test.go18
-rw-r--r--services/auth/source/pam/assert_interface_test.go1
-rw-r--r--services/auth/source/pam/source.go13
-rw-r--r--services/auth/source/pam/source_authenticate.go7
-rw-r--r--services/auth/source/smtp/assert_interface_test.go1
-rw-r--r--services/auth/source/smtp/source.go11
-rw-r--r--services/auth/source/smtp/source_authenticate.go7
-rw-r--r--services/auth/source/sspi/source.go2
-rw-r--r--services/context/context.go9
-rw-r--r--services/context/repo.go12
-rw-r--r--services/forms/auth_form.go9
-rw-r--r--services/issue/pull.go36
-rw-r--r--services/pull/pull.go7
-rw-r--r--templates/admin/auth/edit.tmpl35
-rw-r--r--templates/base/alert.tmpl5
-rw-r--r--templates/repo/branch_dropdown.tmpl14
-rw-r--r--templates/repo/wiki/revision.tmpl2
-rw-r--r--templates/status/404.tmpl1
-rw-r--r--templates/swagger/v1_json.tmpl13
-rw-r--r--tests/integration/api_actions_artifact_v4_test.go20
-rw-r--r--tests/integration/api_twofa_test.go2
-rw-r--r--web_src/css/base.css1
-rw-r--r--web_src/js/components/RepoBranchTagSelector.vue3
-rw-r--r--web_src/js/components/RepoContributors.vue2
61 files changed, 457 insertions, 260 deletions
diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go
index 274ec181d1..d2eeb7c0d6 100644
--- a/cmd/admin_auth_ldap.go
+++ b/cmd/admin_auth_ldap.go
@@ -9,6 +9,7 @@ import (
"strings"
"code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/ldap"
"github.com/urfave/cli/v2"
@@ -210,8 +211,8 @@ func newAuthService() *authService {
}
}
-// parseAuthSource assigns values on authSource according to command line flags.
-func parseAuthSource(c *cli.Context, authSource *auth.Source) {
+// parseAuthSourceLdap assigns values on authSource according to command line flags.
+func parseAuthSourceLdap(c *cli.Context, authSource *auth.Source) {
if c.IsSet("name") {
authSource.Name = c.String("name")
}
@@ -227,6 +228,7 @@ func parseAuthSource(c *cli.Context, authSource *auth.Source) {
if c.IsSet("disable-synchronize-users") {
authSource.IsSyncEnabled = !c.Bool("disable-synchronize-users")
}
+ authSource.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
}
// parseLdapConfig assigns values on config according to command line flags.
@@ -298,9 +300,6 @@ func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
if c.IsSet("allow-deactivate-all") {
config.AllowDeactivateAll = c.Bool("allow-deactivate-all")
}
- if c.IsSet("skip-local-2fa") {
- config.SkipLocalTwoFA = c.Bool("skip-local-2fa")
- }
if c.IsSet("enable-groups") {
config.GroupsEnabled = c.Bool("enable-groups")
}
@@ -376,7 +375,7 @@ func (a *authService) addLdapBindDn(c *cli.Context) error {
},
}
- parseAuthSource(c, authSource)
+ parseAuthSourceLdap(c, authSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
return err
}
@@ -398,7 +397,7 @@ func (a *authService) updateLdapBindDn(c *cli.Context) error {
return err
}
- parseAuthSource(c, authSource)
+ parseAuthSourceLdap(c, authSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
return err
}
@@ -427,7 +426,7 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
},
}
- parseAuthSource(c, authSource)
+ parseAuthSourceLdap(c, authSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
return err
}
@@ -449,7 +448,7 @@ func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
return err
}
- parseAuthSource(c, authSource)
+ parseAuthSourceLdap(c, authSource)
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
return err
}
diff --git a/cmd/admin_auth_oauth.go b/cmd/admin_auth_oauth.go
index 8e6239ac33..be5345d992 100644
--- a/cmd/admin_auth_oauth.go
+++ b/cmd/admin_auth_oauth.go
@@ -9,6 +9,7 @@ import (
"net/url"
auth_model "code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/auth/source/oauth2"
"github.com/urfave/cli/v2"
@@ -156,7 +157,6 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source {
OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"),
CustomURLMapping: customURLMapping,
IconURL: c.String("icon-url"),
- SkipLocalTwoFA: c.Bool("skip-local-2fa"),
Scopes: c.StringSlice("scopes"),
RequiredClaimName: c.String("required-claim-name"),
RequiredClaimValue: c.String("required-claim-value"),
@@ -185,10 +185,11 @@ func runAddOauth(c *cli.Context) error {
}
return auth_model.CreateSource(ctx, &auth_model.Source{
- Type: auth_model.OAuth2,
- Name: c.String("name"),
- IsActive: true,
- Cfg: config,
+ Type: auth_model.OAuth2,
+ Name: c.String("name"),
+ IsActive: true,
+ Cfg: config,
+ TwoFactorPolicy: util.Iif(c.Bool("skip-local-2fa"), "skip", ""),
})
}
@@ -294,6 +295,6 @@ func runUpdateOauth(c *cli.Context) error {
oAuth2Config.CustomURLMapping = customURLMapping
source.Cfg = oAuth2Config
-
+ source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
return auth_model.UpdateSource(ctx, source)
}
diff --git a/cmd/admin_auth_stmp.go b/cmd/admin_auth_stmp.go
index d724746905..babcf78cea 100644
--- a/cmd/admin_auth_stmp.go
+++ b/cmd/admin_auth_stmp.go
@@ -117,9 +117,6 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
if c.IsSet("disable-helo") {
conf.DisableHelo = c.Bool("disable-helo")
}
- if c.IsSet("skip-local-2fa") {
- conf.SkipLocalTwoFA = c.Bool("skip-local-2fa")
- }
return nil
}
@@ -156,10 +153,11 @@ func runAddSMTP(c *cli.Context) error {
}
return auth_model.CreateSource(ctx, &auth_model.Source{
- Type: auth_model.SMTP,
- Name: c.String("name"),
- IsActive: active,
- Cfg: &smtpConfig,
+ Type: auth_model.SMTP,
+ Name: c.String("name"),
+ IsActive: active,
+ Cfg: &smtpConfig,
+ TwoFactorPolicy: util.Iif(c.Bool("skip-local-2fa"), "skip", ""),
})
}
@@ -195,6 +193,6 @@ func runUpdateSMTP(c *cli.Context) error {
}
source.Cfg = smtpConfig
-
+ source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
return auth_model.UpdateSource(ctx, source)
}
diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index 42d181a00a..a7476ad1be 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -524,6 +524,10 @@ INTERNAL_TOKEN =
;;
;; On user registration, record the IP address and user agent of the user to help identify potential abuse.
;; RECORD_USER_SIGNUP_METADATA = false
+;;
+;; Set the two-factor auth behavior.
+;; Set to "enforced", to force users to enroll into Two-Factor Authentication, users without 2FA have no access to repositories via API or web.
+;TWO_FACTOR_AUTH =
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/models/actions/artifact.go b/models/actions/artifact.go
index 524224f070..757bd13acd 100644
--- a/models/actions/artifact.go
+++ b/models/actions/artifact.go
@@ -30,6 +30,25 @@ const (
ArtifactStatusDeleted // 6, ArtifactStatusDeleted is the status of an artifact that is deleted
)
+func (status ArtifactStatus) ToString() string {
+ switch status {
+ case ArtifactStatusUploadPending:
+ return "upload is not yet completed"
+ case ArtifactStatusUploadConfirmed:
+ return "upload is completed"
+ case ArtifactStatusUploadError:
+ return "upload failed"
+ case ArtifactStatusExpired:
+ return "expired"
+ case ArtifactStatusPendingDeletion:
+ return "pending deletion"
+ case ArtifactStatusDeleted:
+ return "deleted"
+ default:
+ return "unknown"
+ }
+}
+
func init() {
db.RegisterModel(new(ActionArtifact))
}
diff --git a/models/auth/source.go b/models/auth/source.go
index a3a250cd91..7d7bc0f03c 100644
--- a/models/auth/source.go
+++ b/models/auth/source.go
@@ -58,6 +58,15 @@ var Names = map[Type]string{
// Config represents login config as far as the db is concerned
type Config interface {
convert.Conversion
+ SetAuthSource(*Source)
+}
+
+type ConfigBase struct {
+ AuthSource *Source
+}
+
+func (p *ConfigBase) SetAuthSource(s *Source) {
+ p.AuthSource = s
}
// SkipVerifiable configurations provide a IsSkipVerify to check if SkipVerify is set
@@ -104,19 +113,15 @@ func RegisterTypeConfig(typ Type, exemplar Config) {
}
}
-// SourceSettable configurations can have their authSource set on them
-type SourceSettable interface {
- SetAuthSource(*Source)
-}
-
// Source represents an external way for authorizing users.
type Source struct {
- ID int64 `xorm:"pk autoincr"`
- Type Type
- Name string `xorm:"UNIQUE"`
- IsActive bool `xorm:"INDEX NOT NULL DEFAULT false"`
- IsSyncEnabled bool `xorm:"INDEX NOT NULL DEFAULT false"`
- Cfg convert.Conversion `xorm:"TEXT"`
+ ID int64 `xorm:"pk autoincr"`
+ Type Type
+ Name string `xorm:"UNIQUE"`
+ IsActive bool `xorm:"INDEX NOT NULL DEFAULT false"`
+ IsSyncEnabled bool `xorm:"INDEX NOT NULL DEFAULT false"`
+ TwoFactorPolicy string `xorm:"two_factor_policy NOT NULL DEFAULT ''"`
+ Cfg Config `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
@@ -140,9 +145,7 @@ func (source *Source) BeforeSet(colName string, val xorm.Cell) {
return
}
source.Cfg = constructor()
- if settable, ok := source.Cfg.(SourceSettable); ok {
- settable.SetAuthSource(source)
- }
+ source.Cfg.SetAuthSource(source)
}
}
@@ -200,6 +203,10 @@ func (source *Source) SkipVerify() bool {
return ok && skipVerifiable.IsSkipVerify()
}
+func (source *Source) TwoFactorShouldSkip() bool {
+ return source.TwoFactorPolicy == "skip"
+}
+
// CreateSource inserts a AuthSource in the DB if not already
// existing with the given name.
func CreateSource(ctx context.Context, source *Source) error {
@@ -223,9 +230,7 @@ func CreateSource(ctx context.Context, source *Source) error {
return nil
}
- if settable, ok := source.Cfg.(SourceSettable); ok {
- settable.SetAuthSource(source)
- }
+ source.Cfg.SetAuthSource(source)
registerableSource, ok := source.Cfg.(RegisterableSource)
if !ok {
@@ -320,9 +325,7 @@ func UpdateSource(ctx context.Context, source *Source) error {
return nil
}
- if settable, ok := source.Cfg.(SourceSettable); ok {
- settable.SetAuthSource(source)
- }
+ source.Cfg.SetAuthSource(source)
registerableSource, ok := source.Cfg.(RegisterableSource)
if !ok {
diff --git a/models/auth/source_test.go b/models/auth/source_test.go
index 84aede0a6b..64c7460b64 100644
--- a/models/auth/source_test.go
+++ b/models/auth/source_test.go
@@ -19,6 +19,8 @@ import (
)
type TestSource struct {
+ auth_model.ConfigBase
+
Provider string
ClientID string
ClientSecret string
diff --git a/models/auth/twofactor.go b/models/auth/twofactor.go
index d0c341a192..200ce7c7c0 100644
--- a/models/auth/twofactor.go
+++ b/models/auth/twofactor.go
@@ -164,3 +164,13 @@ func DeleteTwoFactorByID(ctx context.Context, id, userID int64) error {
}
return nil
}
+
+func HasTwoFactorOrWebAuthn(ctx context.Context, id int64) (bool, error) {
+ has, err := HasTwoFactorByUID(ctx, id)
+ if err != nil {
+ return false, err
+ } else if has {
+ return true, nil
+ }
+ return HasWebAuthnRegistrationsByUID(ctx, id)
+}
diff --git a/models/fixtures/action_artifact.yml b/models/fixtures/action_artifact.yml
index 485474108f..1b00daf198 100644
--- a/models/fixtures/action_artifact.yml
+++ b/models/fixtures/action_artifact.yml
@@ -11,6 +11,24 @@
content_encoding: ""
artifact_path: "abc.txt"
artifact_name: "artifact-download"
+ status: 2
+ created_unix: 1712338649
+ updated_unix: 1712338649
+ expired_unix: 1720114649
+
+-
+ id: 2
+ run_id: 791
+ runner_id: 1
+ repo_id: 4
+ owner_id: 1
+ commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
+ storage_path: ""
+ file_size: 1024
+ file_compressed_size: 1024
+ content_encoding: "30/20/1712348022422036662.chunk"
+ artifact_path: "abc.txt"
+ artifact_name: "artifact-download-incomplete"
status: 1
created_unix: 1712338649
updated_unix: 1712338649
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 31b035eb31..176372486e 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -381,6 +381,7 @@ func prepareMigrationTasks() []*migration {
newMigration(317, "Add new index for action for heatmap", v1_24.AddNewIndexForUserDashboard),
newMigration(318, "Add anonymous_access_mode for repo_unit", v1_24.AddRepoUnitAnonymousAccessMode),
newMigration(319, "Add ExclusiveOrder to Label table", v1_24.AddExclusiveOrderColumnToLabelTable),
+ newMigration(320, "Migrate two_factor_policy to login_source table", v1_24.MigrateSkipTwoFactor),
}
return preparedMigrations
}
diff --git a/models/migrations/v1_24/v320.go b/models/migrations/v1_24/v320.go
new file mode 100644
index 0000000000..1d34444826
--- /dev/null
+++ b/models/migrations/v1_24/v320.go
@@ -0,0 +1,57 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_24 //nolint
+
+import (
+ "code.gitea.io/gitea/modules/json"
+
+ "xorm.io/xorm"
+)
+
+func MigrateSkipTwoFactor(x *xorm.Engine) error {
+ type LoginSource struct {
+ TwoFactorPolicy string `xorm:"two_factor_policy NOT NULL DEFAULT ''"`
+ }
+ _, err := x.SyncWithOptions(
+ xorm.SyncOptions{
+ IgnoreConstrains: true,
+ IgnoreIndices: true,
+ },
+ new(LoginSource),
+ )
+ if err != nil {
+ return err
+ }
+
+ type LoginSourceSimple struct {
+ ID int64
+ Cfg string
+ }
+
+ var loginSources []LoginSourceSimple
+ err = x.Table("login_source").Find(&loginSources)
+ if err != nil {
+ return err
+ }
+
+ for _, source := range loginSources {
+ if source.Cfg == "" {
+ continue
+ }
+
+ var cfg map[string]any
+ err = json.Unmarshal([]byte(source.Cfg), &cfg)
+ if err != nil {
+ return err
+ }
+
+ if cfg["SkipLocalTwoFA"] == true {
+ _, err = x.Exec("UPDATE login_source SET two_factor_policy = 'skip' WHERE id = ?", source.ID)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go
index 9d0c9f0077..45efb192c8 100644
--- a/models/perm/access/repo_permission.go
+++ b/models/perm/access/repo_permission.go
@@ -522,3 +522,7 @@ func CheckRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *u
return perm.CanRead(unitType)
}
+
+func PermissionNoAccess() Permission {
+ return Permission{AccessMode: perm_model.AccessModeNone}
+}
diff --git a/modules/session/key.go b/modules/session/key.go
new file mode 100644
index 0000000000..c3da997c67
--- /dev/null
+++ b/modules/session/key.go
@@ -0,0 +1,11 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package session
+
+const (
+ KeyUID = "uid"
+ KeyUname = "uname"
+
+ KeyUserHasTwoFactorAuth = "userHasTwoFactorAuth"
+)
diff --git a/modules/setting/security.go b/modules/setting/security.go
index 2f798b75c7..3ae4c005c7 100644
--- a/modules/setting/security.go
+++ b/modules/setting/security.go
@@ -39,6 +39,7 @@ var (
CSRFCookieName = "_csrf"
CSRFCookieHTTPOnly = true
RecordUserSignupMetadata = false
+ TwoFactorAuthEnforced = false
)
// loadSecret load the secret from ini by uriKey or verbatimKey, only one of them could be set
@@ -142,6 +143,15 @@ func loadSecurityFrom(rootCfg ConfigProvider) {
PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false)
SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20)
+ twoFactorAuth := sec.Key("TWO_FACTOR_AUTH").String()
+ switch twoFactorAuth {
+ case "":
+ case "enforced":
+ TwoFactorAuthEnforced = true
+ default:
+ log.Fatal("Invalid two-factor auth option: %s", twoFactorAuth)
+ }
+
InternalToken = loadSecret(sec, "INTERNAL_TOKEN_URI", "INTERNAL_TOKEN")
if InstallLock && InternalToken == "" {
// if Gitea has been installed but the InternalToken hasn't been generated (upgrade from an old release), we should generate
diff --git a/modules/structs/user_app.go b/modules/structs/user_app.go
index a7d2e28b41..8401252bd6 100644
--- a/modules/structs/user_app.go
+++ b/modules/structs/user_app.go
@@ -23,9 +23,11 @@ type AccessToken struct {
type AccessTokenList []*AccessToken
// CreateAccessTokenOption options when create access token
+// swagger:model CreateAccessTokenOption
type CreateAccessTokenOption struct {
// required: true
- Name string `json:"name" binding:"Required"`
+ Name string `json:"name" binding:"Required"`
+ // example: ["all", "read:activitypub","read:issue", "write:misc", "read:notification", "read:organization", "read:package", "read:repository", "read:user"]
Scopes []string `json:"scopes"`
}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 9928b3588a..a8fabc9ca1 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -450,6 +450,7 @@ use_scratch_code = Use a scratch code
twofa_scratch_used = You have used your scratch code. You have been redirected to the two-factor settings page so you may remove your device enrollment or generate a new scratch code.
twofa_passcode_incorrect = Your passcode is incorrect. If you misplaced your device, use your scratch code to sign in.
twofa_scratch_token_incorrect = Your scratch code is incorrect.
+twofa_required = You must setup Two-Factor Authentication to get access to repositories, or try to login again.
login_userpass = Sign In
login_openid = OpenID
oauth_signup_tab = Register New Account
diff --git a/routers/api/actions/artifacts.go b/routers/api/actions/artifacts.go
index 0832e52f55..6473659e5c 100644
--- a/routers/api/actions/artifacts.go
+++ b/routers/api/actions/artifacts.go
@@ -337,7 +337,10 @@ func (ar artifactRoutes) listArtifacts(ctx *ArtifactContext) {
return
}
- artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{RunID: runID})
+ artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{
+ RunID: runID,
+ Status: int(actions.ArtifactStatusUploadConfirmed),
+ })
if err != nil {
log.Error("Error getting artifacts: %v", err)
ctx.HTTPError(http.StatusInternalServerError, err.Error())
@@ -402,6 +405,7 @@ func (ar artifactRoutes) getDownloadArtifactURL(ctx *ArtifactContext) {
artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{
RunID: runID,
ArtifactName: itemPath,
+ Status: int(actions.ArtifactStatusUploadConfirmed),
})
if err != nil {
log.Error("Error getting artifacts: %v", err)
@@ -473,6 +477,11 @@ func (ar artifactRoutes) downloadArtifact(ctx *ArtifactContext) {
ctx.HTTPError(http.StatusBadRequest)
return
}
+ if artifact.Status != actions.ArtifactStatusUploadConfirmed {
+ log.Error("Error artifact not found: %s", artifact.Status.ToString())
+ ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
+ return
+ }
fd, err := ar.fs.Open(artifact.StoragePath)
if err != nil {
diff --git a/routers/api/actions/artifactsv4.go b/routers/api/actions/artifactsv4.go
index 9fb0a31549..e9e9fc6393 100644
--- a/routers/api/actions/artifactsv4.go
+++ b/routers/api/actions/artifactsv4.go
@@ -448,17 +448,15 @@ func (r *artifactV4Routes) listArtifacts(ctx *ArtifactContext) {
return
}
- artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{RunID: runID})
+ artifacts, err := db.Find[actions.ActionArtifact](ctx, actions.FindArtifactsOptions{
+ RunID: runID,
+ Status: int(actions.ArtifactStatusUploadConfirmed),
+ })
if err != nil {
log.Error("Error getting artifacts: %v", err)
ctx.HTTPError(http.StatusInternalServerError, err.Error())
return
}
- if len(artifacts) == 0 {
- log.Debug("[artifact] handleListArtifacts, no artifacts")
- ctx.HTTPError(http.StatusNotFound)
- return
- }
list := []*ListArtifactsResponse_MonolithArtifact{}
@@ -510,6 +508,11 @@ func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) {
ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
return
}
+ if artifact.Status != actions.ArtifactStatusUploadConfirmed {
+ log.Error("Error artifact not found: %s", artifact.Status.ToString())
+ ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
+ return
+ }
respData := GetSignedArtifactURLResponse{}
@@ -538,6 +541,11 @@ func (r *artifactV4Routes) downloadArtifact(ctx *ArtifactContext) {
ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
return
}
+ if artifact.Status != actions.ArtifactStatusUploadConfirmed {
+ log.Error("Error artifact not found: %s", artifact.Status.ToString())
+ ctx.HTTPError(http.StatusNotFound, "Error artifact not found")
+ return
+ }
file, _ := r.fs.Open(artifact.StoragePath)
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 58ae8ec90a..b98863b418 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -64,6 +64,7 @@
package v1
import (
+ gocontext "context"
"errors"
"fmt"
"net/http"
@@ -211,11 +212,20 @@ func repoAssignment() func(ctx *context.APIContext) {
}
ctx.Repo.Permission.SetUnitsWithDefaultAccessMode(ctx.Repo.Repository.Units, ctx.Repo.Permission.AccessMode)
} else {
- ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
+ needTwoFactor, err := doerNeedTwoFactorAuth(ctx, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
}
+ if needTwoFactor {
+ ctx.Repo.Permission = access_model.PermissionNoAccess()
+ } else {
+ ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
+ if err != nil {
+ ctx.APIErrorInternal(err)
+ return
+ }
+ }
}
if !ctx.Repo.Permission.HasAnyUnitAccess() {
@@ -225,6 +235,20 @@ func repoAssignment() func(ctx *context.APIContext) {
}
}
+func doerNeedTwoFactorAuth(ctx gocontext.Context, doer *user_model.User) (bool, error) {
+ if !setting.TwoFactorAuthEnforced {
+ return false, nil
+ }
+ if doer == nil {
+ return false, nil
+ }
+ has, err := auth_model.HasTwoFactorOrWebAuthn(ctx, doer.ID)
+ if err != nil {
+ return false, err
+ }
+ return !has, nil
+}
+
func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() {
diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go
index 2b3bf1f77d..80d554b6e3 100644
--- a/routers/web/admin/auths.go
+++ b/routers/web/admin/auths.go
@@ -28,8 +28,6 @@ import (
"code.gitea.io/gitea/services/auth/source/sspi"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
-
- "xorm.io/xorm/convert"
)
const (
@@ -149,7 +147,6 @@ func parseLDAPConfig(form forms.AuthenticationForm) *ldap.Source {
RestrictedFilter: form.RestrictedFilter,
AllowDeactivateAll: form.AllowDeactivateAll,
Enabled: true,
- SkipLocalTwoFA: form.SkipLocalTwoFA,
}
}
@@ -163,7 +160,6 @@ func parseSMTPConfig(form forms.AuthenticationForm) *smtp.Source {
SkipVerify: form.SkipVerify,
HeloHostname: form.HeloHostname,
DisableHelo: form.DisableHelo,
- SkipLocalTwoFA: form.SkipLocalTwoFA,
}
}
@@ -198,7 +194,6 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
Scopes: scopes,
RequiredClaimName: form.Oauth2RequiredClaimName,
RequiredClaimValue: form.Oauth2RequiredClaimValue,
- SkipLocalTwoFA: form.SkipLocalTwoFA,
GroupClaimName: form.Oauth2GroupClaimName,
RestrictedGroup: form.Oauth2RestrictedGroup,
AdminGroup: form.Oauth2AdminGroup,
@@ -252,7 +247,7 @@ func NewAuthSourcePost(ctx *context.Context) {
ctx.Data["SSPIDefaultLanguage"] = ""
hasTLS := false
- var config convert.Conversion
+ var config auth.Config
switch auth.Type(form.Type) {
case auth.LDAP, auth.DLDAP:
config = parseLDAPConfig(form)
@@ -262,9 +257,8 @@ func NewAuthSourcePost(ctx *context.Context) {
hasTLS = true
case auth.PAM:
config = &pam_service.Source{
- ServiceName: form.PAMServiceName,
- EmailDomain: form.PAMEmailDomain,
- SkipLocalTwoFA: form.SkipLocalTwoFA,
+ ServiceName: form.PAMServiceName,
+ EmailDomain: form.PAMEmailDomain,
}
case auth.OAuth2:
config = parseOAuth2Config(form)
@@ -302,11 +296,12 @@ func NewAuthSourcePost(ctx *context.Context) {
}
if err := auth.CreateSource(ctx, &auth.Source{
- Type: auth.Type(form.Type),
- Name: form.Name,
- IsActive: form.IsActive,
- IsSyncEnabled: form.IsSyncEnabled,
- Cfg: config,
+ Type: auth.Type(form.Type),
+ Name: form.Name,
+ IsActive: form.IsActive,
+ IsSyncEnabled: form.IsSyncEnabled,
+ TwoFactorPolicy: form.TwoFactorPolicy,
+ Cfg: config,
}); err != nil {
if auth.IsErrSourceAlreadyExist(err) {
ctx.Data["Err_Name"] = true
@@ -384,7 +379,7 @@ func EditAuthSourcePost(ctx *context.Context) {
return
}
- var config convert.Conversion
+ var config auth.Config
switch auth.Type(form.Type) {
case auth.LDAP, auth.DLDAP:
config = parseLDAPConfig(form)
@@ -421,6 +416,7 @@ func EditAuthSourcePost(ctx *context.Context) {
source.IsActive = form.IsActive
source.IsSyncEnabled = form.IsSyncEnabled
source.Cfg = config
+ source.TwoFactorPolicy = form.TwoFactorPolicy
if err := auth.UpdateSource(ctx, source); err != nil {
if auth.IsErrSourceAlreadyExist(err) {
ctx.Data["Err_Name"] = true
diff --git a/routers/web/auth/2fa.go b/routers/web/auth/2fa.go
index fe363fe90a..d15d33dfd4 100644
--- a/routers/web/auth/2fa.go
+++ b/routers/web/auth/2fa.go
@@ -9,6 +9,7 @@ import (
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web"
@@ -87,6 +88,7 @@ func TwoFactorPost(ctx *context.Context) {
return
}
+ _ = ctx.Session.Set(session.KeyUserHasTwoFactorAuth, true)
handleSignIn(ctx, u, remember)
return
}
diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go
index 1de8d7e8a3..69b9d285b7 100644
--- a/routers/web/auth/auth.go
+++ b/routers/web/auth/auth.go
@@ -76,6 +76,10 @@ func autoSignIn(ctx *context.Context) (bool, error) {
}
return false, nil
}
+ userHasTwoFactorAuth, err := auth.HasTwoFactorOrWebAuthn(ctx, u.ID)
+ if err != nil {
+ return false, fmt.Errorf("HasTwoFactorOrWebAuthn: %w", err)
+ }
isSucceed = true
@@ -87,9 +91,9 @@ func autoSignIn(ctx *context.Context) (bool, error) {
ctx.SetSiteCookie(setting.CookieRememberName, nt.ID+":"+token, setting.LogInRememberDays*timeutil.Day)
if err := updateSession(ctx, nil, map[string]any{
- // Set session IDs
- "uid": u.ID,
- "uname": u.Name,
+ session.KeyUID: u.ID,
+ session.KeyUname: u.Name,
+ session.KeyUserHasTwoFactorAuth: userHasTwoFactorAuth,
}); err != nil {
return false, fmt.Errorf("unable to updateSession: %w", err)
}
@@ -239,9 +243,8 @@ func SignInPost(ctx *context.Context) {
}
// Now handle 2FA:
-
// First of all if the source can skip local two fa we're done
- if skipper, ok := source.Cfg.(auth_service.LocalTwoFASkipper); ok && skipper.IsSkipLocalTwoFA() {
+ if source.TwoFactorShouldSkip() {
handleSignIn(ctx, u, form.Remember)
return
}
@@ -262,7 +265,7 @@ func SignInPost(ctx *context.Context) {
}
if !hasTOTPtwofa && !hasWebAuthnTwofa {
- // No two factor auth configured we can sign in the user
+ // No two-factor auth configured we can sign in the user
handleSignIn(ctx, u, form.Remember)
return
}
@@ -311,8 +314,14 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
ctx.SetSiteCookie(setting.CookieRememberName, nt.ID+":"+token, setting.LogInRememberDays*timeutil.Day)
}
+ userHasTwoFactorAuth, err := auth.HasTwoFactorOrWebAuthn(ctx, u.ID)
+ if err != nil {
+ ctx.ServerError("HasTwoFactorOrWebAuthn", err)
+ return setting.AppSubURL + "/"
+ }
+
if err := updateSession(ctx, []string{
- // Delete the openid, 2fa and linkaccount data
+ // Delete the openid, 2fa and link_account data
"openid_verified_uri",
"openid_signin_remember",
"openid_determined_email",
@@ -321,8 +330,9 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
"twofaRemember",
"linkAccount",
}, map[string]any{
- "uid": u.ID,
- "uname": u.Name,
+ session.KeyUID: u.ID,
+ session.KeyUname: u.Name,
+ session.KeyUserHasTwoFactorAuth: userHasTwoFactorAuth,
}); err != nil {
ctx.ServerError("RegenerateSession", err)
return setting.AppSubURL + "/"
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index 94a8bec565..96c1dcf358 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
+ "code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web/middleware"
@@ -302,7 +303,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
updateAvatarIfNeed(ctx, gothUser.AvatarURL, u)
needs2FA := false
- if !source.Cfg.(*oauth2.Source).SkipLocalTwoFA {
+ if !source.TwoFactorShouldSkip() {
_, err := auth.GetTwoFactorByUID(ctx, u.ID)
if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
ctx.ServerError("UserSignIn", err)
@@ -352,10 +353,16 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
ctx.ServerError("UpdateUser", err)
return
}
+ userHasTwoFactorAuth, err := auth.HasTwoFactorOrWebAuthn(ctx, u.ID)
+ if err != nil {
+ ctx.ServerError("UpdateUser", err)
+ return
+ }
if err := updateSession(ctx, nil, map[string]any{
- "uid": u.ID,
- "uname": u.Name,
+ session.KeyUID: u.ID,
+ session.KeyUname: u.Name,
+ session.KeyUserHasTwoFactorAuth: userHasTwoFactorAuth,
}); err != nil {
ctx.ServerError("updateSession", err)
return
diff --git a/routers/web/user/setting/security/2fa.go b/routers/web/user/setting/security/2fa.go
index e5315efc74..e5e23c820c 100644
--- a/routers/web/user/setting/security/2fa.go
+++ b/routers/web/user/setting/security/2fa.go
@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
@@ -163,6 +164,7 @@ func EnrollTwoFactor(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsSecurity"] = true
+ ctx.Data["ShowTwoFactorRequiredMessage"] = false
t, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID)
if t != nil {
@@ -194,6 +196,7 @@ func EnrollTwoFactorPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.TwoFactorAuthForm)
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsSecurity"] = true
+ ctx.Data["ShowTwoFactorRequiredMessage"] = false
t, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID)
if t != nil {
@@ -246,6 +249,10 @@ func EnrollTwoFactorPost(ctx *context.Context) {
return
}
+ newTwoFactorErr := auth.NewTwoFactor(ctx, t)
+ if newTwoFactorErr == nil {
+ _ = ctx.Session.Set(session.KeyUserHasTwoFactorAuth, true)
+ }
// Now we have to delete the secrets - because if we fail to insert then it's highly likely that they have already been used
// If we can detect the unique constraint failure below we can move this to after the NewTwoFactor
if err := ctx.Session.Delete("twofaSecret"); err != nil {
@@ -261,10 +268,10 @@ func EnrollTwoFactorPost(ctx *context.Context) {
log.Error("Unable to save changes to the session: %v", err)
}
- if err = auth.NewTwoFactor(ctx, t); err != nil {
+ if newTwoFactorErr != nil {
// FIXME: We need to handle a unique constraint fail here it's entirely possible that another request has beaten us.
// If there is a unique constraint fail we should just tolerate the error
- ctx.ServerError("SettingsTwoFactor: Failed to save two factor", err)
+ ctx.ServerError("SettingsTwoFactor: Failed to save two factor", newTwoFactorErr)
return
}
diff --git a/routers/web/user/setting/security/webauthn.go b/routers/web/user/setting/security/webauthn.go
index 63721343df..eb9f46af52 100644
--- a/routers/web/user/setting/security/webauthn.go
+++ b/routers/web/user/setting/security/webauthn.go
@@ -13,6 +13,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
wa "code.gitea.io/gitea/modules/auth/webauthn"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
@@ -120,7 +121,7 @@ func WebauthnRegisterPost(ctx *context.Context) {
return
}
_ = ctx.Session.Delete("webauthnName")
-
+ _ = ctx.Session.Set(session.KeyUserHasTwoFactorAuth, true)
ctx.JSON(http.StatusCreated, cred)
}
diff --git a/services/auth/basic.go b/services/auth/basic.go
index e22b9e1eb7..a208590d7b 100644
--- a/services/auth/basic.go
+++ b/services/auth/basic.go
@@ -142,14 +142,14 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
return nil, err
}
- if skipper, ok := source.Cfg.(LocalTwoFASkipper); !ok || !skipper.IsSkipLocalTwoFA() {
- // Check if the user has webAuthn registration
+ if !source.TwoFactorShouldSkip() {
+ // Check if the user has WebAuthn registration
hasWebAuthn, err := auth_model.HasWebAuthnRegistrationsByUID(req.Context(), u.ID)
if err != nil {
return nil, err
}
if hasWebAuthn {
- return nil, errors.New("Basic authorization is not allowed while webAuthn enrolled")
+ return nil, errors.New("basic authorization is not allowed while WebAuthn enrolled")
}
if err := validateTOTP(req, u); err != nil {
diff --git a/services/auth/interface.go b/services/auth/interface.go
index 275b4dd56c..e7eccecea0 100644
--- a/services/auth/interface.go
+++ b/services/auth/interface.go
@@ -35,11 +35,6 @@ type PasswordAuthenticator interface {
Authenticate(ctx context.Context, user *user_model.User, login, password string) (*user_model.User, error)
}
-// LocalTwoFASkipper represents a source of authentication that can skip local 2fa
-type LocalTwoFASkipper interface {
- IsSkipLocalTwoFA() bool
-}
-
// SynchronizableSource represents a source that can synchronize users
type SynchronizableSource interface {
Sync(ctx context.Context, updateExisting bool) error
diff --git a/services/auth/source/db/source.go b/services/auth/source/db/source.go
index bb2270cbd6..90baa61f5b 100644
--- a/services/auth/source/db/source.go
+++ b/services/auth/source/db/source.go
@@ -11,7 +11,9 @@ import (
)
// Source is a password authentication service
-type Source struct{}
+type Source struct {
+ auth.ConfigBase `json:"-"`
+}
// FromDB fills up an OAuth2Config from serialized format.
func (source *Source) FromDB(bs []byte) error {
diff --git a/services/auth/source/ldap/assert_interface_test.go b/services/auth/source/ldap/assert_interface_test.go
index 33347687dc..8e8accd668 100644
--- a/services/auth/source/ldap/assert_interface_test.go
+++ b/services/auth/source/ldap/assert_interface_test.go
@@ -15,13 +15,11 @@ import (
type sourceInterface interface {
auth.PasswordAuthenticator
auth.SynchronizableSource
- auth.LocalTwoFASkipper
auth_model.SSHKeyProvider
auth_model.Config
auth_model.SkipVerifiable
auth_model.HasTLSer
auth_model.UseTLSer
- auth_model.SourceSettable
}
var _ (sourceInterface) = &ldap.Source{}
diff --git a/services/auth/source/ldap/source.go b/services/auth/source/ldap/source.go
index 963cdba7c2..d49dbc45ce 100644
--- a/services/auth/source/ldap/source.go
+++ b/services/auth/source/ldap/source.go
@@ -24,6 +24,8 @@ import (
// Source Basic LDAP authentication service
type Source struct {
+ auth.ConfigBase `json:"-"`
+
Name string // canonical name (ie. corporate.ad)
Host string // LDAP host
Port int // port number
@@ -54,9 +56,6 @@ type Source struct {
GroupTeamMap string // Map LDAP groups to teams
GroupTeamMapRemoval bool // Remove user from teams which are synchronized and user is not a member of the corresponding LDAP group
UserUID string // User Attribute listed in Group
- SkipLocalTwoFA bool `json:",omitempty"` // Skip Local 2fa for users authenticated with this source
-
- authSource *auth.Source // reference to the authSource
}
// FromDB fills up a LDAPConfig from serialized format.
@@ -109,11 +108,6 @@ func (source *Source) ProvidesSSHKeys() bool {
return strings.TrimSpace(source.AttributeSSHPublicKey) != ""
}
-// SetAuthSource sets the related AuthSource
-func (source *Source) SetAuthSource(authSource *auth.Source) {
- source.authSource = authSource
-}
-
func init() {
auth.RegisterTypeConfig(auth.LDAP, &Source{})
auth.RegisterTypeConfig(auth.DLDAP, &Source{})
diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go
index 7aee761d0d..a2e8c2b86a 100644
--- a/services/auth/source/ldap/source_authenticate.go
+++ b/services/auth/source/ldap/source_authenticate.go
@@ -25,7 +25,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
if user != nil {
loginName = user.LoginName
}
- sr := source.SearchEntry(loginName, password, source.authSource.Type == auth.DLDAP)
+ sr := source.SearchEntry(loginName, password, source.AuthSource.Type == auth.DLDAP)
if sr == nil {
// User not in LDAP, do nothing
return nil, user_model.ErrUserNotExist{Name: loginName}
@@ -73,7 +73,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
}
if user != nil {
- if isAttributeSSHPublicKeySet && asymkey_model.SynchronizePublicKeys(ctx, user, source.authSource, sr.SSHPublicKey) {
+ if isAttributeSSHPublicKeySet && asymkey_model.SynchronizePublicKeys(ctx, user, source.AuthSource, sr.SSHPublicKey) {
if err := asymkey_service.RewriteAllPublicKeys(ctx); err != nil {
return user, err
}
@@ -84,8 +84,8 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
Name: sr.Username,
FullName: composeFullName(sr.Name, sr.Surname, sr.Username),
Email: sr.Mail,
- LoginType: source.authSource.Type,
- LoginSource: source.authSource.ID,
+ LoginType: source.AuthSource.Type,
+ LoginSource: source.AuthSource.ID,
LoginName: userName,
IsAdmin: sr.IsAdmin,
}
@@ -99,7 +99,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
return user, err
}
- if isAttributeSSHPublicKeySet && asymkey_model.AddPublicKeysBySource(ctx, user, source.authSource, sr.SSHPublicKey) {
+ if isAttributeSSHPublicKeySet && asymkey_model.AddPublicKeysBySource(ctx, user, source.AuthSource, sr.SSHPublicKey) {
if err := asymkey_service.RewriteAllPublicKeys(ctx); err != nil {
return user, err
}
@@ -123,8 +123,3 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
return user, nil
}
-
-// IsSkipLocalTwoFA returns if this source should skip local 2fa for password authentication
-func (source *Source) IsSkipLocalTwoFA() bool {
- return source.SkipLocalTwoFA
-}
diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go
index ff36db2955..678b6b2b62 100644
--- a/services/auth/source/ldap/source_sync.go
+++ b/services/auth/source/ldap/source_sync.go
@@ -22,21 +22,21 @@ import (
// Sync causes this ldap source to synchronize its users with the db
func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
- log.Trace("Doing: SyncExternalUsers[%s]", source.authSource.Name)
+ log.Trace("Doing: SyncExternalUsers[%s]", source.AuthSource.Name)
isAttributeSSHPublicKeySet := strings.TrimSpace(source.AttributeSSHPublicKey) != ""
var sshKeysNeedUpdate bool
// Find all users with this login type - FIXME: Should this be an iterator?
- users, err := user_model.GetUsersBySource(ctx, source.authSource)
+ users, err := user_model.GetUsersBySource(ctx, source.AuthSource)
if err != nil {
log.Error("SyncExternalUsers: %v", err)
return err
}
select {
case <-ctx.Done():
- log.Warn("SyncExternalUsers: Cancelled before update of %s", source.authSource.Name)
- return db.ErrCancelledf("Before update of %s", source.authSource.Name)
+ log.Warn("SyncExternalUsers: Cancelled before update of %s", source.AuthSource.Name)
+ return db.ErrCancelledf("Before update of %s", source.AuthSource.Name)
default:
}
@@ -51,7 +51,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
sr, err := source.SearchEntries()
if err != nil {
- log.Error("SyncExternalUsers LDAP source failure [%s], skipped", source.authSource.Name)
+ log.Error("SyncExternalUsers LDAP source failure [%s], skipped", source.AuthSource.Name)
return nil
}
@@ -74,7 +74,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
for _, su := range sr {
select {
case <-ctx.Done():
- log.Warn("SyncExternalUsers: Cancelled at update of %s before completed update of users", source.authSource.Name)
+ log.Warn("SyncExternalUsers: Cancelled at update of %s before completed update of users", source.AuthSource.Name)
// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
if sshKeysNeedUpdate {
err = asymkey_service.RewriteAllPublicKeys(ctx)
@@ -82,7 +82,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
log.Error("RewriteAllPublicKeys: %v", err)
}
}
- return db.ErrCancelledf("During update of %s before completed update of users", source.authSource.Name)
+ return db.ErrCancelledf("During update of %s before completed update of users", source.AuthSource.Name)
default:
}
if su.Username == "" && su.Mail == "" {
@@ -111,14 +111,14 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
fullName := composeFullName(su.Name, su.Surname, su.Username)
// If no existing user found, create one
if usr == nil {
- log.Trace("SyncExternalUsers[%s]: Creating user %s", source.authSource.Name, su.Username)
+ log.Trace("SyncExternalUsers[%s]: Creating user %s", source.AuthSource.Name, su.Username)
usr = &user_model.User{
LowerName: su.LowerName,
Name: su.Username,
FullName: fullName,
- LoginType: source.authSource.Type,
- LoginSource: source.authSource.ID,
+ LoginType: source.AuthSource.Type,
+ LoginSource: source.AuthSource.ID,
LoginName: su.Username,
Email: su.Mail,
IsAdmin: su.IsAdmin,
@@ -130,12 +130,12 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
err = user_model.CreateUser(ctx, usr, &user_model.Meta{}, overwriteDefault)
if err != nil {
- log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.authSource.Name, su.Username, err)
+ log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", source.AuthSource.Name, su.Username, err)
}
if err == nil && isAttributeSSHPublicKeySet {
- log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", source.authSource.Name, usr.Name)
- if asymkey_model.AddPublicKeysBySource(ctx, usr, source.authSource, su.SSHPublicKey) {
+ log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", source.AuthSource.Name, usr.Name)
+ if asymkey_model.AddPublicKeysBySource(ctx, usr, source.AuthSource, su.SSHPublicKey) {
sshKeysNeedUpdate = true
}
}
@@ -145,7 +145,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
}
} else if updateExisting {
// Synchronize SSH Public Key if that attribute is set
- if isAttributeSSHPublicKeySet && asymkey_model.SynchronizePublicKeys(ctx, usr, source.authSource, su.SSHPublicKey) {
+ if isAttributeSSHPublicKeySet && asymkey_model.SynchronizePublicKeys(ctx, usr, source.AuthSource, su.SSHPublicKey) {
sshKeysNeedUpdate = true
}
@@ -155,7 +155,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
!strings.EqualFold(usr.Email, su.Mail) ||
usr.FullName != fullName ||
!usr.IsActive {
- log.Trace("SyncExternalUsers[%s]: Updating user %s", source.authSource.Name, usr.Name)
+ log.Trace("SyncExternalUsers[%s]: Updating user %s", source.AuthSource.Name, usr.Name)
opts := &user_service.UpdateOptions{
FullName: optional.Some(fullName),
@@ -170,11 +170,11 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
}
if err := user_service.UpdateUser(ctx, usr, opts); err != nil {
- log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", source.authSource.Name, usr.Name, err)
+ log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", source.AuthSource.Name, usr.Name, err)
}
if err := user_service.ReplacePrimaryEmailAddress(ctx, usr, su.Mail); err != nil {
- log.Error("SyncExternalUsers[%s]: Error updating user %s primary email %s: %v", source.authSource.Name, usr.Name, su.Mail, err)
+ log.Error("SyncExternalUsers[%s]: Error updating user %s primary email %s: %v", source.AuthSource.Name, usr.Name, su.Mail, err)
}
}
@@ -202,8 +202,8 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
select {
case <-ctx.Done():
- log.Warn("SyncExternalUsers: Cancelled during update of %s before delete users", source.authSource.Name)
- return db.ErrCancelledf("During update of %s before delete users", source.authSource.Name)
+ log.Warn("SyncExternalUsers: Cancelled during update of %s before delete users", source.AuthSource.Name)
+ return db.ErrCancelledf("During update of %s before delete users", source.AuthSource.Name)
default:
}
@@ -214,13 +214,13 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
continue
}
- log.Trace("SyncExternalUsers[%s]: Deactivating user %s", source.authSource.Name, usr.Name)
+ log.Trace("SyncExternalUsers[%s]: Deactivating user %s", source.AuthSource.Name, usr.Name)
opts := &user_service.UpdateOptions{
IsActive: optional.Some(false),
}
if err := user_service.UpdateUser(ctx, usr, opts); err != nil {
- log.Error("SyncExternalUsers[%s]: Error deactivating user %s: %v", source.authSource.Name, usr.Name, err)
+ log.Error("SyncExternalUsers[%s]: Error deactivating user %s: %v", source.AuthSource.Name, usr.Name, err)
}
}
}
diff --git a/services/auth/source/oauth2/assert_interface_test.go b/services/auth/source/oauth2/assert_interface_test.go
index 56fe0e4aa8..d870ac1dcd 100644
--- a/services/auth/source/oauth2/assert_interface_test.go
+++ b/services/auth/source/oauth2/assert_interface_test.go
@@ -14,7 +14,6 @@ import (
type sourceInterface interface {
auth_model.Config
- auth_model.SourceSettable
auth_model.RegisterableSource
auth.PasswordAuthenticator
}
diff --git a/services/auth/source/oauth2/source.go b/services/auth/source/oauth2/source.go
index 3454c9ad55..08837de377 100644
--- a/services/auth/source/oauth2/source.go
+++ b/services/auth/source/oauth2/source.go
@@ -10,6 +10,8 @@ import (
// Source holds configuration for the OAuth2 login source.
type Source struct {
+ auth.ConfigBase `json:"-"`
+
Provider string
ClientID string
ClientSecret string
@@ -25,10 +27,6 @@ type Source struct {
GroupTeamMap string
GroupTeamMapRemoval bool
RestrictedGroup string
- SkipLocalTwoFA bool `json:",omitempty"`
-
- // reference to the authSource
- authSource *auth.Source
}
// FromDB fills up an OAuth2Config from serialized format.
@@ -41,11 +39,6 @@ func (source *Source) ToDB() ([]byte, error) {
return json.Marshal(source)
}
-// SetAuthSource sets the related AuthSource
-func (source *Source) SetAuthSource(authSource *auth.Source) {
- source.authSource = authSource
-}
-
func init() {
auth.RegisterTypeConfig(auth.OAuth2, &Source{})
}
diff --git a/services/auth/source/oauth2/source_callout.go b/services/auth/source/oauth2/source_callout.go
index 8d70bee248..f09d25c772 100644
--- a/services/auth/source/oauth2/source_callout.go
+++ b/services/auth/source/oauth2/source_callout.go
@@ -13,7 +13,7 @@ import (
// Callout redirects request/response pair to authenticate against the provider
func (source *Source) Callout(request *http.Request, response http.ResponseWriter) error {
// not sure if goth is thread safe (?) when using multiple providers
- request.Header.Set(ProviderHeaderKey, source.authSource.Name)
+ request.Header.Set(ProviderHeaderKey, source.AuthSource.Name)
// don't use the default gothic begin handler to prevent issues when some error occurs
// normally the gothic library will write some custom stuff to the response instead of our own nice error page
@@ -33,7 +33,7 @@ func (source *Source) Callout(request *http.Request, response http.ResponseWrite
// this will trigger a new authentication request, but because we save it in the session we can use that
func (source *Source) Callback(request *http.Request, response http.ResponseWriter) (goth.User, error) {
// not sure if goth is thread safe (?) when using multiple providers
- request.Header.Set(ProviderHeaderKey, source.authSource.Name)
+ request.Header.Set(ProviderHeaderKey, source.AuthSource.Name)
gothRWMutex.RLock()
defer gothRWMutex.RUnlock()
diff --git a/services/auth/source/oauth2/source_register.go b/services/auth/source/oauth2/source_register.go
index 82a36acaa6..12da56c11b 100644
--- a/services/auth/source/oauth2/source_register.go
+++ b/services/auth/source/oauth2/source_register.go
@@ -9,13 +9,13 @@ import (
// RegisterSource causes an OAuth2 configuration to be registered
func (source *Source) RegisterSource() error {
- err := RegisterProviderWithGothic(source.authSource.Name, source)
- return wrapOpenIDConnectInitializeError(err, source.authSource.Name, source)
+ err := RegisterProviderWithGothic(source.AuthSource.Name, source)
+ return wrapOpenIDConnectInitializeError(err, source.AuthSource.Name, source)
}
// UnregisterSource causes an OAuth2 configuration to be unregistered
func (source *Source) UnregisterSource() error {
- RemoveProviderFromGothic(source.authSource.Name)
+ RemoveProviderFromGothic(source.AuthSource.Name)
return nil
}
diff --git a/services/auth/source/oauth2/source_sync.go b/services/auth/source/oauth2/source_sync.go
index 5e30313c8f..c2e3dfb1a8 100644
--- a/services/auth/source/oauth2/source_sync.go
+++ b/services/auth/source/oauth2/source_sync.go
@@ -18,27 +18,27 @@ import (
// Sync causes this OAuth2 source to synchronize its users with the db.
func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
- log.Trace("Doing: SyncExternalUsers[%s] %d", source.authSource.Name, source.authSource.ID)
+ log.Trace("Doing: SyncExternalUsers[%s] %d", source.AuthSource.Name, source.AuthSource.ID)
if !updateExisting {
- log.Info("SyncExternalUsers[%s] not running since updateExisting is false", source.authSource.Name)
+ log.Info("SyncExternalUsers[%s] not running since updateExisting is false", source.AuthSource.Name)
return nil
}
- provider, err := createProvider(source.authSource.Name, source)
+ provider, err := createProvider(source.AuthSource.Name, source)
if err != nil {
return err
}
if !provider.RefreshTokenAvailable() {
- log.Trace("SyncExternalUsers[%s] provider doesn't support refresh tokens, can't synchronize", source.authSource.Name)
+ log.Trace("SyncExternalUsers[%s] provider doesn't support refresh tokens, can't synchronize", source.AuthSource.Name)
return nil
}
opts := user_model.FindExternalUserOptions{
HasRefreshToken: true,
Expired: true,
- LoginSourceID: source.authSource.ID,
+ LoginSourceID: source.AuthSource.ID,
}
return user_model.IterateExternalLogin(ctx, opts, func(ctx context.Context, u *user_model.ExternalLoginUser) error {
@@ -77,7 +77,7 @@ func (source *Source) refresh(ctx context.Context, provider goth.Provider, u *us
// recognizes them as a valid user, they will be able to login
// via their provider and reactivate their account.
if shouldDisable {
- log.Info("SyncExternalUsers[%s] disabling user %d", source.authSource.Name, user.ID)
+ log.Info("SyncExternalUsers[%s] disabling user %d", source.AuthSource.Name, user.ID)
return db.WithTx(ctx, func(ctx context.Context) error {
if hasUser {
diff --git a/services/auth/source/oauth2/source_sync_test.go b/services/auth/source/oauth2/source_sync_test.go
index 08d841cc90..2927f3634b 100644
--- a/services/auth/source/oauth2/source_sync_test.go
+++ b/services/auth/source/oauth2/source_sync_test.go
@@ -18,19 +18,21 @@ func TestSource(t *testing.T) {
source := &Source{
Provider: "fake",
- authSource: &auth.Source{
- ID: 12,
- Type: auth.OAuth2,
- Name: "fake",
- IsActive: true,
- IsSyncEnabled: true,
+ ConfigBase: auth.ConfigBase{
+ AuthSource: &auth.Source{
+ ID: 12,
+ Type: auth.OAuth2,
+ Name: "fake",
+ IsActive: true,
+ IsSyncEnabled: true,
+ },
},
}
user := &user_model.User{
LoginName: "external",
LoginType: auth.OAuth2,
- LoginSource: source.authSource.ID,
+ LoginSource: source.AuthSource.ID,
Name: "test",
Email: "external@example.com",
}
@@ -47,7 +49,7 @@ func TestSource(t *testing.T) {
err = user_model.LinkExternalToUser(t.Context(), user, e)
assert.NoError(t, err)
- provider, err := createProvider(source.authSource.Name, source)
+ provider, err := createProvider(source.AuthSource.Name, source)
assert.NoError(t, err)
t.Run("refresh", func(t *testing.T) {
diff --git a/services/auth/source/pam/assert_interface_test.go b/services/auth/source/pam/assert_interface_test.go
index 8e7648b8d3..908d097d96 100644
--- a/services/auth/source/pam/assert_interface_test.go
+++ b/services/auth/source/pam/assert_interface_test.go
@@ -15,7 +15,6 @@ import (
type sourceInterface interface {
auth.PasswordAuthenticator
auth_model.Config
- auth_model.SourceSettable
}
var _ (sourceInterface) = &pam.Source{}
diff --git a/services/auth/source/pam/source.go b/services/auth/source/pam/source.go
index 96b182e185..d1db6db9b7 100644
--- a/services/auth/source/pam/source.go
+++ b/services/auth/source/pam/source.go
@@ -17,12 +17,10 @@ import (
// Source holds configuration for the PAM login source.
type Source struct {
- ServiceName string // pam service (e.g. system-auth)
- EmailDomain string
- SkipLocalTwoFA bool `json:",omitempty"` // Skip Local 2fa for users authenticated with this source
+ auth.ConfigBase `json:"-"`
- // reference to the authSource
- authSource *auth.Source
+ ServiceName string // pam service (e.g. system-auth)
+ EmailDomain string
}
// FromDB fills up a PAMConfig from serialized format.
@@ -35,11 +33,6 @@ func (source *Source) ToDB() ([]byte, error) {
return json.Marshal(source)
}
-// SetAuthSource sets the related AuthSource
-func (source *Source) SetAuthSource(authSource *auth.Source) {
- source.authSource = authSource
-}
-
func init() {
auth.RegisterTypeConfig(auth.PAM, &Source{})
}
diff --git a/services/auth/source/pam/source_authenticate.go b/services/auth/source/pam/source_authenticate.go
index 6fd02dc29f..db7c6aab96 100644
--- a/services/auth/source/pam/source_authenticate.go
+++ b/services/auth/source/pam/source_authenticate.go
@@ -56,7 +56,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
Email: email,
Passwd: password,
LoginType: auth.PAM,
- LoginSource: source.authSource.ID,
+ LoginSource: source.AuthSource.ID,
LoginName: userName, // This is what the user typed in
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
@@ -69,8 +69,3 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
return user, nil
}
-
-// IsSkipLocalTwoFA returns if this source should skip local 2fa for password authentication
-func (source *Source) IsSkipLocalTwoFA() bool {
- return source.SkipLocalTwoFA
-}
diff --git a/services/auth/source/smtp/assert_interface_test.go b/services/auth/source/smtp/assert_interface_test.go
index 6c9cde66e1..56edad0c71 100644
--- a/services/auth/source/smtp/assert_interface_test.go
+++ b/services/auth/source/smtp/assert_interface_test.go
@@ -18,7 +18,6 @@ type sourceInterface interface {
auth_model.SkipVerifiable
auth_model.HasTLSer
auth_model.UseTLSer
- auth_model.SourceSettable
}
var _ (sourceInterface) = &smtp.Source{}
diff --git a/services/auth/source/smtp/source.go b/services/auth/source/smtp/source.go
index 2a648e421e..2ae81ad4f2 100644
--- a/services/auth/source/smtp/source.go
+++ b/services/auth/source/smtp/source.go
@@ -17,6 +17,8 @@ import (
// Source holds configuration for the SMTP login source.
type Source struct {
+ auth.ConfigBase `json:"-"`
+
Auth string
Host string
Port int
@@ -25,10 +27,6 @@ type Source struct {
SkipVerify bool
HeloHostname string
DisableHelo bool
- SkipLocalTwoFA bool `json:",omitempty"`
-
- // reference to the authSource
- authSource *auth.Source
}
// FromDB fills up an SMTPConfig from serialized format.
@@ -56,11 +54,6 @@ func (source *Source) UseTLS() bool {
return source.ForceSMTPS || source.Port == 465
}
-// SetAuthSource sets the related AuthSource
-func (source *Source) SetAuthSource(authSource *auth.Source) {
- source.authSource = authSource
-}
-
func init() {
auth.RegisterTypeConfig(auth.SMTP, &Source{})
}
diff --git a/services/auth/source/smtp/source_authenticate.go b/services/auth/source/smtp/source_authenticate.go
index b2e94933a6..b8e668f5f9 100644
--- a/services/auth/source/smtp/source_authenticate.go
+++ b/services/auth/source/smtp/source_authenticate.go
@@ -72,7 +72,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
Email: userName,
Passwd: password,
LoginType: auth_model.SMTP,
- LoginSource: source.authSource.ID,
+ LoginSource: source.AuthSource.ID,
LoginName: userName,
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
@@ -85,8 +85,3 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
return user, nil
}
-
-// IsSkipLocalTwoFA returns if this source should skip local 2fa for password authentication
-func (source *Source) IsSkipLocalTwoFA() bool {
- return source.SkipLocalTwoFA
-}
diff --git a/services/auth/source/sspi/source.go b/services/auth/source/sspi/source.go
index bdd6ef451c..3b7b5cb033 100644
--- a/services/auth/source/sspi/source.go
+++ b/services/auth/source/sspi/source.go
@@ -17,6 +17,8 @@ import (
// Source holds configuration for SSPI single sign-on.
type Source struct {
+ auth.ConfigBase `json:"-"`
+
AutoCreateUsers bool
AutoActivateUsers bool
StripDomainNames bool
diff --git a/services/context/context.go b/services/context/context.go
index 3c0ac54fc1..7f623f85bd 100644
--- a/services/context/context.go
+++ b/services/context/context.go
@@ -196,6 +196,8 @@ func Contexter() func(next http.Handler) http.Handler {
ctx.Data["SystemConfig"] = setting.Config()
+ ctx.Data["ShowTwoFactorRequiredMessage"] = ctx.DoerNeedTwoFactorAuth()
+
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
ctx.Data["DisableStars"] = setting.Repository.DisableStars
@@ -209,6 +211,13 @@ func Contexter() func(next http.Handler) http.Handler {
}
}
+func (ctx *Context) DoerNeedTwoFactorAuth() bool {
+ if !setting.TwoFactorAuthEnforced {
+ return false
+ }
+ return ctx.Session.Get(session.KeyUserHasTwoFactorAuth) == false
+}
+
// HasError returns true if error occurs in form validation.
// Attention: this function changes ctx.Data and ctx.Flash
// If HasError is called, then before Redirect, the error message should be stored by ctx.Flash.Error(ctx.GetErrMsg()) again.
diff --git a/services/context/repo.go b/services/context/repo.go
index 6f5c772f5e..127d313258 100644
--- a/services/context/repo.go
+++ b/services/context/repo.go
@@ -340,10 +340,14 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
return
}
- ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
- if err != nil {
- ctx.ServerError("GetUserRepoPermission", err)
- return
+ if ctx.DoerNeedTwoFactorAuth() {
+ ctx.Repo.Permission = access_model.PermissionNoAccess()
+ } else {
+ ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
+ if err != nil {
+ ctx.ServerError("GetUserRepoPermission", err)
+ return
+ }
}
if !ctx.Repo.Permission.HasAnyUnitAccessOrPublicAccess() && !canWriteAsMaintainer(ctx) {
diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go
index c9f3182b3a..a8f97572b1 100644
--- a/services/forms/auth_form.go
+++ b/services/forms/auth_form.go
@@ -14,9 +14,11 @@ import (
// AuthenticationForm form for authentication
type AuthenticationForm struct {
- ID int64
- Type int `binding:"Range(2,7)"`
- Name string `binding:"Required;MaxSize(30)"`
+ ID int64
+ Type int `binding:"Range(2,7)"`
+ Name string `binding:"Required;MaxSize(30)"`
+ TwoFactorPolicy string
+
Host string
Port int
BindDN string
@@ -74,7 +76,6 @@ type AuthenticationForm struct {
Oauth2RestrictedGroup string
Oauth2GroupTeamMap string `binding:"ValidGroupTeamMap"`
Oauth2GroupTeamMapRemoval bool
- SkipLocalTwoFA bool
SSPIAutoCreateUsers bool
SSPIAutoActivateUsers bool
SSPIStripDomainNames bool
diff --git a/services/issue/pull.go b/services/issue/pull.go
index c9d32000af..3543b05b18 100644
--- a/services/issue/pull.go
+++ b/services/issue/pull.go
@@ -48,10 +48,6 @@ func IsCodeOwnerFile(f string) bool {
}
func PullRequestCodeOwnersReview(ctx context.Context, pr *issues_model.PullRequest) ([]*ReviewRequestNotifier, error) {
- return PullRequestCodeOwnersReviewSpecialCommits(ctx, pr, "", "") // no commit is provided, then it uses PR's base&head branch
-}
-
-func PullRequestCodeOwnersReviewSpecialCommits(ctx context.Context, pr *issues_model.PullRequest, startCommitID, endCommitID string) ([]*ReviewRequestNotifier, error) {
if err := pr.LoadIssue(ctx); err != nil {
return nil, err
}
@@ -100,19 +96,15 @@ func PullRequestCodeOwnersReviewSpecialCommits(ctx context.Context, pr *issues_m
return nil, nil
}
- if startCommitID == "" && endCommitID == "" {
- // get the mergebase
- mergeBase, err := getMergeBase(repo, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName())
- if err != nil {
- return nil, err
- }
- startCommitID = mergeBase
- endCommitID = pr.GetGitRefName()
+ // get the mergebase
+ mergeBase, err := getMergeBase(repo, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName())
+ if err != nil {
+ return nil, err
}
// https://github.com/go-gitea/gitea/issues/29763, we need to get the files changed
// between the merge base and the head commit but not the base branch and the head commit
- changedFiles, err := repo.GetFilesChangedBetween(startCommitID, endCommitID)
+ changedFiles, err := repo.GetFilesChangedBetween(mergeBase, pr.GetGitRefName())
if err != nil {
return nil, err
}
@@ -138,8 +130,23 @@ func PullRequestCodeOwnersReviewSpecialCommits(ctx context.Context, pr *issues_m
return nil, err
}
+ // load all reviews from database
+ latestReivews, _, err := issues_model.GetReviewsByIssueID(ctx, pr.IssueID)
+ if err != nil {
+ return nil, err
+ }
+
+ contain := func(list issues_model.ReviewList, u *user_model.User) bool {
+ for _, review := range list {
+ if review.ReviewerTeamID == 0 && review.ReviewerID == u.ID {
+ return true
+ }
+ }
+ return false
+ }
+
for _, u := range uniqUsers {
- if u.ID != issue.Poster.ID {
+ if u.ID != issue.Poster.ID && !contain(latestReivews, u) {
comment, err := issues_model.AddReviewRequest(ctx, issue, u, issue.Poster)
if err != nil {
log.Warn("Failed add assignee user: %s to PR review: %s#%d, error: %s", u.Name, pr.BaseRepo.Name, pr.ID, err)
@@ -155,6 +162,7 @@ func PullRequestCodeOwnersReviewSpecialCommits(ctx context.Context, pr *issues_m
})
}
}
+
for _, t := range uniqTeams {
comment, err := issues_model.AddTeamReviewRequest(ctx, issue, t, issue.Poster)
if err != nil {
diff --git a/services/pull/pull.go b/services/pull/pull.go
index e3053409b2..81be797832 100644
--- a/services/pull/pull.go
+++ b/services/pull/pull.go
@@ -463,12 +463,7 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) {
}
if !pr.IsWorkInProgress(ctx) {
- var reviewNotifiers []*issue_service.ReviewRequestNotifier
- if opts.IsForcePush {
- reviewNotifiers, err = issue_service.PullRequestCodeOwnersReview(ctx, pr)
- } else {
- reviewNotifiers, err = issue_service.PullRequestCodeOwnersReviewSpecialCommits(ctx, pr, opts.OldCommitID, opts.NewCommitID)
- }
+ reviewNotifiers, err := issue_service.PullRequestCodeOwnersReview(ctx, pr)
if err != nil {
log.Error("PullRequestCodeOwnersReview: %v", err)
}
diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl
index 660f0d0881..15683307ed 100644
--- a/templates/admin/auth/edit.tmpl
+++ b/templates/admin/auth/edit.tmpl
@@ -17,6 +17,13 @@
<label for="auth_name">{{ctx.Locale.Tr "admin.auths.auth_name"}}</label>
<input id="auth_name" name="name" value="{{.Source.Name}}" autofocus required>
</div>
+ <div class="inline field">
+ <div class="ui checkbox">
+ <label ><strong>{{ctx.Locale.Tr "admin.auths.skip_local_two_fa"}}</strong></label>
+ <input name="two_factor_policy" type="checkbox" value="skip" {{if eq .Source.TwoFactorPolicy "skip"}}checked{{end}}>
+ <p class="help">{{ctx.Locale.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
+ </div>
+ </div>
<!-- LDAP and DLDAP -->
{{if or .Source.IsLDAP .Source.IsDLDAP}}
@@ -159,13 +166,6 @@
</div>
</div>
{{end}}
- <div class="optional field">
- <div class="ui checkbox">
- <label for="skip_local_two_fa"><strong>{{ctx.Locale.Tr "admin.auths.skip_local_two_fa"}}</strong></label>
- <input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}>
- <p class="help">{{ctx.Locale.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
- </div>
- </div>
<div class="inline field">
<div class="ui checkbox">
<label for="allow_deactivate_all"><strong>{{ctx.Locale.Tr "admin.auths.allow_deactivate_all"}}</strong></label>
@@ -227,13 +227,6 @@
<input id="allowed_domains" name="allowed_domains" value="{{$cfg.AllowedDomains}}">
<p class="help">{{ctx.Locale.Tr "admin.auths.allowed_domains_helper"}}</p>
</div>
- <div class="optional field">
- <div class="ui checkbox">
- <label for="skip_local_two_fa"><strong>{{ctx.Locale.Tr "admin.auths.skip_local_two_fa"}}</strong></label>
- <input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}>
- <p class="help">{{ctx.Locale.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
- </div>
- </div>
{{end}}
<!-- PAM -->
@@ -247,13 +240,6 @@
<label for="pam_email_domain">{{ctx.Locale.Tr "admin.auths.pam_email_domain"}}</label>
<input id="pam_email_domain" name="pam_email_domain" value="{{$cfg.EmailDomain}}">
</div>
- <div class="optional field">
- <div class="ui checkbox">
- <label for="skip_local_two_fa"><strong>{{ctx.Locale.Tr "admin.auths.skip_local_two_fa"}}</strong></label>
- <input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}>
- <p class="help">{{ctx.Locale.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
- </div>
- </div>
{{end}}
<!-- OAuth2 -->
@@ -288,13 +274,6 @@
<label for="open_id_connect_auto_discovery_url">{{ctx.Locale.Tr "admin.auths.openIdConnectAutoDiscoveryURL"}}</label>
<input id="open_id_connect_auto_discovery_url" name="open_id_connect_auto_discovery_url" value="{{$cfg.OpenIDConnectAutoDiscoveryURL}}">
</div>
- <div class="optional field">
- <div class="ui checkbox">
- <label for="skip_local_two_fa"><strong>{{ctx.Locale.Tr "admin.auths.skip_local_two_fa"}}</strong></label>
- <input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}>
- <p class="help">{{ctx.Locale.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
- </div>
- </div>
<div class="oauth2_use_custom_url inline field">
<div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "admin.auths.oauth2_use_custom_url"}}</strong></label>
diff --git a/templates/base/alert.tmpl b/templates/base/alert.tmpl
index 3f6d77a645..5ebe191771 100644
--- a/templates/base/alert.tmpl
+++ b/templates/base/alert.tmpl
@@ -18,3 +18,8 @@
<p>{{.Flash.WarningMsg | SanitizeHTML}}</p>
</div>
{{- end -}}
+{{- if .ShowTwoFactorRequiredMessage -}}
+<div class="ui negative message flash-message flash-error">
+ <p><a href="{{AppSubUrl}}/user/settings/security/two_factor/enroll">{{ctx.Locale.Tr "auth.twofa_required"}}</a></p>
+</div>
+{{- end -}}
diff --git a/templates/repo/branch_dropdown.tmpl b/templates/repo/branch_dropdown.tmpl
index 261d0ea409..36dc047c23 100644
--- a/templates/repo/branch_dropdown.tmpl
+++ b/templates/repo/branch_dropdown.tmpl
@@ -47,16 +47,20 @@ Search "repo/branch_dropdown" in the template directory to find all occurrences.
>
{{/* show dummy elements before Vue componment is mounted, this code must match the code in BranchTagSelector.vue */}}
<div class="ui dropdown custom branch-selector-dropdown ellipsis-text-items">
- <div class="ui button branch-dropdown-button">
+ <div class="ui compact button branch-dropdown-button">
<span class="flex-text-block gt-ellipsis">
- {{if not .DropdownFixedText}}
- {{if .ShowTabTags}}
+ {{if .DropdownFixedText}}
+ {{.DropdownFixedText}}
+ {{else}}
+ {{if eq .CurrentRefType "tag"}}
{{svg "octicon-tag"}}
- {{else if .ShowTabBranches}}
+ {{else if eq .CurrentRefType "branch"}}
{{svg "octicon-git-branch"}}
+ {{else}}
+ {{svg "octicon-git-commit"}}
{{end}}
+ <strong class="tw-inline-block gt-ellipsis">{{.CurrentRefShortName}}</strong>
{{end}}
- <strong class="tw-ml-2 tw-inline-block gt-ellipsis">{{Iif .DropdownFixedText .SelectedRefShortName}}</strong>
</span>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
</div>
diff --git a/templates/repo/wiki/revision.tmpl b/templates/repo/wiki/revision.tmpl
index f305238fc9..108e378937 100644
--- a/templates/repo/wiki/revision.tmpl
+++ b/templates/repo/wiki/revision.tmpl
@@ -14,7 +14,7 @@
</div>
</div>
</div>
- <div>
+ <div class="flex-text-block">
{{template "repo/clone_panel" .}}
</div>
</div>
diff --git a/templates/status/404.tmpl b/templates/status/404.tmpl
index 6cfc88a0d7..e83dcf463b 100644
--- a/templates/status/404.tmpl
+++ b/templates/status/404.tmpl
@@ -2,6 +2,7 @@
<div role="main" aria-label="{{.Title}}" class="page-content {{if .IsRepo}}repository{{end}}">
{{if .IsRepo}}{{template "repo/header" .}}{{end}}
<div class="ui container">
+ {{template "base/alert" .}}
<div class="status-page-error">
<div class="status-page-error-title">404 Not Found</div>
<div class="tw-text-center">
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 0a9432fc9d..8758b5c0a1 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -21350,7 +21350,18 @@
"items": {
"type": "string"
},
- "x-go-name": "Scopes"
+ "x-go-name": "Scopes",
+ "example": [
+ "all",
+ "read:activitypub",
+ "read:issue",
+ "write:misc",
+ "read:notification",
+ "read:organization",
+ "read:package",
+ "read:repository",
+ "read:user"
+ ]
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
diff --git a/tests/integration/api_actions_artifact_v4_test.go b/tests/integration/api_actions_artifact_v4_test.go
index b6dfa6e799..3db8bbb82e 100644
--- a/tests/integration/api_actions_artifact_v4_test.go
+++ b/tests/integration/api_actions_artifact_v4_test.go
@@ -557,6 +557,26 @@ func TestActionsArtifactV4Delete(t *testing.T) {
var deleteResp actions.DeleteArtifactResponse
protojson.Unmarshal(resp.Body.Bytes(), &deleteResp)
assert.True(t, deleteResp.Ok)
+
+ // confirm artifact is no longer accessible by GetSignedArtifactURL
+ req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/GetSignedArtifactURL", toProtoJSON(&actions.GetSignedArtifactURLRequest{
+ Name: "artifact-v4-download",
+ WorkflowRunBackendId: "792",
+ WorkflowJobRunBackendId: "193",
+ })).
+ AddTokenAuth(token)
+ _ = MakeRequest(t, req, http.StatusNotFound)
+
+ // confirm artifact is no longer enumerateable by ListArtifacts and returns length == 0 without error
+ req = NewRequestWithBody(t, "POST", "/twirp/github.actions.results.api.v1.ArtifactService/ListArtifacts", toProtoJSON(&actions.ListArtifactsRequest{
+ NameFilter: wrapperspb.String("artifact-v4-download"),
+ WorkflowRunBackendId: "792",
+ WorkflowJobRunBackendId: "193",
+ })).AddTokenAuth(token)
+ resp = MakeRequest(t, req, http.StatusOK)
+ var listResp actions.ListArtifactsResponse
+ protojson.Unmarshal(resp.Body.Bytes(), &listResp)
+ assert.Empty(t, listResp.Artifacts)
}
func TestActionsArtifactV4DeletePublicApi(t *testing.T) {
diff --git a/tests/integration/api_twofa_test.go b/tests/integration/api_twofa_test.go
index b8052c0812..938feba61a 100644
--- a/tests/integration/api_twofa_test.go
+++ b/tests/integration/api_twofa_test.go
@@ -94,7 +94,7 @@ func TestBasicAuthWithWebAuthn(t *testing.T) {
}
var userParsed userResponse
DecodeJSON(t, resp, &userParsed)
- assert.Equal(t, "Basic authorization is not allowed while webAuthn enrolled", userParsed.Message)
+ assert.Equal(t, "basic authorization is not allowed while WebAuthn enrolled", userParsed.Message)
// user32 has webauthn enrolled, he can't request git protocol with basic auth
req = NewRequest(t, "GET", "/user2/repo1/info/refs")
diff --git a/web_src/css/base.css b/web_src/css/base.css
index bf7639859d..dc79f2f322 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -1150,6 +1150,7 @@ table th[data-sortt-desc] .svg {
min-width: 0;
}
+.ui.dropdown > .ui.button,
.flex-text-block > .ui.button,
.flex-text-inline > .ui.button {
margin: 0; /* fomantic buttons have default margin, when we use them in a flex container with gap, we do not need these margins */
diff --git a/web_src/js/components/RepoBranchTagSelector.vue b/web_src/js/components/RepoBranchTagSelector.vue
index aaef8045a0..8e3a29a0e0 100644
--- a/web_src/js/components/RepoBranchTagSelector.vue
+++ b/web_src/js/components/RepoBranchTagSelector.vue
@@ -222,7 +222,8 @@ export default defineComponent({
<template v-if="dropdownFixedText">{{ dropdownFixedText }}</template>
<template v-else>
<svg-icon v-if="currentRefType === 'tag'" name="octicon-tag"/>
- <svg-icon v-else name="octicon-git-branch"/>
+ <svg-icon v-else-if="currentRefType === 'branch'" name="octicon-git-branch"/>
+ <svg-icon v-else name="octicon-git-commit"/>
<strong ref="dropdownRefName" class="tw-inline-block gt-ellipsis">{{ currentRefShortName }}</strong>
</template>
</span>
diff --git a/web_src/js/components/RepoContributors.vue b/web_src/js/components/RepoContributors.vue
index 969a869e0d..1006ea30bb 100644
--- a/web_src/js/components/RepoContributors.vue
+++ b/web_src/js/components/RepoContributors.vue
@@ -354,7 +354,7 @@ export default defineComponent({
<div>
<!-- Contribution type -->
<div class="ui floating dropdown jump" id="repo-contributors">
- <div class="ui basic compact button tw-mr-0">
+ <div class="ui basic compact button">
<span class="not-mobile">{{ locale.filterLabel }}</span> <strong>{{ locale.contributionType[type] }}</strong>
<svg-icon name="octicon-triangle-down" :size="14"/>
</div>