mirror of
https://github.com/go-gitea/gitea.git
synced 2025-05-05 15:32:53 +00:00
Enforce two-factor auth (2FA: TOTP or WebAuthn) (#34187)
Fix #880 Design: 1. A global setting `security.TWO_FACTOR_AUTH`. * To support org-level config, we need to introduce a better "owner setting" system first (in the future) 2. A user without 2FA can login and may explore, but can NOT read or write to any repositories via API/web. 3. Keep things as simple as possible. * This option only aggressively suggest users to enable their 2FA at the moment, it does NOT guarantee that users must have 2FA before all other operations, it should be good enough for real world use cases. * Some details and tests could be improved in the future since this change only adds a check and seems won't affect too much. --------- Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
parent
4ed07244b9
commit
0148d03f21
@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/auth"
|
"code.gitea.io/gitea/models/auth"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/auth/source/ldap"
|
"code.gitea.io/gitea/services/auth/source/ldap"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -210,8 +211,8 @@ func newAuthService() *authService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseAuthSource assigns values on authSource according to command line flags.
|
// parseAuthSourceLdap assigns values on authSource according to command line flags.
|
||||||
func parseAuthSource(c *cli.Context, authSource *auth.Source) {
|
func parseAuthSourceLdap(c *cli.Context, authSource *auth.Source) {
|
||||||
if c.IsSet("name") {
|
if c.IsSet("name") {
|
||||||
authSource.Name = c.String("name")
|
authSource.Name = c.String("name")
|
||||||
}
|
}
|
||||||
@ -227,6 +228,7 @@ func parseAuthSource(c *cli.Context, authSource *auth.Source) {
|
|||||||
if c.IsSet("disable-synchronize-users") {
|
if c.IsSet("disable-synchronize-users") {
|
||||||
authSource.IsSyncEnabled = !c.Bool("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.
|
// 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") {
|
if c.IsSet("allow-deactivate-all") {
|
||||||
config.AllowDeactivateAll = c.Bool("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") {
|
if c.IsSet("enable-groups") {
|
||||||
config.GroupsEnabled = c.Bool("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 {
|
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -398,7 +397,7 @@ func (a *authService) updateLdapBindDn(c *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
parseAuthSource(c, authSource)
|
parseAuthSourceLdap(c, authSource)
|
||||||
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
||||||
return err
|
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 {
|
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -449,7 +448,7 @@ func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
parseAuthSource(c, authSource)
|
parseAuthSourceLdap(c, authSource)
|
||||||
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -156,7 +157,6 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source {
|
|||||||
OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"),
|
OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"),
|
||||||
CustomURLMapping: customURLMapping,
|
CustomURLMapping: customURLMapping,
|
||||||
IconURL: c.String("icon-url"),
|
IconURL: c.String("icon-url"),
|
||||||
SkipLocalTwoFA: c.Bool("skip-local-2fa"),
|
|
||||||
Scopes: c.StringSlice("scopes"),
|
Scopes: c.StringSlice("scopes"),
|
||||||
RequiredClaimName: c.String("required-claim-name"),
|
RequiredClaimName: c.String("required-claim-name"),
|
||||||
RequiredClaimValue: c.String("required-claim-value"),
|
RequiredClaimValue: c.String("required-claim-value"),
|
||||||
@ -189,6 +189,7 @@ func runAddOauth(c *cli.Context) error {
|
|||||||
Name: c.String("name"),
|
Name: c.String("name"),
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
Cfg: config,
|
Cfg: config,
|
||||||
|
TwoFactorPolicy: util.Iif(c.Bool("skip-local-2fa"), "skip", ""),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,6 +295,6 @@ func runUpdateOauth(c *cli.Context) error {
|
|||||||
|
|
||||||
oAuth2Config.CustomURLMapping = customURLMapping
|
oAuth2Config.CustomURLMapping = customURLMapping
|
||||||
source.Cfg = oAuth2Config
|
source.Cfg = oAuth2Config
|
||||||
|
source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
|
||||||
return auth_model.UpdateSource(ctx, source)
|
return auth_model.UpdateSource(ctx, source)
|
||||||
}
|
}
|
||||||
|
@ -117,9 +117,6 @@ func parseSMTPConfig(c *cli.Context, conf *smtp.Source) error {
|
|||||||
if c.IsSet("disable-helo") {
|
if c.IsSet("disable-helo") {
|
||||||
conf.DisableHelo = c.Bool("disable-helo")
|
conf.DisableHelo = c.Bool("disable-helo")
|
||||||
}
|
}
|
||||||
if c.IsSet("skip-local-2fa") {
|
|
||||||
conf.SkipLocalTwoFA = c.Bool("skip-local-2fa")
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,6 +157,7 @@ func runAddSMTP(c *cli.Context) error {
|
|||||||
Name: c.String("name"),
|
Name: c.String("name"),
|
||||||
IsActive: active,
|
IsActive: active,
|
||||||
Cfg: &smtpConfig,
|
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.Cfg = smtpConfig
|
||||||
|
source.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
|
||||||
return auth_model.UpdateSource(ctx, source)
|
return auth_model.UpdateSource(ctx, source)
|
||||||
}
|
}
|
||||||
|
@ -524,6 +524,10 @@ INTERNAL_TOKEN =
|
|||||||
;;
|
;;
|
||||||
;; On user registration, record the IP address and user agent of the user to help identify potential abuse.
|
;; On user registration, record the IP address and user agent of the user to help identify potential abuse.
|
||||||
;; RECORD_USER_SIGNUP_METADATA = false
|
;; 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 =
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -58,6 +58,15 @@ var Names = map[Type]string{
|
|||||||
// Config represents login config as far as the db is concerned
|
// Config represents login config as far as the db is concerned
|
||||||
type Config interface {
|
type Config interface {
|
||||||
convert.Conversion
|
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
|
// SkipVerifiable configurations provide a IsSkipVerify to check if SkipVerify is set
|
||||||
@ -104,11 +113,6 @@ 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.
|
// Source represents an external way for authorizing users.
|
||||||
type Source struct {
|
type Source struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
@ -116,7 +120,8 @@ type Source struct {
|
|||||||
Name string `xorm:"UNIQUE"`
|
Name string `xorm:"UNIQUE"`
|
||||||
IsActive bool `xorm:"INDEX NOT NULL DEFAULT false"`
|
IsActive bool `xorm:"INDEX NOT NULL DEFAULT false"`
|
||||||
IsSyncEnabled bool `xorm:"INDEX NOT NULL DEFAULT false"`
|
IsSyncEnabled bool `xorm:"INDEX NOT NULL DEFAULT false"`
|
||||||
Cfg convert.Conversion `xorm:"TEXT"`
|
TwoFactorPolicy string `xorm:"two_factor_policy NOT NULL DEFAULT ''"`
|
||||||
|
Cfg Config `xorm:"TEXT"`
|
||||||
|
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||||
@ -140,9 +145,7 @@ func (source *Source) BeforeSet(colName string, val xorm.Cell) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
source.Cfg = constructor()
|
source.Cfg = constructor()
|
||||||
if settable, ok := source.Cfg.(SourceSettable); ok {
|
source.Cfg.SetAuthSource(source)
|
||||||
settable.SetAuthSource(source)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,6 +203,10 @@ func (source *Source) SkipVerify() bool {
|
|||||||
return ok && skipVerifiable.IsSkipVerify()
|
return ok && skipVerifiable.IsSkipVerify()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (source *Source) TwoFactorShouldSkip() bool {
|
||||||
|
return source.TwoFactorPolicy == "skip"
|
||||||
|
}
|
||||||
|
|
||||||
// CreateSource inserts a AuthSource in the DB if not already
|
// CreateSource inserts a AuthSource in the DB if not already
|
||||||
// existing with the given name.
|
// existing with the given name.
|
||||||
func CreateSource(ctx context.Context, source *Source) error {
|
func CreateSource(ctx context.Context, source *Source) error {
|
||||||
@ -223,9 +230,7 @@ func CreateSource(ctx context.Context, source *Source) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if settable, ok := source.Cfg.(SourceSettable); ok {
|
source.Cfg.SetAuthSource(source)
|
||||||
settable.SetAuthSource(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
registerableSource, ok := source.Cfg.(RegisterableSource)
|
registerableSource, ok := source.Cfg.(RegisterableSource)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -320,9 +325,7 @@ func UpdateSource(ctx context.Context, source *Source) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if settable, ok := source.Cfg.(SourceSettable); ok {
|
source.Cfg.SetAuthSource(source)
|
||||||
settable.SetAuthSource(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
registerableSource, ok := source.Cfg.(RegisterableSource)
|
registerableSource, ok := source.Cfg.(RegisterableSource)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -19,6 +19,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type TestSource struct {
|
type TestSource struct {
|
||||||
|
auth_model.ConfigBase
|
||||||
|
|
||||||
Provider string
|
Provider string
|
||||||
ClientID string
|
ClientID string
|
||||||
ClientSecret string
|
ClientSecret string
|
||||||
|
@ -164,3 +164,13 @@ func DeleteTwoFactorByID(ctx context.Context, id, userID int64) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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)
|
||||||
|
}
|
||||||
|
@ -381,6 +381,7 @@ func prepareMigrationTasks() []*migration {
|
|||||||
newMigration(317, "Add new index for action for heatmap", v1_24.AddNewIndexForUserDashboard),
|
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(318, "Add anonymous_access_mode for repo_unit", v1_24.AddRepoUnitAnonymousAccessMode),
|
||||||
newMigration(319, "Add ExclusiveOrder to Label table", v1_24.AddExclusiveOrderColumnToLabelTable),
|
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
|
return preparedMigrations
|
||||||
}
|
}
|
||||||
|
57
models/migrations/v1_24/v320.go
Normal file
57
models/migrations/v1_24/v320.go
Normal file
@ -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
|
||||||
|
}
|
@ -522,3 +522,7 @@ func CheckRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *u
|
|||||||
|
|
||||||
return perm.CanRead(unitType)
|
return perm.CanRead(unitType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PermissionNoAccess() Permission {
|
||||||
|
return Permission{AccessMode: perm_model.AccessModeNone}
|
||||||
|
}
|
||||||
|
11
modules/session/key.go
Normal file
11
modules/session/key.go
Normal file
@ -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"
|
||||||
|
)
|
@ -39,6 +39,7 @@ var (
|
|||||||
CSRFCookieName = "_csrf"
|
CSRFCookieName = "_csrf"
|
||||||
CSRFCookieHTTPOnly = true
|
CSRFCookieHTTPOnly = true
|
||||||
RecordUserSignupMetadata = false
|
RecordUserSignupMetadata = false
|
||||||
|
TwoFactorAuthEnforced = false
|
||||||
)
|
)
|
||||||
|
|
||||||
// loadSecret load the secret from ini by uriKey or verbatimKey, only one of them could be set
|
// 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)
|
PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false)
|
||||||
SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20)
|
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")
|
InternalToken = loadSecret(sec, "INTERNAL_TOKEN_URI", "INTERNAL_TOKEN")
|
||||||
if InstallLock && InternalToken == "" {
|
if InstallLock && InternalToken == "" {
|
||||||
// if Gitea has been installed but the InternalToken hasn't been generated (upgrade from an old release), we should generate
|
// if Gitea has been installed but the InternalToken hasn't been generated (upgrade from an old release), we should generate
|
||||||
|
@ -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_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_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_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_userpass = Sign In
|
||||||
login_openid = OpenID
|
login_openid = OpenID
|
||||||
oauth_signup_tab = Register New Account
|
oauth_signup_tab = Register New Account
|
||||||
|
@ -64,6 +64,7 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
gocontext "context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -210,6 +211,14 @@ func repoAssignment() func(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Repo.Permission.SetUnitsWithDefaultAccessMode(ctx.Repo.Repository.Units, ctx.Repo.Permission.AccessMode)
|
ctx.Repo.Permission.SetUnitsWithDefaultAccessMode(ctx.Repo.Repository.Units, ctx.Repo.Permission.AccessMode)
|
||||||
|
} else {
|
||||||
|
needTwoFactor, err := doerNeedTwoFactorAuth(ctx, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if needTwoFactor {
|
||||||
|
ctx.Repo.Permission = access_model.PermissionNoAccess()
|
||||||
} else {
|
} else {
|
||||||
ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -217,6 +226,7 @@ func repoAssignment() func(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !ctx.Repo.Permission.HasAnyUnitAccess() {
|
if !ctx.Repo.Permission.HasAnyUnitAccess() {
|
||||||
ctx.APIErrorNotFound()
|
ctx.APIErrorNotFound()
|
||||||
@ -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) {
|
func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext) {
|
||||||
return func(ctx *context.APIContext) {
|
return func(ctx *context.APIContext) {
|
||||||
if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() {
|
if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() {
|
||||||
|
@ -28,8 +28,6 @@ import (
|
|||||||
"code.gitea.io/gitea/services/auth/source/sspi"
|
"code.gitea.io/gitea/services/auth/source/sspi"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
|
|
||||||
"xorm.io/xorm/convert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -149,7 +147,6 @@ func parseLDAPConfig(form forms.AuthenticationForm) *ldap.Source {
|
|||||||
RestrictedFilter: form.RestrictedFilter,
|
RestrictedFilter: form.RestrictedFilter,
|
||||||
AllowDeactivateAll: form.AllowDeactivateAll,
|
AllowDeactivateAll: form.AllowDeactivateAll,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
SkipLocalTwoFA: form.SkipLocalTwoFA,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +160,6 @@ func parseSMTPConfig(form forms.AuthenticationForm) *smtp.Source {
|
|||||||
SkipVerify: form.SkipVerify,
|
SkipVerify: form.SkipVerify,
|
||||||
HeloHostname: form.HeloHostname,
|
HeloHostname: form.HeloHostname,
|
||||||
DisableHelo: form.DisableHelo,
|
DisableHelo: form.DisableHelo,
|
||||||
SkipLocalTwoFA: form.SkipLocalTwoFA,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,7 +194,6 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
|
|||||||
Scopes: scopes,
|
Scopes: scopes,
|
||||||
RequiredClaimName: form.Oauth2RequiredClaimName,
|
RequiredClaimName: form.Oauth2RequiredClaimName,
|
||||||
RequiredClaimValue: form.Oauth2RequiredClaimValue,
|
RequiredClaimValue: form.Oauth2RequiredClaimValue,
|
||||||
SkipLocalTwoFA: form.SkipLocalTwoFA,
|
|
||||||
GroupClaimName: form.Oauth2GroupClaimName,
|
GroupClaimName: form.Oauth2GroupClaimName,
|
||||||
RestrictedGroup: form.Oauth2RestrictedGroup,
|
RestrictedGroup: form.Oauth2RestrictedGroup,
|
||||||
AdminGroup: form.Oauth2AdminGroup,
|
AdminGroup: form.Oauth2AdminGroup,
|
||||||
@ -252,7 +247,7 @@ func NewAuthSourcePost(ctx *context.Context) {
|
|||||||
ctx.Data["SSPIDefaultLanguage"] = ""
|
ctx.Data["SSPIDefaultLanguage"] = ""
|
||||||
|
|
||||||
hasTLS := false
|
hasTLS := false
|
||||||
var config convert.Conversion
|
var config auth.Config
|
||||||
switch auth.Type(form.Type) {
|
switch auth.Type(form.Type) {
|
||||||
case auth.LDAP, auth.DLDAP:
|
case auth.LDAP, auth.DLDAP:
|
||||||
config = parseLDAPConfig(form)
|
config = parseLDAPConfig(form)
|
||||||
@ -264,7 +259,6 @@ func NewAuthSourcePost(ctx *context.Context) {
|
|||||||
config = &pam_service.Source{
|
config = &pam_service.Source{
|
||||||
ServiceName: form.PAMServiceName,
|
ServiceName: form.PAMServiceName,
|
||||||
EmailDomain: form.PAMEmailDomain,
|
EmailDomain: form.PAMEmailDomain,
|
||||||
SkipLocalTwoFA: form.SkipLocalTwoFA,
|
|
||||||
}
|
}
|
||||||
case auth.OAuth2:
|
case auth.OAuth2:
|
||||||
config = parseOAuth2Config(form)
|
config = parseOAuth2Config(form)
|
||||||
@ -306,6 +300,7 @@ func NewAuthSourcePost(ctx *context.Context) {
|
|||||||
Name: form.Name,
|
Name: form.Name,
|
||||||
IsActive: form.IsActive,
|
IsActive: form.IsActive,
|
||||||
IsSyncEnabled: form.IsSyncEnabled,
|
IsSyncEnabled: form.IsSyncEnabled,
|
||||||
|
TwoFactorPolicy: form.TwoFactorPolicy,
|
||||||
Cfg: config,
|
Cfg: config,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
if auth.IsErrSourceAlreadyExist(err) {
|
if auth.IsErrSourceAlreadyExist(err) {
|
||||||
@ -384,7 +379,7 @@ func EditAuthSourcePost(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var config convert.Conversion
|
var config auth.Config
|
||||||
switch auth.Type(form.Type) {
|
switch auth.Type(form.Type) {
|
||||||
case auth.LDAP, auth.DLDAP:
|
case auth.LDAP, auth.DLDAP:
|
||||||
config = parseLDAPConfig(form)
|
config = parseLDAPConfig(form)
|
||||||
@ -421,6 +416,7 @@ func EditAuthSourcePost(ctx *context.Context) {
|
|||||||
source.IsActive = form.IsActive
|
source.IsActive = form.IsActive
|
||||||
source.IsSyncEnabled = form.IsSyncEnabled
|
source.IsSyncEnabled = form.IsSyncEnabled
|
||||||
source.Cfg = config
|
source.Cfg = config
|
||||||
|
source.TwoFactorPolicy = form.TwoFactorPolicy
|
||||||
if err := auth.UpdateSource(ctx, source); err != nil {
|
if err := auth.UpdateSource(ctx, source); err != nil {
|
||||||
if auth.IsErrSourceAlreadyExist(err) {
|
if auth.IsErrSourceAlreadyExist(err) {
|
||||||
ctx.Data["Err_Name"] = true
|
ctx.Data["Err_Name"] = true
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/auth"
|
"code.gitea.io/gitea/models/auth"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
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/setting"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
@ -87,6 +88,7 @@ func TwoFactorPost(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = ctx.Session.Set(session.KeyUserHasTwoFactorAuth, true)
|
||||||
handleSignIn(ctx, u, remember)
|
handleSignIn(ctx, u, remember)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,10 @@ func autoSignIn(ctx *context.Context) (bool, error) {
|
|||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
userHasTwoFactorAuth, err := auth.HasTwoFactorOrWebAuthn(ctx, u.ID)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("HasTwoFactorOrWebAuthn: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
isSucceed = true
|
isSucceed = true
|
||||||
|
|
||||||
@ -87,9 +91,9 @@ func autoSignIn(ctx *context.Context) (bool, error) {
|
|||||||
ctx.SetSiteCookie(setting.CookieRememberName, nt.ID+":"+token, setting.LogInRememberDays*timeutil.Day)
|
ctx.SetSiteCookie(setting.CookieRememberName, nt.ID+":"+token, setting.LogInRememberDays*timeutil.Day)
|
||||||
|
|
||||||
if err := updateSession(ctx, nil, map[string]any{
|
if err := updateSession(ctx, nil, map[string]any{
|
||||||
// Set session IDs
|
session.KeyUID: u.ID,
|
||||||
"uid": u.ID,
|
session.KeyUname: u.Name,
|
||||||
"uname": u.Name,
|
session.KeyUserHasTwoFactorAuth: userHasTwoFactorAuth,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return false, fmt.Errorf("unable to updateSession: %w", err)
|
return false, fmt.Errorf("unable to updateSession: %w", err)
|
||||||
}
|
}
|
||||||
@ -239,9 +243,8 @@ func SignInPost(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Now handle 2FA:
|
// Now handle 2FA:
|
||||||
|
|
||||||
// First of all if the source can skip local two fa we're done
|
// 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)
|
handleSignIn(ctx, u, form.Remember)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -262,7 +265,7 @@ func SignInPost(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !hasTOTPtwofa && !hasWebAuthnTwofa {
|
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)
|
handleSignIn(ctx, u, form.Remember)
|
||||||
return
|
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)
|
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{
|
if err := updateSession(ctx, []string{
|
||||||
// Delete the openid, 2fa and linkaccount data
|
// Delete the openid, 2fa and link_account data
|
||||||
"openid_verified_uri",
|
"openid_verified_uri",
|
||||||
"openid_signin_remember",
|
"openid_signin_remember",
|
||||||
"openid_determined_email",
|
"openid_determined_email",
|
||||||
@ -321,8 +330,9 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
|
|||||||
"twofaRemember",
|
"twofaRemember",
|
||||||
"linkAccount",
|
"linkAccount",
|
||||||
}, map[string]any{
|
}, map[string]any{
|
||||||
"uid": u.ID,
|
session.KeyUID: u.ID,
|
||||||
"uname": u.Name,
|
session.KeyUname: u.Name,
|
||||||
|
session.KeyUserHasTwoFactorAuth: userHasTwoFactorAuth,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
ctx.ServerError("RegenerateSession", err)
|
ctx.ServerError("RegenerateSession", err)
|
||||||
return setting.AppSubURL + "/"
|
return setting.AppSubURL + "/"
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
|
"code.gitea.io/gitea/modules/session"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"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)
|
updateAvatarIfNeed(ctx, gothUser.AvatarURL, u)
|
||||||
|
|
||||||
needs2FA := false
|
needs2FA := false
|
||||||
if !source.Cfg.(*oauth2.Source).SkipLocalTwoFA {
|
if !source.TwoFactorShouldSkip() {
|
||||||
_, err := auth.GetTwoFactorByUID(ctx, u.ID)
|
_, err := auth.GetTwoFactorByUID(ctx, u.ID)
|
||||||
if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
|
if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
|
||||||
ctx.ServerError("UserSignIn", err)
|
ctx.ServerError("UserSignIn", err)
|
||||||
@ -352,10 +353,16 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
|
|||||||
ctx.ServerError("UpdateUser", err)
|
ctx.ServerError("UpdateUser", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
userHasTwoFactorAuth, err := auth.HasTwoFactorOrWebAuthn(ctx, u.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("UpdateUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := updateSession(ctx, nil, map[string]any{
|
if err := updateSession(ctx, nil, map[string]any{
|
||||||
"uid": u.ID,
|
session.KeyUID: u.ID,
|
||||||
"uname": u.Name,
|
session.KeyUname: u.Name,
|
||||||
|
session.KeyUserHasTwoFactorAuth: userHasTwoFactorAuth,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
ctx.ServerError("updateSession", err)
|
ctx.ServerError("updateSession", err)
|
||||||
return
|
return
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/auth"
|
"code.gitea.io/gitea/models/auth"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/session"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
@ -163,6 +164,7 @@ func EnrollTwoFactor(ctx *context.Context) {
|
|||||||
|
|
||||||
ctx.Data["Title"] = ctx.Tr("settings")
|
ctx.Data["Title"] = ctx.Tr("settings")
|
||||||
ctx.Data["PageIsSettingsSecurity"] = true
|
ctx.Data["PageIsSettingsSecurity"] = true
|
||||||
|
ctx.Data["ShowTwoFactorRequiredMessage"] = false
|
||||||
|
|
||||||
t, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID)
|
t, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID)
|
||||||
if t != nil {
|
if t != nil {
|
||||||
@ -194,6 +196,7 @@ func EnrollTwoFactorPost(ctx *context.Context) {
|
|||||||
form := web.GetForm(ctx).(*forms.TwoFactorAuthForm)
|
form := web.GetForm(ctx).(*forms.TwoFactorAuthForm)
|
||||||
ctx.Data["Title"] = ctx.Tr("settings")
|
ctx.Data["Title"] = ctx.Tr("settings")
|
||||||
ctx.Data["PageIsSettingsSecurity"] = true
|
ctx.Data["PageIsSettingsSecurity"] = true
|
||||||
|
ctx.Data["ShowTwoFactorRequiredMessage"] = false
|
||||||
|
|
||||||
t, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID)
|
t, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID)
|
||||||
if t != nil {
|
if t != nil {
|
||||||
@ -246,6 +249,10 @@ func EnrollTwoFactorPost(ctx *context.Context) {
|
|||||||
return
|
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
|
// 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 we can detect the unique constraint failure below we can move this to after the NewTwoFactor
|
||||||
if err := ctx.Session.Delete("twofaSecret"); err != nil {
|
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)
|
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.
|
// 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
|
// 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
wa "code.gitea.io/gitea/modules/auth/webauthn"
|
wa "code.gitea.io/gitea/modules/auth/webauthn"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/session"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
@ -120,7 +121,7 @@ func WebauthnRegisterPost(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = ctx.Session.Delete("webauthnName")
|
_ = ctx.Session.Delete("webauthnName")
|
||||||
|
_ = ctx.Session.Set(session.KeyUserHasTwoFactorAuth, true)
|
||||||
ctx.JSON(http.StatusCreated, cred)
|
ctx.JSON(http.StatusCreated, cred)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,14 +142,14 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if skipper, ok := source.Cfg.(LocalTwoFASkipper); !ok || !skipper.IsSkipLocalTwoFA() {
|
if !source.TwoFactorShouldSkip() {
|
||||||
// Check if the user has webAuthn registration
|
// Check if the user has WebAuthn registration
|
||||||
hasWebAuthn, err := auth_model.HasWebAuthnRegistrationsByUID(req.Context(), u.ID)
|
hasWebAuthn, err := auth_model.HasWebAuthnRegistrationsByUID(req.Context(), u.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if hasWebAuthn {
|
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 {
|
if err := validateTOTP(req, u); err != nil {
|
||||||
|
@ -35,11 +35,6 @@ type PasswordAuthenticator interface {
|
|||||||
Authenticate(ctx context.Context, user *user_model.User, login, password string) (*user_model.User, error)
|
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
|
// SynchronizableSource represents a source that can synchronize users
|
||||||
type SynchronizableSource interface {
|
type SynchronizableSource interface {
|
||||||
Sync(ctx context.Context, updateExisting bool) error
|
Sync(ctx context.Context, updateExisting bool) error
|
||||||
|
@ -11,7 +11,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Source is a password authentication service
|
// Source is a password authentication service
|
||||||
type Source struct{}
|
type Source struct {
|
||||||
|
auth.ConfigBase `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
// FromDB fills up an OAuth2Config from serialized format.
|
// FromDB fills up an OAuth2Config from serialized format.
|
||||||
func (source *Source) FromDB(bs []byte) error {
|
func (source *Source) FromDB(bs []byte) error {
|
||||||
|
@ -15,13 +15,11 @@ import (
|
|||||||
type sourceInterface interface {
|
type sourceInterface interface {
|
||||||
auth.PasswordAuthenticator
|
auth.PasswordAuthenticator
|
||||||
auth.SynchronizableSource
|
auth.SynchronizableSource
|
||||||
auth.LocalTwoFASkipper
|
|
||||||
auth_model.SSHKeyProvider
|
auth_model.SSHKeyProvider
|
||||||
auth_model.Config
|
auth_model.Config
|
||||||
auth_model.SkipVerifiable
|
auth_model.SkipVerifiable
|
||||||
auth_model.HasTLSer
|
auth_model.HasTLSer
|
||||||
auth_model.UseTLSer
|
auth_model.UseTLSer
|
||||||
auth_model.SourceSettable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ (sourceInterface) = &ldap.Source{}
|
var _ (sourceInterface) = &ldap.Source{}
|
||||||
|
@ -24,6 +24,8 @@ import (
|
|||||||
|
|
||||||
// Source Basic LDAP authentication service
|
// Source Basic LDAP authentication service
|
||||||
type Source struct {
|
type Source struct {
|
||||||
|
auth.ConfigBase `json:"-"`
|
||||||
|
|
||||||
Name string // canonical name (ie. corporate.ad)
|
Name string // canonical name (ie. corporate.ad)
|
||||||
Host string // LDAP host
|
Host string // LDAP host
|
||||||
Port int // port number
|
Port int // port number
|
||||||
@ -54,9 +56,6 @@ type Source struct {
|
|||||||
GroupTeamMap string // Map LDAP groups to teams
|
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
|
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
|
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.
|
// FromDB fills up a LDAPConfig from serialized format.
|
||||||
@ -109,11 +108,6 @@ func (source *Source) ProvidesSSHKeys() bool {
|
|||||||
return strings.TrimSpace(source.AttributeSSHPublicKey) != ""
|
return strings.TrimSpace(source.AttributeSSHPublicKey) != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAuthSource sets the related AuthSource
|
|
||||||
func (source *Source) SetAuthSource(authSource *auth.Source) {
|
|
||||||
source.authSource = authSource
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
auth.RegisterTypeConfig(auth.LDAP, &Source{})
|
auth.RegisterTypeConfig(auth.LDAP, &Source{})
|
||||||
auth.RegisterTypeConfig(auth.DLDAP, &Source{})
|
auth.RegisterTypeConfig(auth.DLDAP, &Source{})
|
||||||
|
@ -25,7 +25,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
|||||||
if user != nil {
|
if user != nil {
|
||||||
loginName = user.LoginName
|
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 {
|
if sr == nil {
|
||||||
// User not in LDAP, do nothing
|
// User not in LDAP, do nothing
|
||||||
return nil, user_model.ErrUserNotExist{Name: loginName}
|
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 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 {
|
if err := asymkey_service.RewriteAllPublicKeys(ctx); err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
@ -84,8 +84,8 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
|||||||
Name: sr.Username,
|
Name: sr.Username,
|
||||||
FullName: composeFullName(sr.Name, sr.Surname, sr.Username),
|
FullName: composeFullName(sr.Name, sr.Surname, sr.Username),
|
||||||
Email: sr.Mail,
|
Email: sr.Mail,
|
||||||
LoginType: source.authSource.Type,
|
LoginType: source.AuthSource.Type,
|
||||||
LoginSource: source.authSource.ID,
|
LoginSource: source.AuthSource.ID,
|
||||||
LoginName: userName,
|
LoginName: userName,
|
||||||
IsAdmin: sr.IsAdmin,
|
IsAdmin: sr.IsAdmin,
|
||||||
}
|
}
|
||||||
@ -99,7 +99,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
|||||||
return user, err
|
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 {
|
if err := asymkey_service.RewriteAllPublicKeys(ctx); err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
@ -123,8 +123,3 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
|||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSkipLocalTwoFA returns if this source should skip local 2fa for password authentication
|
|
||||||
func (source *Source) IsSkipLocalTwoFA() bool {
|
|
||||||
return source.SkipLocalTwoFA
|
|
||||||
}
|
|
||||||
|
@ -22,21 +22,21 @@ import (
|
|||||||
|
|
||||||
// Sync causes this ldap source to synchronize its users with the db
|
// Sync causes this ldap source to synchronize its users with the db
|
||||||
func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
|
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) != ""
|
isAttributeSSHPublicKeySet := strings.TrimSpace(source.AttributeSSHPublicKey) != ""
|
||||||
var sshKeysNeedUpdate bool
|
var sshKeysNeedUpdate bool
|
||||||
|
|
||||||
// Find all users with this login type - FIXME: Should this be an iterator?
|
// 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 {
|
if err != nil {
|
||||||
log.Error("SyncExternalUsers: %v", err)
|
log.Error("SyncExternalUsers: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Warn("SyncExternalUsers: Cancelled 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)
|
return db.ErrCancelledf("Before update of %s", source.AuthSource.Name)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
|
|||||||
|
|
||||||
sr, err := source.SearchEntries()
|
sr, err := source.SearchEntries()
|
||||||
if err != nil {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
|
|||||||
for _, su := range sr {
|
for _, su := range sr {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
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
|
// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
|
||||||
if sshKeysNeedUpdate {
|
if sshKeysNeedUpdate {
|
||||||
err = asymkey_service.RewriteAllPublicKeys(ctx)
|
err = asymkey_service.RewriteAllPublicKeys(ctx)
|
||||||
@ -82,7 +82,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
|
|||||||
log.Error("RewriteAllPublicKeys: %v", err)
|
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:
|
default:
|
||||||
}
|
}
|
||||||
if su.Username == "" && su.Mail == "" {
|
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)
|
fullName := composeFullName(su.Name, su.Surname, su.Username)
|
||||||
// If no existing user found, create one
|
// If no existing user found, create one
|
||||||
if usr == nil {
|
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{
|
usr = &user_model.User{
|
||||||
LowerName: su.LowerName,
|
LowerName: su.LowerName,
|
||||||
Name: su.Username,
|
Name: su.Username,
|
||||||
FullName: fullName,
|
FullName: fullName,
|
||||||
LoginType: source.authSource.Type,
|
LoginType: source.AuthSource.Type,
|
||||||
LoginSource: source.authSource.ID,
|
LoginSource: source.AuthSource.ID,
|
||||||
LoginName: su.Username,
|
LoginName: su.Username,
|
||||||
Email: su.Mail,
|
Email: su.Mail,
|
||||||
IsAdmin: su.IsAdmin,
|
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)
|
err = user_model.CreateUser(ctx, usr, &user_model.Meta{}, overwriteDefault)
|
||||||
if err != nil {
|
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 {
|
if err == nil && isAttributeSSHPublicKeySet {
|
||||||
log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", source.authSource.Name, usr.Name)
|
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) {
|
if asymkey_model.AddPublicKeysBySource(ctx, usr, source.AuthSource, su.SSHPublicKey) {
|
||||||
sshKeysNeedUpdate = true
|
sshKeysNeedUpdate = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,7 +145,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
|
|||||||
}
|
}
|
||||||
} else if updateExisting {
|
} else if updateExisting {
|
||||||
// Synchronize SSH Public Key if that attribute is set
|
// 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
|
sshKeysNeedUpdate = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +155,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
|
|||||||
!strings.EqualFold(usr.Email, su.Mail) ||
|
!strings.EqualFold(usr.Email, su.Mail) ||
|
||||||
usr.FullName != fullName ||
|
usr.FullName != fullName ||
|
||||||
!usr.IsActive {
|
!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{
|
opts := &user_service.UpdateOptions{
|
||||||
FullName: optional.Some(fullName),
|
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 {
|
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 {
|
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 {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
log.Warn("SyncExternalUsers: Cancelled 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)
|
return db.ErrCancelledf("During update of %s before delete users", source.AuthSource.Name)
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,13 +214,13 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
|
|||||||
continue
|
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{
|
opts := &user_service.UpdateOptions{
|
||||||
IsActive: optional.Some(false),
|
IsActive: optional.Some(false),
|
||||||
}
|
}
|
||||||
if err := user_service.UpdateUser(ctx, usr, opts); err != nil {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
|
|
||||||
type sourceInterface interface {
|
type sourceInterface interface {
|
||||||
auth_model.Config
|
auth_model.Config
|
||||||
auth_model.SourceSettable
|
|
||||||
auth_model.RegisterableSource
|
auth_model.RegisterableSource
|
||||||
auth.PasswordAuthenticator
|
auth.PasswordAuthenticator
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ import (
|
|||||||
|
|
||||||
// Source holds configuration for the OAuth2 login source.
|
// Source holds configuration for the OAuth2 login source.
|
||||||
type Source struct {
|
type Source struct {
|
||||||
|
auth.ConfigBase `json:"-"`
|
||||||
|
|
||||||
Provider string
|
Provider string
|
||||||
ClientID string
|
ClientID string
|
||||||
ClientSecret string
|
ClientSecret string
|
||||||
@ -25,10 +27,6 @@ type Source struct {
|
|||||||
GroupTeamMap string
|
GroupTeamMap string
|
||||||
GroupTeamMapRemoval bool
|
GroupTeamMapRemoval bool
|
||||||
RestrictedGroup string
|
RestrictedGroup string
|
||||||
SkipLocalTwoFA bool `json:",omitempty"`
|
|
||||||
|
|
||||||
// reference to the authSource
|
|
||||||
authSource *auth.Source
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromDB fills up an OAuth2Config from serialized format.
|
// FromDB fills up an OAuth2Config from serialized format.
|
||||||
@ -41,11 +39,6 @@ func (source *Source) ToDB() ([]byte, error) {
|
|||||||
return json.Marshal(source)
|
return json.Marshal(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAuthSource sets the related AuthSource
|
|
||||||
func (source *Source) SetAuthSource(authSource *auth.Source) {
|
|
||||||
source.authSource = authSource
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
auth.RegisterTypeConfig(auth.OAuth2, &Source{})
|
auth.RegisterTypeConfig(auth.OAuth2, &Source{})
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
// Callout redirects request/response pair to authenticate against the provider
|
// Callout redirects request/response pair to authenticate against the provider
|
||||||
func (source *Source) Callout(request *http.Request, response http.ResponseWriter) error {
|
func (source *Source) Callout(request *http.Request, response http.ResponseWriter) error {
|
||||||
// not sure if goth is thread safe (?) when using multiple providers
|
// 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
|
// 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
|
// 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
|
// 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) {
|
func (source *Source) Callback(request *http.Request, response http.ResponseWriter) (goth.User, error) {
|
||||||
// not sure if goth is thread safe (?) when using multiple providers
|
// 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()
|
gothRWMutex.RLock()
|
||||||
defer gothRWMutex.RUnlock()
|
defer gothRWMutex.RUnlock()
|
||||||
|
@ -9,13 +9,13 @@ import (
|
|||||||
|
|
||||||
// RegisterSource causes an OAuth2 configuration to be registered
|
// RegisterSource causes an OAuth2 configuration to be registered
|
||||||
func (source *Source) RegisterSource() error {
|
func (source *Source) RegisterSource() error {
|
||||||
err := RegisterProviderWithGothic(source.authSource.Name, source)
|
err := RegisterProviderWithGothic(source.AuthSource.Name, source)
|
||||||
return wrapOpenIDConnectInitializeError(err, source.authSource.Name, source)
|
return wrapOpenIDConnectInitializeError(err, source.AuthSource.Name, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnregisterSource causes an OAuth2 configuration to be unregistered
|
// UnregisterSource causes an OAuth2 configuration to be unregistered
|
||||||
func (source *Source) UnregisterSource() error {
|
func (source *Source) UnregisterSource() error {
|
||||||
RemoveProviderFromGothic(source.authSource.Name)
|
RemoveProviderFromGothic(source.AuthSource.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,27 +18,27 @@ import (
|
|||||||
|
|
||||||
// Sync causes this OAuth2 source to synchronize its users with the db.
|
// Sync causes this OAuth2 source to synchronize its users with the db.
|
||||||
func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
|
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 {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, err := createProvider(source.authSource.Name, source)
|
provider, err := createProvider(source.AuthSource.Name, source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !provider.RefreshTokenAvailable() {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := user_model.FindExternalUserOptions{
|
opts := user_model.FindExternalUserOptions{
|
||||||
HasRefreshToken: true,
|
HasRefreshToken: true,
|
||||||
Expired: 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 {
|
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
|
// recognizes them as a valid user, they will be able to login
|
||||||
// via their provider and reactivate their account.
|
// via their provider and reactivate their account.
|
||||||
if shouldDisable {
|
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 {
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
if hasUser {
|
if hasUser {
|
||||||
|
@ -18,19 +18,21 @@ func TestSource(t *testing.T) {
|
|||||||
|
|
||||||
source := &Source{
|
source := &Source{
|
||||||
Provider: "fake",
|
Provider: "fake",
|
||||||
authSource: &auth.Source{
|
ConfigBase: auth.ConfigBase{
|
||||||
|
AuthSource: &auth.Source{
|
||||||
ID: 12,
|
ID: 12,
|
||||||
Type: auth.OAuth2,
|
Type: auth.OAuth2,
|
||||||
Name: "fake",
|
Name: "fake",
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
IsSyncEnabled: true,
|
IsSyncEnabled: true,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
user := &user_model.User{
|
user := &user_model.User{
|
||||||
LoginName: "external",
|
LoginName: "external",
|
||||||
LoginType: auth.OAuth2,
|
LoginType: auth.OAuth2,
|
||||||
LoginSource: source.authSource.ID,
|
LoginSource: source.AuthSource.ID,
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Email: "external@example.com",
|
Email: "external@example.com",
|
||||||
}
|
}
|
||||||
@ -47,7 +49,7 @@ func TestSource(t *testing.T) {
|
|||||||
err = user_model.LinkExternalToUser(t.Context(), user, e)
|
err = user_model.LinkExternalToUser(t.Context(), user, e)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
provider, err := createProvider(source.authSource.Name, source)
|
provider, err := createProvider(source.AuthSource.Name, source)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
t.Run("refresh", func(t *testing.T) {
|
t.Run("refresh", func(t *testing.T) {
|
||||||
|
@ -15,7 +15,6 @@ import (
|
|||||||
type sourceInterface interface {
|
type sourceInterface interface {
|
||||||
auth.PasswordAuthenticator
|
auth.PasswordAuthenticator
|
||||||
auth_model.Config
|
auth_model.Config
|
||||||
auth_model.SourceSettable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ (sourceInterface) = &pam.Source{}
|
var _ (sourceInterface) = &pam.Source{}
|
||||||
|
@ -17,12 +17,10 @@ import (
|
|||||||
|
|
||||||
// Source holds configuration for the PAM login source.
|
// Source holds configuration for the PAM login source.
|
||||||
type Source struct {
|
type Source struct {
|
||||||
|
auth.ConfigBase `json:"-"`
|
||||||
|
|
||||||
ServiceName string // pam service (e.g. system-auth)
|
ServiceName string // pam service (e.g. system-auth)
|
||||||
EmailDomain string
|
EmailDomain string
|
||||||
SkipLocalTwoFA bool `json:",omitempty"` // Skip Local 2fa for users authenticated with this source
|
|
||||||
|
|
||||||
// reference to the authSource
|
|
||||||
authSource *auth.Source
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromDB fills up a PAMConfig from serialized format.
|
// FromDB fills up a PAMConfig from serialized format.
|
||||||
@ -35,11 +33,6 @@ func (source *Source) ToDB() ([]byte, error) {
|
|||||||
return json.Marshal(source)
|
return json.Marshal(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAuthSource sets the related AuthSource
|
|
||||||
func (source *Source) SetAuthSource(authSource *auth.Source) {
|
|
||||||
source.authSource = authSource
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
auth.RegisterTypeConfig(auth.PAM, &Source{})
|
auth.RegisterTypeConfig(auth.PAM, &Source{})
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
|||||||
Email: email,
|
Email: email,
|
||||||
Passwd: password,
|
Passwd: password,
|
||||||
LoginType: auth.PAM,
|
LoginType: auth.PAM,
|
||||||
LoginSource: source.authSource.ID,
|
LoginSource: source.AuthSource.ID,
|
||||||
LoginName: userName, // This is what the user typed in
|
LoginName: userName, // This is what the user typed in
|
||||||
}
|
}
|
||||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||||
@ -69,8 +69,3 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
|||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSkipLocalTwoFA returns if this source should skip local 2fa for password authentication
|
|
||||||
func (source *Source) IsSkipLocalTwoFA() bool {
|
|
||||||
return source.SkipLocalTwoFA
|
|
||||||
}
|
|
||||||
|
@ -18,7 +18,6 @@ type sourceInterface interface {
|
|||||||
auth_model.SkipVerifiable
|
auth_model.SkipVerifiable
|
||||||
auth_model.HasTLSer
|
auth_model.HasTLSer
|
||||||
auth_model.UseTLSer
|
auth_model.UseTLSer
|
||||||
auth_model.SourceSettable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ (sourceInterface) = &smtp.Source{}
|
var _ (sourceInterface) = &smtp.Source{}
|
||||||
|
@ -17,6 +17,8 @@ import (
|
|||||||
|
|
||||||
// Source holds configuration for the SMTP login source.
|
// Source holds configuration for the SMTP login source.
|
||||||
type Source struct {
|
type Source struct {
|
||||||
|
auth.ConfigBase `json:"-"`
|
||||||
|
|
||||||
Auth string
|
Auth string
|
||||||
Host string
|
Host string
|
||||||
Port int
|
Port int
|
||||||
@ -25,10 +27,6 @@ type Source struct {
|
|||||||
SkipVerify bool
|
SkipVerify bool
|
||||||
HeloHostname string
|
HeloHostname string
|
||||||
DisableHelo bool
|
DisableHelo bool
|
||||||
SkipLocalTwoFA bool `json:",omitempty"`
|
|
||||||
|
|
||||||
// reference to the authSource
|
|
||||||
authSource *auth.Source
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromDB fills up an SMTPConfig from serialized format.
|
// FromDB fills up an SMTPConfig from serialized format.
|
||||||
@ -56,11 +54,6 @@ func (source *Source) UseTLS() bool {
|
|||||||
return source.ForceSMTPS || source.Port == 465
|
return source.ForceSMTPS || source.Port == 465
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAuthSource sets the related AuthSource
|
|
||||||
func (source *Source) SetAuthSource(authSource *auth.Source) {
|
|
||||||
source.authSource = authSource
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
auth.RegisterTypeConfig(auth.SMTP, &Source{})
|
auth.RegisterTypeConfig(auth.SMTP, &Source{})
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
|||||||
Email: userName,
|
Email: userName,
|
||||||
Passwd: password,
|
Passwd: password,
|
||||||
LoginType: auth_model.SMTP,
|
LoginType: auth_model.SMTP,
|
||||||
LoginSource: source.authSource.ID,
|
LoginSource: source.AuthSource.ID,
|
||||||
LoginName: userName,
|
LoginName: userName,
|
||||||
}
|
}
|
||||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||||
@ -85,8 +85,3 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u
|
|||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSkipLocalTwoFA returns if this source should skip local 2fa for password authentication
|
|
||||||
func (source *Source) IsSkipLocalTwoFA() bool {
|
|
||||||
return source.SkipLocalTwoFA
|
|
||||||
}
|
|
||||||
|
@ -17,6 +17,8 @@ import (
|
|||||||
|
|
||||||
// Source holds configuration for SSPI single sign-on.
|
// Source holds configuration for SSPI single sign-on.
|
||||||
type Source struct {
|
type Source struct {
|
||||||
|
auth.ConfigBase `json:"-"`
|
||||||
|
|
||||||
AutoCreateUsers bool
|
AutoCreateUsers bool
|
||||||
AutoActivateUsers bool
|
AutoActivateUsers bool
|
||||||
StripDomainNames bool
|
StripDomainNames bool
|
||||||
|
@ -196,6 +196,8 @@ func Contexter() func(next http.Handler) http.Handler {
|
|||||||
|
|
||||||
ctx.Data["SystemConfig"] = setting.Config()
|
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
|
// 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["DisableMigrations"] = setting.Repository.DisableMigrations
|
||||||
ctx.Data["DisableStars"] = setting.Repository.DisableStars
|
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.
|
// HasError returns true if error occurs in form validation.
|
||||||
// Attention: this function changes ctx.Data and ctx.Flash
|
// 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.
|
// If HasError is called, then before Redirect, the error message should be stored by ctx.Flash.Error(ctx.GetErrMsg()) again.
|
||||||
|
@ -340,11 +340,15 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctx.DoerNeedTwoFactorAuth() {
|
||||||
|
ctx.Repo.Permission = access_model.PermissionNoAccess()
|
||||||
|
} else {
|
||||||
ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetUserRepoPermission", err)
|
ctx.ServerError("GetUserRepoPermission", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !ctx.Repo.Permission.HasAnyUnitAccessOrPublicAccess() && !canWriteAsMaintainer(ctx) {
|
if !ctx.Repo.Permission.HasAnyUnitAccessOrPublicAccess() && !canWriteAsMaintainer(ctx) {
|
||||||
if ctx.FormString("go-get") == "1" {
|
if ctx.FormString("go-get") == "1" {
|
||||||
|
@ -17,6 +17,8 @@ type AuthenticationForm struct {
|
|||||||
ID int64
|
ID int64
|
||||||
Type int `binding:"Range(2,7)"`
|
Type int `binding:"Range(2,7)"`
|
||||||
Name string `binding:"Required;MaxSize(30)"`
|
Name string `binding:"Required;MaxSize(30)"`
|
||||||
|
TwoFactorPolicy string
|
||||||
|
|
||||||
Host string
|
Host string
|
||||||
Port int
|
Port int
|
||||||
BindDN string
|
BindDN string
|
||||||
@ -74,7 +76,6 @@ type AuthenticationForm struct {
|
|||||||
Oauth2RestrictedGroup string
|
Oauth2RestrictedGroup string
|
||||||
Oauth2GroupTeamMap string `binding:"ValidGroupTeamMap"`
|
Oauth2GroupTeamMap string `binding:"ValidGroupTeamMap"`
|
||||||
Oauth2GroupTeamMapRemoval bool
|
Oauth2GroupTeamMapRemoval bool
|
||||||
SkipLocalTwoFA bool
|
|
||||||
SSPIAutoCreateUsers bool
|
SSPIAutoCreateUsers bool
|
||||||
SSPIAutoActivateUsers bool
|
SSPIAutoActivateUsers bool
|
||||||
SSPIStripDomainNames bool
|
SSPIStripDomainNames bool
|
||||||
|
@ -17,6 +17,13 @@
|
|||||||
<label for="auth_name">{{ctx.Locale.Tr "admin.auths.auth_name"}}</label>
|
<label for="auth_name">{{ctx.Locale.Tr "admin.auths.auth_name"}}</label>
|
||||||
<input id="auth_name" name="name" value="{{.Source.Name}}" autofocus required>
|
<input id="auth_name" name="name" value="{{.Source.Name}}" autofocus required>
|
||||||
</div>
|
</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 -->
|
<!-- LDAP and DLDAP -->
|
||||||
{{if or .Source.IsLDAP .Source.IsDLDAP}}
|
{{if or .Source.IsLDAP .Source.IsDLDAP}}
|
||||||
@ -159,13 +166,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{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="inline field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<label for="allow_deactivate_all"><strong>{{ctx.Locale.Tr "admin.auths.allow_deactivate_all"}}</strong></label>
|
<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}}">
|
<input id="allowed_domains" name="allowed_domains" value="{{$cfg.AllowedDomains}}">
|
||||||
<p class="help">{{ctx.Locale.Tr "admin.auths.allowed_domains_helper"}}</p>
|
<p class="help">{{ctx.Locale.Tr "admin.auths.allowed_domains_helper"}}</p>
|
||||||
</div>
|
</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}}
|
{{end}}
|
||||||
|
|
||||||
<!-- PAM -->
|
<!-- PAM -->
|
||||||
@ -247,13 +240,6 @@
|
|||||||
<label for="pam_email_domain">{{ctx.Locale.Tr "admin.auths.pam_email_domain"}}</label>
|
<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}}">
|
<input id="pam_email_domain" name="pam_email_domain" value="{{$cfg.EmailDomain}}">
|
||||||
</div>
|
</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}}
|
{{end}}
|
||||||
|
|
||||||
<!-- OAuth2 -->
|
<!-- OAuth2 -->
|
||||||
@ -288,13 +274,6 @@
|
|||||||
<label for="open_id_connect_auto_discovery_url">{{ctx.Locale.Tr "admin.auths.openIdConnectAutoDiscoveryURL"}}</label>
|
<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}}">
|
<input id="open_id_connect_auto_discovery_url" name="open_id_connect_auto_discovery_url" value="{{$cfg.OpenIDConnectAutoDiscoveryURL}}">
|
||||||
</div>
|
</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="oauth2_use_custom_url inline field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<label><strong>{{ctx.Locale.Tr "admin.auths.oauth2_use_custom_url"}}</strong></label>
|
<label><strong>{{ctx.Locale.Tr "admin.auths.oauth2_use_custom_url"}}</strong></label>
|
||||||
|
@ -18,3 +18,8 @@
|
|||||||
<p>{{.Flash.WarningMsg | SanitizeHTML}}</p>
|
<p>{{.Flash.WarningMsg | SanitizeHTML}}</p>
|
||||||
</div>
|
</div>
|
||||||
{{- end -}}
|
{{- 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 -}}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<div role="main" aria-label="{{.Title}}" class="page-content {{if .IsRepo}}repository{{end}}">
|
<div role="main" aria-label="{{.Title}}" class="page-content {{if .IsRepo}}repository{{end}}">
|
||||||
{{if .IsRepo}}{{template "repo/header" .}}{{end}}
|
{{if .IsRepo}}{{template "repo/header" .}}{{end}}
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
|
{{template "base/alert" .}}
|
||||||
<div class="status-page-error">
|
<div class="status-page-error">
|
||||||
<div class="status-page-error-title">404 Not Found</div>
|
<div class="status-page-error-title">404 Not Found</div>
|
||||||
<div class="tw-text-center">
|
<div class="tw-text-center">
|
||||||
|
@ -94,7 +94,7 @@ func TestBasicAuthWithWebAuthn(t *testing.T) {
|
|||||||
}
|
}
|
||||||
var userParsed userResponse
|
var userParsed userResponse
|
||||||
DecodeJSON(t, resp, &userParsed)
|
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
|
// user32 has webauthn enrolled, he can't request git protocol with basic auth
|
||||||
req = NewRequest(t, "GET", "/user2/repo1/info/refs")
|
req = NewRequest(t, "GET", "/user2/repo1/info/refs")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user