mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 02:04:11 +01:00 
			
		
		
		
	Normalize oauth email username (#28561)
This commit is contained in:
		
							parent
							
								
									657b23d635
								
							
						
					
					
						commit
						54acf7b0d4
					
				@ -1529,6 +1529,10 @@ LEVEL = Info
 | 
			
		||||
;; userid = use the userid / sub attribute
 | 
			
		||||
;; nickname = use the nickname attribute
 | 
			
		||||
;; email = use the username part of the email attribute
 | 
			
		||||
;; Note: `nickname` and `email` options will normalize input strings using the following criteria:
 | 
			
		||||
;; - diacritics are removed
 | 
			
		||||
;; - the characters in the set `['´\x60]` are removed
 | 
			
		||||
;; - the characters in the set `[\s~+]` are replaced with `-`
 | 
			
		||||
;USERNAME = nickname
 | 
			
		||||
;;
 | 
			
		||||
;; Update avatar if available from oauth2 provider.
 | 
			
		||||
 | 
			
		||||
@ -596,9 +596,13 @@ And the following unique queues:
 | 
			
		||||
- `OPENID_CONNECT_SCOPES`: **_empty_**: List of additional openid connect scopes. (`openid` is implicitly added)
 | 
			
		||||
- `ENABLE_AUTO_REGISTRATION`: **false**: Automatically create user accounts for new oauth2 users.
 | 
			
		||||
- `USERNAME`: **nickname**: The source of the username for new oauth2 accounts:
 | 
			
		||||
  - userid - use the userid / sub attribute
 | 
			
		||||
  - nickname - use the nickname attribute
 | 
			
		||||
  - email - use the username part of the email attribute
 | 
			
		||||
  - `userid` - use the userid / sub attribute
 | 
			
		||||
  - `nickname` - use the nickname attribute
 | 
			
		||||
  - `email` - use the username part of the email attribute
 | 
			
		||||
  - Note: `nickname` and `email` options will normalize input strings using the following criteria:
 | 
			
		||||
    - diacritics are removed
 | 
			
		||||
    - the characters in the set `['´\x60]` are removed
 | 
			
		||||
    - the characters in the set `[\s~+]` are replaced with `-`
 | 
			
		||||
- `UPDATE_AVATAR`: **false**: Update avatar if available from oauth2 provider. Update will be performed on each login.
 | 
			
		||||
- `ACCOUNT_LINKING`: **login**: How to handle if an account / email already exists:
 | 
			
		||||
  - disabled - show an error
 | 
			
		||||
 | 
			
		||||
@ -10,8 +10,10 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
	"unicode"
 | 
			
		||||
 | 
			
		||||
	_ "image/jpeg" // Needed for jpeg support
 | 
			
		||||
 | 
			
		||||
@ -29,6 +31,9 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
	"code.gitea.io/gitea/modules/validation"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/text/runes"
 | 
			
		||||
	"golang.org/x/text/transform"
 | 
			
		||||
	"golang.org/x/text/unicode/norm"
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -515,6 +520,26 @@ func GetUserSalt() (string, error) {
 | 
			
		||||
	return hex.EncodeToString(rBytes), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Note: The set of characters here can safely expand without a breaking change,
 | 
			
		||||
// but characters removed from this set can cause user account linking to break
 | 
			
		||||
var (
 | 
			
		||||
	customCharsReplacement    = strings.NewReplacer("Æ", "AE")
 | 
			
		||||
	removeCharsRE             = regexp.MustCompile(`['´\x60]`)
 | 
			
		||||
	removeDiacriticsTransform = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
 | 
			
		||||
	replaceCharsHyphenRE      = regexp.MustCompile(`[\s~+]`)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// normalizeUserName returns a string with single-quotes and diacritics
 | 
			
		||||
// removed, and any other non-supported username characters replaced with
 | 
			
		||||
// a `-` character
 | 
			
		||||
func NormalizeUserName(s string) (string, error) {
 | 
			
		||||
	strDiacriticsRemoved, n, err := transform.String(removeDiacriticsTransform, customCharsReplacement.Replace(s))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("Failed to normalize character `%v` in provided username `%v`", s[n], s)
 | 
			
		||||
	}
 | 
			
		||||
	return replaceCharsHyphenRE.ReplaceAllLiteralString(removeCharsRE.ReplaceAllLiteralString(strDiacriticsRemoved, ""), "-"), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	reservedUsernames = []string{
 | 
			
		||||
		".",
 | 
			
		||||
 | 
			
		||||
@ -544,3 +544,31 @@ func Test_ValidateUser(t *testing.T) {
 | 
			
		||||
		assert.EqualValues(t, expected, err == nil, fmt.Sprintf("case: %+v", kase))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_NormalizeUserFromEmail(t *testing.T) {
 | 
			
		||||
	testCases := []struct {
 | 
			
		||||
		Input             string
 | 
			
		||||
		Expected          string
 | 
			
		||||
		IsNormalizedValid bool
 | 
			
		||||
	}{
 | 
			
		||||
		{"test", "test", true},
 | 
			
		||||
		{"Sinéad.O'Connor", "Sinead.OConnor", true},
 | 
			
		||||
		{"Æsir", "AEsir", true},
 | 
			
		||||
		// \u00e9\u0065\u0301
 | 
			
		||||
		{"éé", "ee", true},
 | 
			
		||||
		{"Awareness Hub", "Awareness-Hub", true},
 | 
			
		||||
		{"double__underscore", "double__underscore", false}, // We should consider squashing double non-alpha characters
 | 
			
		||||
		{".bad.", ".bad.", false},
 | 
			
		||||
		{"new😀user", "new😀user", false}, // No plans to support
 | 
			
		||||
	}
 | 
			
		||||
	for _, testCase := range testCases {
 | 
			
		||||
		normalizedName, err := user_model.NormalizeUserName(testCase.Input)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.EqualValues(t, testCase.Expected, normalizedName)
 | 
			
		||||
		if testCase.IsNormalizedValid {
 | 
			
		||||
			assert.NoError(t, user_model.IsUsableUsername(normalizedName))
 | 
			
		||||
		} else {
 | 
			
		||||
			assert.Error(t, user_model.IsUsableUsername(normalizedName))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ const (
 | 
			
		||||
	OAuth2UsernameUserid OAuth2UsernameType = "userid"
 | 
			
		||||
	// OAuth2UsernameNickname oauth2 nickname field will be used as gitea name
 | 
			
		||||
	OAuth2UsernameNickname OAuth2UsernameType = "nickname"
 | 
			
		||||
	// OAuth2UsernameEmail username of oauth2 email filed will be used as gitea name
 | 
			
		||||
	// OAuth2UsernameEmail username of oauth2 email field will be used as gitea name
 | 
			
		||||
	OAuth2UsernameEmail OAuth2UsernameType = "email"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -368,14 +368,14 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
 | 
			
		||||
	return setting.AppSubURL + "/"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getUserName(gothUser *goth.User) string {
 | 
			
		||||
func getUserName(gothUser *goth.User) (string, error) {
 | 
			
		||||
	switch setting.OAuth2Client.Username {
 | 
			
		||||
	case setting.OAuth2UsernameEmail:
 | 
			
		||||
		return strings.Split(gothUser.Email, "@")[0]
 | 
			
		||||
		return user_model.NormalizeUserName(strings.Split(gothUser.Email, "@")[0])
 | 
			
		||||
	case setting.OAuth2UsernameNickname:
 | 
			
		||||
		return gothUser.NickName
 | 
			
		||||
		return user_model.NormalizeUserName(gothUser.NickName)
 | 
			
		||||
	default: // OAuth2UsernameUserid
 | 
			
		||||
		return gothUser.UserID
 | 
			
		||||
		return gothUser.UserID, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -55,7 +55,11 @@ func LinkAccount(ctx *context.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gu, _ := gothUser.(goth.User)
 | 
			
		||||
	uname := getUserName(&gu)
 | 
			
		||||
	uname, err := getUserName(&gu)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("UserSignIn", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	email := gu.Email
 | 
			
		||||
	ctx.Data["user_name"] = uname
 | 
			
		||||
	ctx.Data["email"] = email
 | 
			
		||||
 | 
			
		||||
@ -970,8 +970,13 @@ func SignInOAuthCallback(ctx *context.Context) {
 | 
			
		||||
				ctx.ServerError("CreateUser", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			uname, err := getUserName(&gothUser)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.ServerError("UserSignIn", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			u = &user_model.User{
 | 
			
		||||
				Name:        getUserName(&gothUser),
 | 
			
		||||
				Name:        uname,
 | 
			
		||||
				FullName:    gothUser.Name,
 | 
			
		||||
				Email:       gothUser.Email,
 | 
			
		||||
				LoginType:   auth.OAuth2,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user