mirror of
https://github.com/go-gitea/gitea.git
synced 2025-04-15 13:47:42 +00:00
Merge 3804e6c903
into a2651c14ce
This commit is contained in:
commit
0693c7fecc
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -518,6 +518,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 any repositories.
|
||||
;TWO_FACTOR_AUTH =
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -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 {
|
||||
|
@ -19,6 +19,8 @@ import (
|
||||
)
|
||||
|
||||
type TestSource struct {
|
||||
auth_model.ConfigBase
|
||||
|
||||
Provider string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
51
models/migrations/v1_24/v320.go
Normal file
51
models/migrations/v1_24/v320.go
Normal file
@ -0,0 +1,51 @@
|
||||
// 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.Sync(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)
|
||||
}
|
||||
|
||||
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"
|
||||
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
|
||||
|
@ -449,6 +449,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
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 + "/"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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{}
|
||||
|
@ -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{})
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import (
|
||||
|
||||
type sourceInterface interface {
|
||||
auth_model.Config
|
||||
auth_model.SourceSettable
|
||||
auth_model.RegisterableSource
|
||||
auth.PasswordAuthenticator
|
||||
}
|
||||
|
@ -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{})
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -15,7 +15,6 @@ import (
|
||||
type sourceInterface interface {
|
||||
auth.PasswordAuthenticator
|
||||
auth_model.Config
|
||||
auth_model.SourceSettable
|
||||
}
|
||||
|
||||
var _ (sourceInterface) = &pam.Source{}
|
||||
|
@ -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{})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ type sourceInterface interface {
|
||||
auth_model.SkipVerifiable
|
||||
auth_model.HasTLSer
|
||||
auth_model.UseTLSer
|
||||
auth_model.SourceSettable
|
||||
}
|
||||
|
||||
var _ (sourceInterface) = &smtp.Source{}
|
||||
|
@ -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{})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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 -}}
|
||||
|
@ -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">
|
||||
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user