Various fixes in login sources (#10428) (#10429)

Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com>
This commit is contained in:
zeripath 2020-02-23 20:46:17 +00:00 committed by GitHub
parent e938f1d945
commit d3b6f001fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 66 additions and 21 deletions

View file

@ -56,6 +56,21 @@ func (err ErrNamePatternNotAllowed) Error() string {
return fmt.Sprintf("name pattern is not allowed [pattern: %s]", err.Pattern) return fmt.Sprintf("name pattern is not allowed [pattern: %s]", err.Pattern)
} }
// ErrNameCharsNotAllowed represents a "character not allowed in name" error.
type ErrNameCharsNotAllowed struct {
Name string
}
// IsErrNameCharsNotAllowed checks if an error is an ErrNameCharsNotAllowed.
func IsErrNameCharsNotAllowed(err error) bool {
_, ok := err.(ErrNameCharsNotAllowed)
return ok
}
func (err ErrNameCharsNotAllowed) Error() string {
return fmt.Sprintf("User name is invalid [%s]: must be valid alpha or numeric or dash(-_) or dot characters", err.Name)
}
// ErrSSHDisabled represents an "SSH disabled" error. // ErrSSHDisabled represents an "SSH disabled" error.
type ErrSSHDisabled struct { type ErrSSHDisabled struct {
} }

View file

@ -12,7 +12,6 @@ import (
"fmt" "fmt"
"net/smtp" "net/smtp"
"net/textproto" "net/textproto"
"regexp"
"strings" "strings"
"code.gitea.io/gitea/modules/auth/ldap" "code.gitea.io/gitea/modules/auth/ldap"
@ -455,10 +454,6 @@ func composeFullName(firstname, surname, username string) string {
} }
} }
var (
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
)
// LoginViaLDAP queries if login/password is valid against the LDAP directory pool, // LoginViaLDAP queries if login/password is valid against the LDAP directory pool,
// and create a local user if success when enabled. // and create a local user if success when enabled.
func LoginViaLDAP(user *User, login, password string, source *LoginSource) (*User, error) { func LoginViaLDAP(user *User, login, password string, source *LoginSource) (*User, error) {
@ -503,10 +498,6 @@ func LoginViaLDAP(user *User, login, password string, source *LoginSource) (*Use
if len(sr.Username) == 0 { if len(sr.Username) == 0 {
sr.Username = login sr.Username = login
} }
// Validate username make sure it satisfies requirement.
if alphaDashDotPattern.MatchString(sr.Username) {
return nil, fmt.Errorf("Invalid pattern for attribute 'username' [%s]: must be valid alpha or numeric or dash(-_) or dot characters", sr.Username)
}
if len(sr.Mail) == 0 { if len(sr.Mail) == 0 {
sr.Mail = fmt.Sprintf("%s@localhost", sr.Username) sr.Mail = fmt.Sprintf("%s@localhost", sr.Username)
@ -666,7 +657,8 @@ func LoginViaSMTP(user *User, login, password string, sourceID int64, cfg *SMTPC
// LoginViaPAM queries if login/password is valid against the PAM, // LoginViaPAM queries if login/password is valid against the PAM,
// and create a local user if success when enabled. // and create a local user if success when enabled.
func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMConfig) (*User, error) { func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMConfig) (*User, error) {
if err := pam.Auth(cfg.ServiceName, login, password); err != nil { pamLogin, err := pam.Auth(cfg.ServiceName, login, password)
if err != nil {
if strings.Contains(err.Error(), "Authentication failure") { if strings.Contains(err.Error(), "Authentication failure") {
return nil, ErrUserNotExist{0, login, 0} return nil, ErrUserNotExist{0, login, 0}
} }
@ -677,14 +669,21 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon
return user, nil return user, nil
} }
// Allow PAM sources with `@` in their name, like from Active Directory
username := pamLogin
idx := strings.Index(pamLogin, "@")
if idx > -1 {
username = pamLogin[:idx]
}
user = &User{ user = &User{
LowerName: strings.ToLower(login), LowerName: strings.ToLower(username),
Name: login, Name: username,
Email: login, Email: pamLogin,
Passwd: password, Passwd: password,
LoginType: LoginPAM, LoginType: LoginPAM,
LoginSource: sourceID, LoginSource: sourceID,
LoginName: login, LoginName: login, // This is what the user typed in
IsActive: true, IsActive: true,
} }
return user, CreateUser(user) return user, CreateUser(user)

View file

@ -399,7 +399,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) {
// Create org. // Create org.
org := &User{ org := &User{
Name: "All repo", Name: "All_repo",
IsActive: true, IsActive: true,
Type: UserTypeOrganization, Type: UserTypeOrganization,
Visibility: structs.VisibleTypePublic, Visibility: structs.VisibleTypePublic,

View file

@ -18,6 +18,7 @@ import (
"image/png" "image/png"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -87,6 +88,9 @@ var (
// ErrUnsupportedLoginType login source is unknown error // ErrUnsupportedLoginType login source is unknown error
ErrUnsupportedLoginType = errors.New("Login source is unknown") ErrUnsupportedLoginType = errors.New("Login source is unknown")
// Characters prohibited in a user name (anything except A-Za-z0-9_.-)
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
) )
// User represents the object of individual and member of organization. // User represents the object of individual and member of organization.
@ -870,6 +874,11 @@ func isUsableName(names, patterns []string, name string) error {
// IsUsableUsername returns an error when a username is reserved // IsUsableUsername returns an error when a username is reserved
func IsUsableUsername(name string) error { func IsUsableUsername(name string) error {
// Validate username make sure it satisfies requirement.
if alphaDashDotPattern.MatchString(name) {
// Note: usually this error is normally caught up earlier in the UI
return ErrNameCharsNotAllowed{Name: name}
}
return isUsableName(reservedUsernames, reservedUserPatterns, name) return isUsableName(reservedUsernames, reservedUserPatterns, name)
} }

View file

@ -13,7 +13,7 @@ import (
) )
// Auth pam auth service // Auth pam auth service
func Auth(serviceName, userName, passwd string) error { func Auth(serviceName, userName, passwd string) (string, error) {
t, err := pam.StartFunc(serviceName, userName, func(s pam.Style, msg string) (string, error) { t, err := pam.StartFunc(serviceName, userName, func(s pam.Style, msg string) (string, error) {
switch s { switch s {
case pam.PromptEchoOff: case pam.PromptEchoOff:
@ -25,12 +25,14 @@ func Auth(serviceName, userName, passwd string) error {
}) })
if err != nil { if err != nil {
return err return "", err
} }
if err = t.Authenticate(0); err != nil { if err = t.Authenticate(0); err != nil {
return err return "", err
} }
return nil // PAM login names might suffer transformations in the PAM stack.
// We should take whatever the PAM stack returns for it.
return t.GetItem(pam.User)
} }

View file

@ -11,6 +11,6 @@ import (
) )
// Auth not supported lack of pam tag // Auth not supported lack of pam tag
func Auth(serviceName, userName, passwd string) error { func Auth(serviceName, userName, passwd string) (string, error) {
return errors.New("PAM not supported") return "", errors.New("PAM not supported")
} }

View file

@ -374,6 +374,7 @@ user_bio = Biography
form.name_reserved = The username '%s' is reserved. form.name_reserved = The username '%s' is reserved.
form.name_pattern_not_allowed = The pattern '%s' is not allowed in a username. form.name_pattern_not_allowed = The pattern '%s' is not allowed in a username.
form.name_chars_not_allowed = User name '%s' contains invalid characters.
[settings] [settings]
profile = Profile profile = Profile

View file

@ -121,6 +121,9 @@ func NewUserPost(ctx *context.Context, form auth.AdminCreateUserForm) {
case models.IsErrNamePatternNotAllowed(err): case models.IsErrNamePatternNotAllowed(err):
ctx.Data["Err_UserName"] = true ctx.Data["Err_UserName"] = true
ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplUserNew, &form) ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplUserNew, &form)
case models.IsErrNameCharsNotAllowed(err):
ctx.Data["Err_UserName"] = true
ctx.RenderWithErr(ctx.Tr("user.form.name_chars_not_allowed", err.(models.ErrNameCharsNotAllowed).Name), tplUserNew, &form)
default: default:
ctx.ServerError("CreateUser", err) ctx.ServerError("CreateUser", err)
} }

View file

@ -66,6 +66,7 @@ func CreateOrg(ctx *context.APIContext, form api.CreateOrgOption) {
if err := models.CreateOrganization(org, u); err != nil { if err := models.CreateOrganization(org, u); err != nil {
if models.IsErrUserAlreadyExist(err) || if models.IsErrUserAlreadyExist(err) ||
models.IsErrNameReserved(err) || models.IsErrNameReserved(err) ||
models.IsErrNameCharsNotAllowed(err) ||
models.IsErrNamePatternNotAllowed(err) { models.IsErrNamePatternNotAllowed(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err) ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {

View file

@ -90,6 +90,7 @@ func CreateUser(ctx *context.APIContext, form api.CreateUserOption) {
if models.IsErrUserAlreadyExist(err) || if models.IsErrUserAlreadyExist(err) ||
models.IsErrEmailAlreadyUsed(err) || models.IsErrEmailAlreadyUsed(err) ||
models.IsErrNameReserved(err) || models.IsErrNameReserved(err) ||
models.IsErrNameCharsNotAllowed(err) ||
models.IsErrNamePatternNotAllowed(err) { models.IsErrNamePatternNotAllowed(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err) ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {

View file

@ -112,6 +112,7 @@ func Create(ctx *context.APIContext, form api.CreateOrgOption) {
if err := models.CreateOrganization(org, ctx.User); err != nil { if err := models.CreateOrganization(org, ctx.User); err != nil {
if models.IsErrUserAlreadyExist(err) || if models.IsErrUserAlreadyExist(err) ||
models.IsErrNameReserved(err) || models.IsErrNameReserved(err) ||
models.IsErrNameCharsNotAllowed(err) ||
models.IsErrNamePatternNotAllowed(err) { models.IsErrNamePatternNotAllowed(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err) ctx.Error(http.StatusUnprocessableEntity, "", err)
} else { } else {

View file

@ -511,6 +511,8 @@ func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteA
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit())) ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))
case models.IsErrNameReserved(err): case models.IsErrNameReserved(err):
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name)) ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))
case models.IsErrNameCharsNotAllowed(err):
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The username '%s' contains invalid characters.", err.(models.ErrNameCharsNotAllowed).Name))
case models.IsErrNamePatternNotAllowed(err): case models.IsErrNamePatternNotAllowed(err):
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern)) ctx.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern))
default: default:

View file

@ -928,6 +928,7 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au
LoginName: gothUser.(goth.User).UserID, LoginName: gothUser.(goth.User).UserID,
} }
//nolint: dupl
if err := models.CreateUser(u); err != nil { if err := models.CreateUser(u); err != nil {
switch { switch {
case models.IsErrUserAlreadyExist(err): case models.IsErrUserAlreadyExist(err):
@ -942,6 +943,9 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au
case models.IsErrNamePatternNotAllowed(err): case models.IsErrNamePatternNotAllowed(err):
ctx.Data["Err_UserName"] = true ctx.Data["Err_UserName"] = true
ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplLinkAccount, &form) ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplLinkAccount, &form)
case models.IsErrNameCharsNotAllowed(err):
ctx.Data["Err_UserName"] = true
ctx.RenderWithErr(ctx.Tr("user.form.name_chars_not_allowed", err.(models.ErrNameCharsNotAllowed).Name), tplLinkAccount, &form)
default: default:
ctx.ServerError("CreateUser", err) ctx.ServerError("CreateUser", err)
} }

View file

@ -400,6 +400,7 @@ func RegisterOpenIDPost(ctx *context.Context, cpt *captcha.Captcha, form auth.Si
Passwd: password, Passwd: password,
IsActive: !setting.Service.RegisterEmailConfirm, IsActive: !setting.Service.RegisterEmailConfirm,
} }
//nolint: dupl
if err := models.CreateUser(u); err != nil { if err := models.CreateUser(u); err != nil {
switch { switch {
case models.IsErrUserAlreadyExist(err): case models.IsErrUserAlreadyExist(err):
@ -414,6 +415,9 @@ func RegisterOpenIDPost(ctx *context.Context, cpt *captcha.Captcha, form auth.Si
case models.IsErrNamePatternNotAllowed(err): case models.IsErrNamePatternNotAllowed(err):
ctx.Data["Err_UserName"] = true ctx.Data["Err_UserName"] = true
ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplSignUpOID, &form) ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplSignUpOID, &form)
case models.IsErrNameCharsNotAllowed(err):
ctx.Data["Err_UserName"] = true
ctx.RenderWithErr(ctx.Tr("user.form.name_chars_not_allowed", err.(models.ErrNameCharsNotAllowed).Name), tplSignUpOID, &form)
default: default:
ctx.ServerError("CreateUser", err) ctx.ServerError("CreateUser", err)
} }

View file

@ -58,6 +58,9 @@ func handleUsernameChange(ctx *context.Context, newName string) {
case models.IsErrNamePatternNotAllowed(err): case models.IsErrNamePatternNotAllowed(err):
ctx.Flash.Error(ctx.Tr("user.form.name_pattern_not_allowed", newName)) ctx.Flash.Error(ctx.Tr("user.form.name_pattern_not_allowed", newName))
ctx.Redirect(setting.AppSubURL + "/user/settings") ctx.Redirect(setting.AppSubURL + "/user/settings")
case models.IsErrNameCharsNotAllowed(err):
ctx.Flash.Error(ctx.Tr("user.form.name_chars_not_allowed", newName))
ctx.Redirect(setting.AppSubURL + "/user/settings")
default: default:
ctx.ServerError("ChangeUserName", err) ctx.ServerError("ChangeUserName", err)
} }