mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-29 00:02:33 -04:00
Compare commits
7 Commits
91aa263225
...
92711b001e
Author | SHA1 | Date | |
---|---|---|---|
|
92711b001e | ||
|
54acf7b0d4 | ||
|
657b23d635 | ||
|
9b418158b9 | ||
|
3ba1216b9d | ||
|
361839fb1c | ||
|
12c0487e01 |
@ -1529,6 +1529,10 @@ LEVEL = Info
|
|||||||
;; userid = use the userid / sub attribute
|
;; userid = use the userid / sub attribute
|
||||||
;; nickname = use the nickname attribute
|
;; nickname = use the nickname attribute
|
||||||
;; email = use the username part of the email 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
|
;USERNAME = nickname
|
||||||
;;
|
;;
|
||||||
;; Update avatar if available from oauth2 provider.
|
;; 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)
|
- `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.
|
- `ENABLE_AUTO_REGISTRATION`: **false**: Automatically create user accounts for new oauth2 users.
|
||||||
- `USERNAME`: **nickname**: The source of the username for new oauth2 accounts:
|
- `USERNAME`: **nickname**: The source of the username for new oauth2 accounts:
|
||||||
- userid - use the userid / sub attribute
|
- `userid` - use the userid / sub attribute
|
||||||
- nickname - use the nickname attribute
|
- `nickname` - use the nickname attribute
|
||||||
- email - use the username part of the email 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.
|
- `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:
|
- `ACCOUNT_LINKING`: **login**: How to handle if an account / email already exists:
|
||||||
- disabled - show an error
|
- disabled - show an error
|
||||||
|
@ -10,8 +10,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
_ "image/jpeg" // Needed for jpeg support
|
_ "image/jpeg" // Needed for jpeg support
|
||||||
|
|
||||||
@ -29,6 +31,9 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/validation"
|
"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"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -515,6 +520,26 @@ func GetUserSalt() (string, error) {
|
|||||||
return hex.EncodeToString(rBytes), nil
|
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 (
|
var (
|
||||||
reservedUsernames = []string{
|
reservedUsernames = []string{
|
||||||
".",
|
".",
|
||||||
|
@ -544,3 +544,31 @@ func Test_ValidateUser(t *testing.T) {
|
|||||||
assert.EqualValues(t, expected, err == nil, fmt.Sprintf("case: %+v", kase))
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -852,11 +852,14 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
// FIXME: the use of "mode" is quite dirty and hacky, for example: what is a "document"? how should it be rendered?
|
if ctx.Metas == nil {
|
||||||
// The "mode" approach should be refactored to some other more clear&reliable way.
|
|
||||||
if ctx.Metas == nil || (ctx.Metas["mode"] == "document" && !ctx.IsWiki) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: the use of "mode" is quite dirty and hacky, for example: what is a "document"? how should it be rendered?
|
||||||
|
// The "mode" approach should be refactored to some other more clear&reliable way.
|
||||||
|
crossLinkOnly := (ctx.Metas["mode"] == "document" && !ctx.IsWiki)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
found bool
|
found bool
|
||||||
ref *references.RenderizableReference
|
ref *references.RenderizableReference
|
||||||
@ -870,7 +873,7 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
// Repos with external issue trackers might still need to reference local PRs
|
// Repos with external issue trackers might still need to reference local PRs
|
||||||
// We need to concern with the first one that shows up in the text, whichever it is
|
// We need to concern with the first one that shows up in the text, whichever it is
|
||||||
isNumericStyle := ctx.Metas["style"] == "" || ctx.Metas["style"] == IssueNameStyleNumeric
|
isNumericStyle := ctx.Metas["style"] == "" || ctx.Metas["style"] == IssueNameStyleNumeric
|
||||||
foundNumeric, refNumeric := references.FindRenderizableReferenceNumeric(node.Data, hasExtTrackFormat && !isNumericStyle)
|
foundNumeric, refNumeric := references.FindRenderizableReferenceNumeric(node.Data, hasExtTrackFormat && !isNumericStyle, crossLinkOnly)
|
||||||
|
|
||||||
switch ctx.Metas["style"] {
|
switch ctx.Metas["style"] {
|
||||||
case "", IssueNameStyleNumeric:
|
case "", IssueNameStyleNumeric:
|
||||||
|
@ -561,11 +561,16 @@ func TestPostProcess_RenderDocument(t *testing.T) {
|
|||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res.String()))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue index shouldn't be post processing in an document.
|
// Issue index shouldn't be post processing in a document.
|
||||||
test(
|
test(
|
||||||
"#1",
|
"#1",
|
||||||
"#1")
|
"#1")
|
||||||
|
|
||||||
|
// But cross-referenced issue index should work.
|
||||||
|
test(
|
||||||
|
"go-gitea/gitea#12345",
|
||||||
|
`<a href="`+util.URLJoin(markup.TestAppURL, "go-gitea", "gitea", "issues", "12345")+`" class="ref-issue">go-gitea/gitea#12345</a>`)
|
||||||
|
|
||||||
// Test that other post processing still works.
|
// Test that other post processing still works.
|
||||||
test(
|
test(
|
||||||
":gitea:",
|
":gitea:",
|
||||||
|
@ -331,8 +331,11 @@ func FindAllIssueReferences(content string) []IssueReference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FindRenderizableReferenceNumeric returns the first unvalidated reference found in a string.
|
// FindRenderizableReferenceNumeric returns the first unvalidated reference found in a string.
|
||||||
func FindRenderizableReferenceNumeric(content string, prOnly bool) (bool, *RenderizableReference) {
|
func FindRenderizableReferenceNumeric(content string, prOnly, crossLinkOnly bool) (bool, *RenderizableReference) {
|
||||||
match := issueNumericPattern.FindStringSubmatchIndex(content)
|
var match []int
|
||||||
|
if !crossLinkOnly {
|
||||||
|
match = issueNumericPattern.FindStringSubmatchIndex(content)
|
||||||
|
}
|
||||||
if match == nil {
|
if match == nil {
|
||||||
if match = crossReferenceIssueNumericPattern.FindStringSubmatchIndex(content); match == nil {
|
if match = crossReferenceIssueNumericPattern.FindStringSubmatchIndex(content); match == nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
@ -21,7 +21,7 @@ const (
|
|||||||
OAuth2UsernameUserid OAuth2UsernameType = "userid"
|
OAuth2UsernameUserid OAuth2UsernameType = "userid"
|
||||||
// OAuth2UsernameNickname oauth2 nickname field will be used as gitea name
|
// OAuth2UsernameNickname oauth2 nickname field will be used as gitea name
|
||||||
OAuth2UsernameNickname OAuth2UsernameType = "nickname"
|
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"
|
OAuth2UsernameEmail OAuth2UsernameType = "email"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -3532,8 +3532,8 @@ runs.actors_no_select = All actors
|
|||||||
runs.status_no_select = All status
|
runs.status_no_select = All status
|
||||||
runs.no_results = No results matched.
|
runs.no_results = No results matched.
|
||||||
runs.no_workflows = There are no workflows yet.
|
runs.no_workflows = There are no workflows yet.
|
||||||
runs.no_workflows.quick_start = Don't know how to start with Gitea Action? See <a target="_blank" rel="noopener noreferrer" href="%s">the quick start guide</a>.
|
runs.no_workflows.quick_start = Don't know how to start with Gitea Actions? See <a target="_blank" rel="noopener noreferrer" href="%s">the quick start guide</a>.
|
||||||
runs.no_workflows.documentation = For more information on the Gitea Action, see <a target="_blank" rel="noopener noreferrer" href="%s">the documentation</a>.
|
runs.no_workflows.documentation = For more information on Gitea Actions, see <a target="_blank" rel="noopener noreferrer" href="%s">the documentation</a>.
|
||||||
runs.no_runs = The workflow has no runs yet.
|
runs.no_runs = The workflow has no runs yet.
|
||||||
runs.empty_commit_message = (empty commit message)
|
runs.empty_commit_message = (empty commit message)
|
||||||
|
|
||||||
@ -3552,7 +3552,7 @@ variables.none = There are no variables yet.
|
|||||||
variables.deletion = Remove variable
|
variables.deletion = Remove variable
|
||||||
variables.deletion.description = Removing a variable is permanent and cannot be undone. Continue?
|
variables.deletion.description = Removing a variable is permanent and cannot be undone. Continue?
|
||||||
variables.description = Variables will be passed to certain actions and cannot be read otherwise.
|
variables.description = Variables will be passed to certain actions and cannot be read otherwise.
|
||||||
variables.id_not_exist = Variable with id %d not exists.
|
variables.id_not_exist = Variable with ID %d does not exist.
|
||||||
variables.edit = Edit Variable
|
variables.edit = Edit Variable
|
||||||
variables.deletion.failed = Failed to remove variable.
|
variables.deletion.failed = Failed to remove variable.
|
||||||
variables.deletion.success = The variable has been removed.
|
variables.deletion.success = The variable has been removed.
|
||||||
|
@ -368,14 +368,14 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
|
|||||||
return setting.AppSubURL + "/"
|
return setting.AppSubURL + "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUserName(gothUser *goth.User) string {
|
func getUserName(gothUser *goth.User) (string, error) {
|
||||||
switch setting.OAuth2Client.Username {
|
switch setting.OAuth2Client.Username {
|
||||||
case setting.OAuth2UsernameEmail:
|
case setting.OAuth2UsernameEmail:
|
||||||
return strings.Split(gothUser.Email, "@")[0]
|
return user_model.NormalizeUserName(strings.Split(gothUser.Email, "@")[0])
|
||||||
case setting.OAuth2UsernameNickname:
|
case setting.OAuth2UsernameNickname:
|
||||||
return gothUser.NickName
|
return user_model.NormalizeUserName(gothUser.NickName)
|
||||||
default: // OAuth2UsernameUserid
|
default: // OAuth2UsernameUserid
|
||||||
return gothUser.UserID
|
return gothUser.UserID, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,11 @@ func LinkAccount(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gu, _ := gothUser.(goth.User)
|
gu, _ := gothUser.(goth.User)
|
||||||
uname := getUserName(&gu)
|
uname, err := getUserName(&gu)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("UserSignIn", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
email := gu.Email
|
email := gu.Email
|
||||||
ctx.Data["user_name"] = uname
|
ctx.Data["user_name"] = uname
|
||||||
ctx.Data["email"] = email
|
ctx.Data["email"] = email
|
||||||
|
@ -970,8 +970,13 @@ func SignInOAuthCallback(ctx *context.Context) {
|
|||||||
ctx.ServerError("CreateUser", err)
|
ctx.ServerError("CreateUser", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
uname, err := getUserName(&gothUser)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("UserSignIn", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
u = &user_model.User{
|
u = &user_model.User{
|
||||||
Name: getUserName(&gothUser),
|
Name: uname,
|
||||||
FullName: gothUser.Name,
|
FullName: gothUser.Name,
|
||||||
Email: gothUser.Email,
|
Email: gothUser.Email,
|
||||||
LoginType: auth.OAuth2,
|
LoginType: auth.OAuth2,
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix) | Safe}}
|
{{ctx.Locale.Tr "settings.added_on" (DateTime "short" .CreatedUnix) | Safe}}
|
||||||
</span>
|
</span>
|
||||||
<button class="btn interact-bg gt-p-3 show-modal"
|
<button class="btn interact-bg gt-p-3 show-modal"
|
||||||
data-tooltip-content="{{ctx.Locale.Tr "variables.edit"}}"
|
data-tooltip-content="{{ctx.Locale.Tr "actions.variables.edit"}}"
|
||||||
data-modal="#edit-variable-modal"
|
data-modal="#edit-variable-modal"
|
||||||
data-modal-form.action="{{$.Link}}/{{.ID}}/edit"
|
data-modal-form.action="{{$.Link}}/{{.ID}}/edit"
|
||||||
data-modal-header="{{ctx.Locale.Tr "actions.variables.edit"}}"
|
data-modal-header="{{ctx.Locale.Tr "actions.variables.edit"}}"
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
|
|
||||||
#issue-list .flex-item-title .labels-list {
|
#issue-list .flex-item-title .labels-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,10 @@
|
|||||||
|
|
||||||
.repository.wiki .markup {
|
.repository.wiki .markup {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
min-height: 340px;
|
}
|
||||||
|
|
||||||
|
.repository.wiki .markup[data-tab-panel="markdown-previewer"] {
|
||||||
|
min-height: 340px; /* This height matches the markdown editor's height */
|
||||||
}
|
}
|
||||||
|
|
||||||
.repository.wiki .wiki-content-parts .markup {
|
.repository.wiki .wiki-content-parts .markup {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user