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 { | ||||
} | } |
"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" | ||||
} | } | ||||
} | } | ||||
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) { | ||||
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) | ||||
// 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} | ||||
} | } | ||||
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), | |||||
Name: login, | |||||
Email: login, | |||||
LowerName: strings.ToLower(username), | |||||
Name: username, | |||||
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) |
"image/png" | "image/png" | ||||
"os" | "os" | ||||
"path/filepath" | "path/filepath" | ||||
"regexp" | |||||
"strconv" | "strconv" | ||||
"strings" | "strings" | ||||
"time" | "time" | ||||
// 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. | ||||
// 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) | ||||
} | } | ||||
) | ) | ||||
// 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: | ||||
}) | }) | ||||
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) | |||||
} | } |
) | ) | ||||
// Auth not supported lack of pam tag | // Auth not supported lack of pam tag | ||||
func Auth(serviceName, userName, passwd string) error { | |||||
return errors.New("PAM not supported") | |||||
func Auth(serviceName, userName, passwd string) (string, error) { | |||||
return "", errors.New("PAM not supported") | |||||
} | } |
// Create org. | // Create org. | ||||
org := &models.User{ | org := &models.User{ | ||||
Name: "All repo", | |||||
Name: "All_repo", | |||||
IsActive: true, | IsActive: true, | ||||
Type: models.UserTypeOrganization, | Type: models.UserTypeOrganization, | ||||
Visibility: structs.VisibleTypePublic, | Visibility: structs.VisibleTypePublic, |
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 |
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) | ||||
} | } |
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 { |
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 { |
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 { |
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: |
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): | ||||
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) | ||||
} | } |
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): | ||||
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) | ||||
} | } |
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) | ||||
} | } |