mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-28 11:14:54 -04:00
feat: add option to use preferred_username claim when registering users via oauth2 (#12504)
This is a continuation of #3346 based on the advise of https://codeberg.org/forgejo/forgejo/issues/1452#issuecomment-14591307. fixes: #1452 docs: https://codeberg.org/forgejo/docs/pulls/1938 Extends the `oauth2_client` `USERNAME` setting to be able to use the `preferred_username` claim. Co-authored-by: thepaperpilot <thepaperpilot@gmail.com> Co-authored-by: Anthony Lawn <thepaperpilot@gmail.com> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12504 Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
parent
cf087a2f12
commit
d130e1ee94
4 changed files with 213 additions and 1 deletions
|
|
@ -22,11 +22,13 @@ const (
|
|||
OAuth2UsernameNickname OAuth2UsernameType = "nickname"
|
||||
// OAuth2UsernameEmail username of oauth2 email field will be used as gitea name
|
||||
OAuth2UsernameEmail OAuth2UsernameType = "email"
|
||||
// @OAuth2UsernamePreferredUsername oauth2 preferred_username field will be used as gitea name
|
||||
OAuth2UsernamePreferredUsername OAuth2UsernameType = "preferred_username"
|
||||
)
|
||||
|
||||
func (username OAuth2UsernameType) isValid() bool {
|
||||
switch username {
|
||||
case OAuth2UsernameUserid, OAuth2UsernameNickname, OAuth2UsernameEmail:
|
||||
case OAuth2UsernameUserid, OAuth2UsernameNickname, OAuth2UsernameEmail, OAuth2UsernamePreferredUsername:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -401,6 +401,12 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
|
|||
|
||||
func getUserName(gothUser *goth.User) (string, error) {
|
||||
switch setting.OAuth2Client.Username {
|
||||
case setting.OAuth2UsernamePreferredUsername:
|
||||
username := gothUser.RawData["preferred_username"].(string)
|
||||
if strings.Contains(username, "@") {
|
||||
return user_model.NormalizeUserName(strings.Split(username, "@")[0])
|
||||
}
|
||||
return user_model.NormalizeUserName(username)
|
||||
case setting.OAuth2UsernameEmail:
|
||||
return user_model.NormalizeUserName(strings.Split(gothUser.Email, "@")[0])
|
||||
case setting.OAuth2UsernameNickname:
|
||||
|
|
|
|||
|
|
@ -1117,6 +1117,8 @@ func SignInOAuthCallback(ctx *context.Context) {
|
|||
}
|
||||
if setting.OAuth2Client.Username == setting.OAuth2UsernameNickname && gothUser.NickName == "" {
|
||||
missingFields = append(missingFields, "nickname")
|
||||
} else if setting.OAuth2Client.Username == setting.OAuth2UsernamePreferredUsername && (gothUser.RawData["preferred_username"] == nil || gothUser.RawData["preferred_username"].(string) == "") {
|
||||
missingFields = append(missingFields, "preferred_username")
|
||||
}
|
||||
if len(missingFields) > 0 {
|
||||
// we don't have enough information to create an account automatically,
|
||||
|
|
|
|||
|
|
@ -2127,3 +2127,205 @@ func TestSignInOAuthCallbackSignInRetrieveError(t *testing.T) {
|
|||
assert.NotNil(t, flashCookie)
|
||||
assert.Equal(t, "error%3DOAuth2%2BRetrieveError%253A%2Boauth2%253A%2Bcannot%2Bfetch%2Btoken%253A%2B404%2BNot%2BFound%250AResponse%253A%2Bcooked", flashCookie.Value)
|
||||
}
|
||||
|
||||
func TestSignUpViaOAuthWithMissingNickname(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
// enable auto-creation of accounts via OAuth2 with "nickname" username
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)()
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.Username, "nickname")()
|
||||
|
||||
// OAuth2 authentication source GitLab
|
||||
gitlabName := "gitlab"
|
||||
addAuthSource(t, authSourcePayloadGitLabCustom(gitlabName))
|
||||
userGitLabUserID := "5678"
|
||||
|
||||
// The Goth User doesn't contain a nickname, redirected to link account
|
||||
defer mockCompleteUserAuth(func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
|
||||
return goth.User{
|
||||
Provider: gitlabName,
|
||||
UserID: userGitLabUserID,
|
||||
Email: "gitlabuser@example.com",
|
||||
RawData: map[string]any{
|
||||
"preferred_username": "preferred_username",
|
||||
},
|
||||
}, nil
|
||||
})()
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s/callback?code=XYZ&state=XYZ", gitlabName))
|
||||
resp := MakeRequest(t, req, http.StatusSeeOther)
|
||||
assert.Equal(t, "/user/link_account", test.RedirectURL(resp))
|
||||
}
|
||||
|
||||
func TestSignUpViaOAuthWithMissingUsername(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
// enable auto-creation of accounts via OAuth2 with "preferred_username" username
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)()
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.Username, "preferred_username")()
|
||||
|
||||
// OAuth2 authentication source GitLab
|
||||
gitlabName := "gitlab"
|
||||
addAuthSource(t, authSourcePayloadGitLabCustom(gitlabName))
|
||||
userGitLabUserID := "5678"
|
||||
|
||||
// The Goth User doesn't contain preferred_username, redirected to link account
|
||||
defer mockCompleteUserAuth(func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
|
||||
return goth.User{
|
||||
Provider: gitlabName,
|
||||
UserID: userGitLabUserID,
|
||||
Email: "gitlabuser@example.com",
|
||||
NickName: "nickname",
|
||||
}, nil
|
||||
})()
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s/callback?code=XYZ&state=XYZ", gitlabName))
|
||||
resp := MakeRequest(t, req, http.StatusSeeOther)
|
||||
assert.Equal(t, "/user/link_account", test.RedirectURL(resp))
|
||||
}
|
||||
|
||||
func TestSignUpViaOAuthWithNicknameAsUsername(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
// enable auto-creation of accounts via OAuth2 with "nickname" username
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)()
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.Username, "nickname")()
|
||||
|
||||
// OAuth2 authentication source GitLab
|
||||
gitlabName := "gitlab"
|
||||
addAuthSource(t, authSourcePayloadGitLabCustom(gitlabName))
|
||||
userGitLabUserID := "5678"
|
||||
|
||||
// The Goth User contains the nickname that'll be used as the Forgejo user's username
|
||||
defer mockCompleteUserAuth(func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
|
||||
return goth.User{
|
||||
Provider: gitlabName,
|
||||
UserID: userGitLabUserID,
|
||||
Email: "gitlabuser@example.com",
|
||||
NickName: "nickname",
|
||||
RawData: map[string]any{
|
||||
"preferred_username": "preferred_username",
|
||||
},
|
||||
}, nil
|
||||
})()
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s/callback?code=XYZ&state=XYZ", gitlabName))
|
||||
resp := MakeRequest(t, req, http.StatusSeeOther)
|
||||
assert.Equal(t, "/", test.RedirectURL(resp))
|
||||
userAfterLogin := unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: userGitLabUserID})
|
||||
assert.Equal(t, "nickname", userAfterLogin.Name)
|
||||
}
|
||||
|
||||
func TestSignUpViaOAuthWithUserIdAsUsername(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
// enable auto-creation of accounts via OAuth2 with "userid" username
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)()
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.Username, "userid")()
|
||||
|
||||
// OAuth2 authentication source GitLab
|
||||
gitlabName := "gitlab"
|
||||
addAuthSource(t, authSourcePayloadGitLabCustom(gitlabName))
|
||||
userGitLabUserID := "5678"
|
||||
|
||||
// The Goth User's UserID will be used as the Forgejo user's username
|
||||
defer mockCompleteUserAuth(func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
|
||||
return goth.User{
|
||||
Provider: gitlabName,
|
||||
UserID: userGitLabUserID,
|
||||
Email: "gitlabuser@example.com",
|
||||
NickName: "nickname",
|
||||
RawData: map[string]any{
|
||||
"preferred_username": "preferred_username",
|
||||
},
|
||||
}, nil
|
||||
})()
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s/callback?code=XYZ&state=XYZ", gitlabName))
|
||||
resp := MakeRequest(t, req, http.StatusSeeOther)
|
||||
assert.Equal(t, "/", test.RedirectURL(resp))
|
||||
userAfterLogin := unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: userGitLabUserID})
|
||||
assert.Equal(t, userGitLabUserID, userAfterLogin.Name)
|
||||
}
|
||||
|
||||
func TestSignUpViaOAuthWithEmailAsUsername(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
// enable auto-creation of accounts via OAuth2 with "email" username
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)()
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.Username, "email")()
|
||||
|
||||
// OAuth2 authentication source GitLab
|
||||
gitlabName := "gitlab"
|
||||
addAuthSource(t, authSourcePayloadGitLabCustom(gitlabName))
|
||||
userGitLabUserID := "5678"
|
||||
|
||||
// The Goth User's email local part will be used as the Forgejo user's username
|
||||
defer mockCompleteUserAuth(func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
|
||||
return goth.User{
|
||||
Provider: gitlabName,
|
||||
UserID: userGitLabUserID,
|
||||
Email: "gitlabuser@example.com",
|
||||
NickName: "nickname",
|
||||
RawData: map[string]any{
|
||||
"preferred_username": "preferred_username",
|
||||
},
|
||||
}, nil
|
||||
})()
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s/callback?code=XYZ&state=XYZ", gitlabName))
|
||||
resp := MakeRequest(t, req, http.StatusSeeOther)
|
||||
assert.Equal(t, "/", test.RedirectURL(resp))
|
||||
userAfterLogin := unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: userGitLabUserID})
|
||||
assert.Equal(t, "gitlabuser", userAfterLogin.Name)
|
||||
}
|
||||
|
||||
func TestSignUpViaOAuthWithPreferredUsernameAsUsername(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
// enable auto-creation of accounts via OAuth2 with "preferred_username" username
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)()
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.Username, "preferred_username")()
|
||||
|
||||
// OAuth2 authentication source GitLab
|
||||
gitlabName := "gitlab"
|
||||
addAuthSource(t, authSourcePayloadGitLabCustom(gitlabName))
|
||||
userGitLabUserID := "5678"
|
||||
|
||||
// The Goth User's preferred_username claim will be used as the Forgejo user's username
|
||||
defer mockCompleteUserAuth(func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
|
||||
return goth.User{
|
||||
Provider: gitlabName,
|
||||
UserID: userGitLabUserID,
|
||||
Email: "gitlabuser@example.com",
|
||||
NickName: "nickname",
|
||||
RawData: map[string]any{
|
||||
"preferred_username": "preferred_username",
|
||||
},
|
||||
}, nil
|
||||
})()
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s/callback?code=XYZ&state=XYZ", gitlabName))
|
||||
resp := MakeRequest(t, req, http.StatusSeeOther)
|
||||
assert.Equal(t, "/", test.RedirectURL(resp))
|
||||
userAfterLogin := unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: userGitLabUserID})
|
||||
assert.Equal(t, "preferred_username", userAfterLogin.Name)
|
||||
}
|
||||
|
||||
func TestSignUpViaOAuthWithPreferredUsernameProcessesEmail(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
// enable auto-creation of accounts via OAuth2 with "preferred_username" username
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)()
|
||||
defer test.MockVariableValue(&setting.OAuth2Client.Username, "preferred_username")()
|
||||
|
||||
// OAuth2 authentication source GitLab
|
||||
gitlabName := "gitlab"
|
||||
addAuthSource(t, authSourcePayloadGitLabCustom(gitlabName))
|
||||
userGitLabUserID := "5678"
|
||||
|
||||
// The Goth User's preferred_username claim contains an "@" suffix that will be stripped
|
||||
defer mockCompleteUserAuth(func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
|
||||
return goth.User{
|
||||
Provider: gitlabName,
|
||||
UserID: userGitLabUserID,
|
||||
Email: "gitlabuser@example.com",
|
||||
NickName: "nickname",
|
||||
RawData: map[string]any{
|
||||
"preferred_username": "someotheremail@gmail.com",
|
||||
},
|
||||
}, nil
|
||||
})()
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s/callback?code=XYZ&state=XYZ", gitlabName))
|
||||
resp := MakeRequest(t, req, http.StatusSeeOther)
|
||||
assert.Equal(t, "/", test.RedirectURL(resp))
|
||||
userAfterLogin := unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: userGitLabUserID})
|
||||
assert.Equal(t, "someotheremail", userAfterLogin.Name)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue