Skip to content

Commit

Permalink
Gitealize nicknames from Blender ID
Browse files Browse the repository at this point in the history
Blender ID allows a wider range of characters in the nickname than Gitea
allows. Before using a Blender ID nickname as a Gitea username, it needs
to be massaged into a valid form.

This was already done in the Blender ID-to-Gitea webhook for username
changes, and this PR introduces it in Gitea itself for the registration
flow.

The implementation follows what the webhook code[1] does, except it's
simpler because it can use built-in Gitea functionality.

This fixes https://projects.blender.org/blender/blender/issues/111937

[1]: https://projects.blender.org/infrastructure/gitea-blenderid-webhook/src/branch/main/gitea_blenderid_webhook/gitea_users.py
  • Loading branch information
drsybren committed Sep 7, 2023
1 parent 5360cab commit 417887d
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 1 deletion.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ require (
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mozillazg/go-unidecode v0.2.0 // indirect
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mozillazg/go-unidecode v0.2.0 h1:vFGEzAH9KSwyWmXCOblazEWDh7fOkpmy/Z4ArmamSUc=
github.com/mozillazg/go-unidecode v0.2.0/go.mod h1:zB48+/Z5toiRolOZy9ksLryJ976VIwmDmpQ2quyt1aA=
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM=
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
Expand Down
2 changes: 1 addition & 1 deletion services/auth/source/oauth2/blenderid/blenderid.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func userFromReader(r io.Reader, user *goth.User) error {
}
user.Email = u.Email
user.Name = u.Name
user.NickName = u.NickName
user.NickName = gitealizeUsername(u.NickName)
user.UserID = strconv.Itoa(u.ID)
user.AvatarURL = fmt.Sprintf("https://id.blender.org/api/user/%s/avatar", user.UserID)
return nil
Expand Down
64 changes: 64 additions & 0 deletions services/auth/source/oauth2/blenderid/gitealize_usernames.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package blenderid

// SPDX-License-Identifier: MIT

import (
"regexp"
"strings"

"code.gitea.io/gitea/models/user"
"github.com/mozillazg/go-unidecode"
)

var (
reInvalidCharsPattern = regexp.MustCompile(`[^\da-zA-Z.\w-]+`)

// Consecutive non-alphanumeric at start:
reConsPrefix = regexp.MustCompile(`^[._-]+`)
reConsSuffix = regexp.MustCompile(`[._-]+$`)
reConsInfix = regexp.MustCompile(`[._-]{2,}`)
)

// gitealizeUsername turns a valid Blender ID nickname into a valid Gitea username.
func gitealizeUsername(bidNickname string) string {
// Remove accents and other non-ASCIIness.
asciiUsername := unidecode.Unidecode(bidNickname)
asciiUsername = strings.TrimSpace(asciiUsername)
asciiUsername = strings.ReplaceAll(asciiUsername, " ", "_")

err := user.IsUsableUsername(asciiUsername)
if err == nil && len(asciiUsername) <= 40 {
return asciiUsername
}

newUsername := asciiUsername
newUsername = reInvalidCharsPattern.ReplaceAllString(newUsername, "_")
newUsername = reConsPrefix.ReplaceAllString(newUsername, "")
newUsername = reConsSuffix.ReplaceAllString(newUsername, "")
newUsername = reConsInfix.ReplaceAllStringFunc(
newUsername,
func(match string) string {
firstRune := []rune(match)[0]
return string(firstRune)
})

if newUsername == "" {
// Everything was stripped and nothing was left. Better to keep as-is and
// just let Gitea bork on it.
return asciiUsername
}

// This includes a test for reserved names, which are easily circumvented by
// appending another character.
if user.IsUsableUsername(newUsername) != nil {
if len(newUsername) > 39 {
return newUsername[:39] + "2"
}
return newUsername + "2"
}

if len(newUsername) > 40 {
return newUsername[:40]
}
return newUsername
}
41 changes: 41 additions & 0 deletions services/auth/source/oauth2/blenderid/gitealize_usernames_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package blenderid

import "testing"

func Test_gitealizeUsername(t *testing.T) {
tests := []struct {
name string
bidNickname string
want string
}{
{"empty", "", ""},
{"underscore", "_", "_"},
{"reserved-name", "ghost", "ghost2"}, // Reserved name in Gitea.
{"short", "x", "x"},
{"simple", "simple", "simple"},
{"start-bad", "____startbad", "startbad"},
{"end-bad", "endbad___", "endbad"},
{"mid-bad-1", "mid__bad", "mid_bad"},
{"mid-bad-2", "user_.-name", "user_name"},
{"plus-mid-single", "RT2+356", "RT2_356"},
{"plus-mid-many", "RT2+++356", "RT2_356"},
{"plus-end", "RT2356+", "RT2356"},
{
"too-long", // # Max username length is 40:
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
{"accented-latin", "Ümlaut-Đenja", "Umlaut-Denja"},
{"thai", "แบบไทย", "aebbaithy"},
{"mandarin", "普通话", "Pu_Tong_Hua"},
{"cyrillic", "ћирилица", "tshirilitsa"},
{"all-bad", "------", "------"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := gitealizeUsername(tt.bidNickname); got != tt.want {
t.Errorf("gitealizeUsername() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit 417887d

Please sign in to comment.