feat(coderd): set full name from IDP name claim (#13468)

* Updates OIDC and GitHub OAuth login to fetch set name from relevant claim fields
* Adds CODER_OIDC_NAME_FIELD as configurable source of user name claim
* Adds httpapi function to normalize a username such that it will pass validation
* Adds firstName / lastName fields to dev OIDC setup
This commit is contained in:
Cian Johnston
2024-06-06 13:37:08 +01:00
committed by GitHub
parent e743588843
commit 1131772e79
16 changed files with 301 additions and 42 deletions

View File

@ -607,6 +607,9 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
return
}
ghName := ghUser.GetName()
normName := httpapi.NormalizeRealUsername(ghName)
// If we have a nil GitHub ID, that is a big problem. That would mean we link
// this user and all other users with this bug to the same uuid.
// We should instead throw an error. This should never occur in production.
@ -652,6 +655,7 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
Email: verifiedEmail.GetEmail(),
Username: ghUser.GetLogin(),
AvatarURL: ghUser.GetAvatarURL(),
Name: normName,
DebugContext: OauthDebugContext{},
}).SetInitAuditRequest(func(params *audit.RequestParams) (*audit.Request[database.User], func()) {
return audit.InitRequest[database.User](rw, params)
@ -701,6 +705,9 @@ type OIDCConfig struct {
// EmailField selects the claim field to be used as the created user's
// email.
EmailField string
// NameField selects the claim field to be used as the created user's
// full / given name.
NameField string
// AuthURLParams are additional parameters to be passed to the OIDC provider
// when requesting an access token.
AuthURLParams map[string]string
@ -952,13 +959,22 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
}
}
// The 'name' is an optional property in Coder. If not specified,
// it will be left blank.
var name string
nameRaw, ok := mergedClaims[api.OIDCConfig.NameField]
if ok {
name, _ = nameRaw.(string)
name = httpapi.NormalizeRealUsername(name)
}
var picture string
pictureRaw, ok := mergedClaims["picture"]
if ok {
picture, _ = pictureRaw.(string)
}
ctx = slog.With(ctx, slog.F("email", email), slog.F("username", username))
ctx = slog.With(ctx, slog.F("email", email), slog.F("username", username), slog.F("name", name))
usingGroups, groups, groupErr := api.oidcGroups(ctx, mergedClaims)
if groupErr != nil {
groupErr.Write(rw, r)
@ -996,6 +1012,7 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
AllowSignups: api.OIDCConfig.AllowSignups,
Email: email,
Username: username,
Name: name,
AvatarURL: picture,
UsingRoles: api.OIDCConfig.RoleSyncEnabled(),
Roles: roles,
@ -1222,6 +1239,7 @@ type oauthLoginParams struct {
AllowSignups bool
Email string
Username string
Name string
AvatarURL string
// Is UsingGroups is true, then the user will be assigned
// to the Groups provided.
@ -1544,6 +1562,10 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
user.AvatarURL = params.AvatarURL
needsUpdate = true
}
if user.Name != params.Name {
user.Name = params.Name
needsUpdate = true
}
// If the upstream email or username has changed we should mirror
// that in Coder. Many enterprises use a user's email/username as