Skip to content

Commit 302ab38

Browse files
committed
Gitealize nicknames from Blender ID
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 Reviewed on: #3
1 parent 5360cab commit 302ab38

File tree

6 files changed

+117
-1
lines changed

6 files changed

+117
-1
lines changed

assets/go-licenses.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ require (
7979
github.com/microcosm-cc/bluemonday v1.0.25
8080
github.com/minio/minio-go/v7 v7.0.52
8181
github.com/minio/sha256-simd v1.0.0
82+
github.com/mozillazg/go-unidecode v0.2.0
8283
github.com/msteinert/pam v1.1.0
8384
github.com/nektos/act v0.2.45
8485
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
829829
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
830830
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
831831
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
832+
github.com/mozillazg/go-unidecode v0.2.0 h1:vFGEzAH9KSwyWmXCOblazEWDh7fOkpmy/Z4ArmamSUc=
833+
github.com/mozillazg/go-unidecode v0.2.0/go.mod h1:zB48+/Z5toiRolOZy9ksLryJ976VIwmDmpQ2quyt1aA=
832834
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
833835
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM=
834836
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=

services/auth/source/oauth2/blenderid/blenderid.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func userFromReader(r io.Reader, user *goth.User) error {
164164
}
165165
user.Email = u.Email
166166
user.Name = u.Name
167-
user.NickName = u.NickName
167+
user.NickName = gitealizeUsername(u.NickName)
168168
user.UserID = strconv.Itoa(u.ID)
169169
user.AvatarURL = fmt.Sprintf("https://id.blender.org/api/user/%s/avatar", user.UserID)
170170
return nil
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
package blenderid
4+
5+
import (
6+
"regexp"
7+
"strings"
8+
9+
"code.gitea.io/gitea/models/user"
10+
11+
"github.com/mozillazg/go-unidecode"
12+
)
13+
14+
var (
15+
reInvalidCharsPattern = regexp.MustCompile(`[^\da-zA-Z.\w-]+`)
16+
17+
// Consecutive non-alphanumeric at start:
18+
reConsPrefix = regexp.MustCompile(`^[._-]+`)
19+
reConsSuffix = regexp.MustCompile(`[._-]+$`)
20+
reConsInfix = regexp.MustCompile(`[._-]{2,}`)
21+
)
22+
23+
// gitealizeUsername turns a valid Blender ID nickname into a valid Gitea username.
24+
func gitealizeUsername(bidNickname string) string {
25+
// Remove accents and other non-ASCIIness.
26+
asciiUsername := unidecode.Unidecode(bidNickname)
27+
asciiUsername = strings.TrimSpace(asciiUsername)
28+
asciiUsername = strings.ReplaceAll(asciiUsername, " ", "_")
29+
30+
err := user.IsUsableUsername(asciiUsername)
31+
if err == nil && len(asciiUsername) <= 40 {
32+
return asciiUsername
33+
}
34+
35+
newUsername := asciiUsername
36+
newUsername = reInvalidCharsPattern.ReplaceAllString(newUsername, "_")
37+
newUsername = reConsPrefix.ReplaceAllString(newUsername, "")
38+
newUsername = reConsSuffix.ReplaceAllString(newUsername, "")
39+
newUsername = reConsInfix.ReplaceAllStringFunc(
40+
newUsername,
41+
func(match string) string {
42+
firstRune := []rune(match)[0]
43+
return string(firstRune)
44+
})
45+
46+
if newUsername == "" {
47+
// Everything was stripped and nothing was left. Better to keep as-is and
48+
// just let Gitea bork on it.
49+
return asciiUsername
50+
}
51+
52+
// This includes a test for reserved names, which are easily circumvented by
53+
// appending another character.
54+
if user.IsUsableUsername(newUsername) != nil {
55+
if len(newUsername) > 39 {
56+
return newUsername[:39] + "2"
57+
}
58+
return newUsername + "2"
59+
}
60+
61+
if len(newUsername) > 40 {
62+
return newUsername[:40]
63+
}
64+
return newUsername
65+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
package blenderid
4+
5+
import "testing"
6+
7+
func Test_gitealizeUsername(t *testing.T) {
8+
tests := []struct {
9+
name string
10+
bidNickname string
11+
want string
12+
}{
13+
{"empty", "", ""},
14+
{"underscore", "_", "_"},
15+
{"reserved-name", "ghost", "ghost2"}, // Reserved name in Gitea.
16+
{"short", "x", "x"},
17+
{"simple", "simple", "simple"},
18+
{"start-bad", "____startbad", "startbad"},
19+
{"end-bad", "endbad___", "endbad"},
20+
{"mid-bad-1", "mid__bad", "mid_bad"},
21+
{"mid-bad-2", "user_.-name", "user_name"},
22+
{"plus-mid-single", "RT2+356", "RT2_356"},
23+
{"plus-mid-many", "RT2+++356", "RT2_356"},
24+
{"plus-end", "RT2356+", "RT2356"},
25+
{
26+
"too-long", // # Max username length is 40:
27+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
28+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
29+
},
30+
{"accented-latin", "Ümlaut-Đenja", "Umlaut-Denja"},
31+
{"thai", "แบบไทย", "aebbaithy"},
32+
{"mandarin", "普通话", "Pu_Tong_Hua"},
33+
{"cyrillic", "ћирилица", "tshirilitsa"},
34+
{"all-bad", "------", "------"},
35+
}
36+
for _, tt := range tests {
37+
t.Run(tt.name, func(t *testing.T) {
38+
if got := gitealizeUsername(tt.bidNickname); got != tt.want {
39+
t.Errorf("gitealizeUsername() = %v, want %v", got, tt.want)
40+
}
41+
})
42+
}
43+
}

0 commit comments

Comments
 (0)