Add profile scope to OIDC integration to support accounts without email
This commit is contained in:
parent
ab0c4ec0f5
commit
fbce915d84
5 changed files with 44 additions and 17 deletions
|
@ -917,7 +917,7 @@ func TestDefaultOAuth2RedirectURLValue(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOAuth2OidcDiscoveryEndpoint(t *testing.T) {
|
func TestOAuth2OIDCDiscoveryEndpoint(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("OAUTH2_OIDC_DISCOVERY_ENDPOINT", "http://example.org")
|
os.Setenv("OAUTH2_OIDC_DISCOVERY_ENDPOINT", "http://example.org")
|
||||||
|
|
||||||
|
@ -928,14 +928,14 @@ func TestOAuth2OidcDiscoveryEndpoint(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := "http://example.org"
|
expected := "http://example.org"
|
||||||
result := opts.OAuth2OidcDiscoveryEndpoint()
|
result := opts.OIDCDiscoveryEndpoint()
|
||||||
|
|
||||||
if result != expected {
|
if result != expected {
|
||||||
t.Fatalf(`Unexpected OAUTH2_OIDC_DISCOVERY_ENDPOINT value, got %q instead of %q`, result, expected)
|
t.Fatalf(`Unexpected OAUTH2_OIDC_DISCOVERY_ENDPOINT value, got %q instead of %q`, result, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultOAuth2OidcDiscoveryEndpointValue(t *testing.T) {
|
func TestDefaultOIDCDiscoveryEndpointValue(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
|
|
||||||
parser := NewParser()
|
parser := NewParser()
|
||||||
|
@ -945,10 +945,10 @@ func TestDefaultOAuth2OidcDiscoveryEndpointValue(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := defaultOAuth2OidcDiscoveryEndpoint
|
expected := defaultOAuth2OidcDiscoveryEndpoint
|
||||||
result := opts.OAuth2OidcDiscoveryEndpoint()
|
result := opts.OIDCDiscoveryEndpoint()
|
||||||
|
|
||||||
if result != expected {
|
if result != expected {
|
||||||
t.Fatalf(`Unexpected OAUTH2_REDIRECT_URL value, got %q instead of %q`, result, expected)
|
t.Fatalf(`Unexpected OAUTH2_OIDC_DISCOVERY_ENDPOINT value, got %q instead of %q`, result, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,7 @@ type Options struct {
|
||||||
oauth2ClientID string
|
oauth2ClientID string
|
||||||
oauth2ClientSecret string
|
oauth2ClientSecret string
|
||||||
oauth2RedirectURL string
|
oauth2RedirectURL string
|
||||||
oauth2OidcDiscoveryEndpoint string
|
oidcDiscoveryEndpoint string
|
||||||
oauth2Provider string
|
oauth2Provider string
|
||||||
pocketConsumerKey string
|
pocketConsumerKey string
|
||||||
httpClientTimeout int
|
httpClientTimeout int
|
||||||
|
@ -208,7 +208,7 @@ func NewOptions() *Options {
|
||||||
oauth2ClientID: defaultOAuth2ClientID,
|
oauth2ClientID: defaultOAuth2ClientID,
|
||||||
oauth2ClientSecret: defaultOAuth2ClientSecret,
|
oauth2ClientSecret: defaultOAuth2ClientSecret,
|
||||||
oauth2RedirectURL: defaultOAuth2RedirectURL,
|
oauth2RedirectURL: defaultOAuth2RedirectURL,
|
||||||
oauth2OidcDiscoveryEndpoint: defaultOAuth2OidcDiscoveryEndpoint,
|
oidcDiscoveryEndpoint: defaultOAuth2OidcDiscoveryEndpoint,
|
||||||
oauth2Provider: defaultOAuth2Provider,
|
oauth2Provider: defaultOAuth2Provider,
|
||||||
pocketConsumerKey: defaultPocketConsumerKey,
|
pocketConsumerKey: defaultPocketConsumerKey,
|
||||||
httpClientTimeout: defaultHTTPClientTimeout,
|
httpClientTimeout: defaultHTTPClientTimeout,
|
||||||
|
@ -401,9 +401,9 @@ func (o *Options) OAuth2RedirectURL() string {
|
||||||
return o.oauth2RedirectURL
|
return o.oauth2RedirectURL
|
||||||
}
|
}
|
||||||
|
|
||||||
// OAuth2OidcDiscoveryEndpoint returns the OAuth2 OIDC discovery endpoint.
|
// OIDCDiscoveryEndpoint returns the OAuth2 OIDC discovery endpoint.
|
||||||
func (o *Options) OAuth2OidcDiscoveryEndpoint() string {
|
func (o *Options) OIDCDiscoveryEndpoint() string {
|
||||||
return o.oauth2OidcDiscoveryEndpoint
|
return o.oidcDiscoveryEndpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
// OAuth2Provider returns the name of the OAuth2 provider configured.
|
// OAuth2Provider returns the name of the OAuth2 provider configured.
|
||||||
|
@ -619,7 +619,7 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
|
||||||
"METRICS_USERNAME": o.metricsUsername,
|
"METRICS_USERNAME": o.metricsUsername,
|
||||||
"OAUTH2_CLIENT_ID": o.oauth2ClientID,
|
"OAUTH2_CLIENT_ID": o.oauth2ClientID,
|
||||||
"OAUTH2_CLIENT_SECRET": redactSecretValue(o.oauth2ClientSecret, redactSecret),
|
"OAUTH2_CLIENT_SECRET": redactSecretValue(o.oauth2ClientSecret, redactSecret),
|
||||||
"OAUTH2_OIDC_DISCOVERY_ENDPOINT": o.oauth2OidcDiscoveryEndpoint,
|
"OAUTH2_OIDC_DISCOVERY_ENDPOINT": o.oidcDiscoveryEndpoint,
|
||||||
"OAUTH2_PROVIDER": o.oauth2Provider,
|
"OAUTH2_PROVIDER": o.oauth2Provider,
|
||||||
"OAUTH2_REDIRECT_URL": o.oauth2RedirectURL,
|
"OAUTH2_REDIRECT_URL": o.oauth2RedirectURL,
|
||||||
"OAUTH2_USER_CREATION": o.oauth2UserCreationAllowed,
|
"OAUTH2_USER_CREATION": o.oauth2UserCreationAllowed,
|
||||||
|
|
|
@ -180,7 +180,7 @@ func (p *Parser) parseLines(lines []string) (err error) {
|
||||||
case "OAUTH2_REDIRECT_URL":
|
case "OAUTH2_REDIRECT_URL":
|
||||||
p.opts.oauth2RedirectURL = parseString(value, defaultOAuth2RedirectURL)
|
p.opts.oauth2RedirectURL = parseString(value, defaultOAuth2RedirectURL)
|
||||||
case "OAUTH2_OIDC_DISCOVERY_ENDPOINT":
|
case "OAUTH2_OIDC_DISCOVERY_ENDPOINT":
|
||||||
p.opts.oauth2OidcDiscoveryEndpoint = parseString(value, defaultOAuth2OidcDiscoveryEndpoint)
|
p.opts.oidcDiscoveryEndpoint = parseString(value, defaultOAuth2OidcDiscoveryEndpoint)
|
||||||
case "OAUTH2_PROVIDER":
|
case "OAUTH2_PROVIDER":
|
||||||
p.opts.oauth2Provider = parseString(value, defaultOAuth2Provider)
|
p.opts.oauth2Provider = parseString(value, defaultOAuth2Provider)
|
||||||
case "HTTP_CLIENT_TIMEOUT":
|
case "HTTP_CLIENT_TIMEOUT":
|
||||||
|
|
|
@ -28,10 +28,15 @@ type oidcProvider struct {
|
||||||
func NewOidcProvider(ctx context.Context, clientID, clientSecret, redirectURL, discoveryEndpoint string) (*oidcProvider, error) {
|
func NewOidcProvider(ctx context.Context, clientID, clientSecret, redirectURL, discoveryEndpoint string) (*oidcProvider, error) {
|
||||||
provider, err := oidc.NewProvider(ctx, discoveryEndpoint)
|
provider, err := oidc.NewProvider(ctx, discoveryEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf(`oidc: failed to initialize provider %q: %w`, discoveryEndpoint, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &oidcProvider{clientID: clientID, clientSecret: clientSecret, redirectURL: redirectURL, provider: provider}, nil
|
return &oidcProvider{
|
||||||
|
clientID: clientID,
|
||||||
|
clientSecret: clientSecret,
|
||||||
|
redirectURL: redirectURL,
|
||||||
|
provider: provider,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *oidcProvider) GetUserExtraKey() string {
|
func (o *oidcProvider) GetUserExtraKey() string {
|
||||||
|
@ -43,7 +48,7 @@ func (o *oidcProvider) GetConfig() *oauth2.Config {
|
||||||
RedirectURL: o.redirectURL,
|
RedirectURL: o.redirectURL,
|
||||||
ClientID: o.clientID,
|
ClientID: o.clientID,
|
||||||
ClientSecret: o.clientSecret,
|
ClientSecret: o.clientSecret,
|
||||||
Scopes: []string{"openid", "email"},
|
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||||
Endpoint: o.provider.Endpoint(),
|
Endpoint: o.provider.Endpoint(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +65,22 @@ func (o *oidcProvider) GetProfile(ctx context.Context, code, codeVerifier string
|
||||||
return nil, fmt.Errorf(`oidc: failed to get user info: %w`, err)
|
return nil, fmt.Errorf(`oidc: failed to get user info: %w`, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
profile := &Profile{Key: o.GetUserExtraKey(), ID: userInfo.Subject, Username: userInfo.Email}
|
profile := &Profile{
|
||||||
|
Key: o.GetUserExtraKey(),
|
||||||
|
ID: userInfo.Subject,
|
||||||
|
}
|
||||||
|
|
||||||
|
var userClaims userClaims
|
||||||
|
if err := userInfo.Claims(&userClaims); err != nil {
|
||||||
|
return nil, fmt.Errorf(`oidc: failed to parse user claims: %w`, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, value := range []string{userClaims.Email, userClaims.PreferredUsername, userClaims.Name, userClaims.Profile} {
|
||||||
|
if value != "" {
|
||||||
|
profile.Username = value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if profile.Username == "" {
|
if profile.Username == "" {
|
||||||
return nil, ErrEmptyUsername
|
return nil, ErrEmptyUsername
|
||||||
|
@ -80,3 +100,10 @@ func (o *oidcProvider) PopulateUserWithProfileID(user *model.User, profile *Prof
|
||||||
func (o *oidcProvider) UnsetUserProfileID(user *model.User) {
|
func (o *oidcProvider) UnsetUserProfileID(user *model.User) {
|
||||||
user.OpenIDConnectID = ""
|
user.OpenIDConnectID = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type userClaims struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Profile string `json:"profile"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
PreferredUsername string `json:"preferred_username"`
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,6 @@ func getOAuth2Manager(ctx context.Context) *oauth2.Manager {
|
||||||
config.Opts.OAuth2ClientID(),
|
config.Opts.OAuth2ClientID(),
|
||||||
config.Opts.OAuth2ClientSecret(),
|
config.Opts.OAuth2ClientSecret(),
|
||||||
config.Opts.OAuth2RedirectURL(),
|
config.Opts.OAuth2RedirectURL(),
|
||||||
config.Opts.OAuth2OidcDiscoveryEndpoint(),
|
config.Opts.OIDCDiscoveryEndpoint(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue