mirror of
https://github.com/juanfont/headscale.git
synced 2025-05-02 07:53:42 -04:00
oidc: try to get username from userinfo (#2545)
* oidc: try to get username from userinfo Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * changelog Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> --------- Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
parent
8f9fbf16f1
commit
cfe9bbf829
@ -97,6 +97,8 @@ working in v1 and not tested might be broken in v2 (and vice versa).
|
|||||||
[#2493](https://github.com/juanfont/headscale/pull/2493)
|
[#2493](https://github.com/juanfont/headscale/pull/2493)
|
||||||
- If a OIDC provider doesn't include the `email_verified` claim in its ID
|
- If a OIDC provider doesn't include the `email_verified` claim in its ID
|
||||||
tokens, Headscale will attempt to get it from the UserInfo endpoint.
|
tokens, Headscale will attempt to get it from the UserInfo endpoint.
|
||||||
|
- OIDC: Try to populate name, email and username from UserInfo
|
||||||
|
[#2545](https://github.com/juanfont/headscale/pull/2545)
|
||||||
- Improve performance by only querying relevant nodes from the database for node
|
- Improve performance by only querying relevant nodes from the database for node
|
||||||
updates [#2509](https://github.com/juanfont/headscale/pull/2509)
|
updates [#2509](https://github.com/juanfont/headscale/pull/2509)
|
||||||
- node FQDNs in the netmap will now contain a dot (".") at the end. This aligns
|
- node FQDNs in the netmap will now contain a dot (".") at the end. This aligns
|
||||||
|
@ -2,6 +2,7 @@ package hscontrol
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
@ -280,14 +281,28 @@ func (a *AuthProviderOIDC) OIDCCallbackHandler(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If EmailVerified is missing, we can try to get it from UserInfo
|
var userinfo *oidc.UserInfo
|
||||||
if !claims.EmailVerified {
|
userinfo, err = a.oidcProvider.UserInfo(req.Context(), oauth2.StaticTokenSource(oauth2Token))
|
||||||
var userinfo *oidc.UserInfo
|
if err != nil {
|
||||||
userinfo, err = a.oidcProvider.UserInfo(req.Context(), oauth2.StaticTokenSource(oauth2Token))
|
util.LogErr(err, "could not get userinfo; only checking claim")
|
||||||
if err != nil {
|
}
|
||||||
util.LogErr(err, "could not get userinfo; email cannot be verified")
|
|
||||||
|
// If the userinfo is available, we can check if the subject matches the
|
||||||
|
// claims, then use some of the userinfo fields to update the user.
|
||||||
|
// https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
|
||||||
|
if userinfo != nil && userinfo.Subject == claims.Sub {
|
||||||
|
claims.Email = cmp.Or(claims.Email, userinfo.Email)
|
||||||
|
claims.EmailVerified = cmp.Or(claims.EmailVerified, types.FlexibleBoolean(userinfo.EmailVerified))
|
||||||
|
|
||||||
|
// The userinfo has some extra fields that we can use to update the user but they are only
|
||||||
|
// available in the underlying claims struct.
|
||||||
|
// TODO(kradalby): there might be more interesting fields here that we have not found yet.
|
||||||
|
var userinfo2 types.OIDCUserInfo
|
||||||
|
if err := userinfo.Claims(&userinfo2); err == nil {
|
||||||
|
claims.Username = cmp.Or(claims.Username, userinfo2.PreferredUsername)
|
||||||
|
claims.Name = cmp.Or(claims.Name, userinfo2.Name)
|
||||||
|
claims.ProfilePictureURL = cmp.Or(claims.ProfilePictureURL, userinfo2.Picture)
|
||||||
}
|
}
|
||||||
claims.EmailVerified = types.FlexibleBoolean(userinfo.EmailVerified)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := a.createOrUpdateUserFromClaim(&claims)
|
user, err := a.createOrUpdateUserFromClaim(&claims)
|
||||||
|
@ -157,7 +157,7 @@ func (u *User) Proto() *v1.User {
|
|||||||
type FlexibleBoolean bool
|
type FlexibleBoolean bool
|
||||||
|
|
||||||
func (bit *FlexibleBoolean) UnmarshalJSON(data []byte) error {
|
func (bit *FlexibleBoolean) UnmarshalJSON(data []byte) error {
|
||||||
var val interface{}
|
var val any
|
||||||
err := json.Unmarshal(data, &val)
|
err := json.Unmarshal(data, &val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not unmarshal data: %w", err)
|
return fmt.Errorf("could not unmarshal data: %w", err)
|
||||||
@ -203,6 +203,17 @@ func (c *OIDCClaims) Identifier() string {
|
|||||||
return c.Iss + "/" + c.Sub
|
return c.Iss + "/" + c.Sub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OIDCUserInfo struct {
|
||||||
|
Sub string `json:"sub"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
GivenName string `json:"given_name"`
|
||||||
|
FamilyName string `json:"family_name"`
|
||||||
|
PreferredUsername string `json:"preferred_username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
EmailVerified FlexibleBoolean `json:"email_verified,omitempty"`
|
||||||
|
Picture string `json:"picture"`
|
||||||
|
}
|
||||||
|
|
||||||
// FromClaim overrides a User from OIDC claims.
|
// FromClaim overrides a User from OIDC claims.
|
||||||
// All fields will be updated, except for the ID.
|
// All fields will be updated, except for the ID.
|
||||||
func (u *User) FromClaim(claims *OIDCClaims) {
|
func (u *User) FromClaim(claims *OIDCClaims) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user