Files
upstream-ory-kratos/selfservice/strategy/oidc/provider_userinfo_test.go
upstream-mirror c60cc7ca75
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 21:19:12 +05:00

381 lines
15 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package oidc_test
import (
"context"
"encoding/json"
"net/http"
"net/url"
"testing"
"time"
"github.com/hashicorp/go-retryablehttp"
"github.com/jarcoal/httpmock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
"github.com/ory/herodot"
"github.com/ory/kratos/driver"
"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/pkg"
"github.com/ory/kratos/selfservice/strategy/oidc"
"github.com/ory/x/configx"
"github.com/ory/x/httpx"
)
type mockRegistry struct {
*driver.RegistryDefault
cl *retryablehttp.Client
}
func (s *mockRegistry) HTTPClient(context.Context, ...httpx.ResilientOptions) *retryablehttp.Client {
return s.cl
}
func TestProviderClaimsRespectsErrorCodes(t *testing.T) {
t.Parallel()
_, base := pkg.NewFastRegistryWithMocks(t,
configx.WithValue(config.ViperKeyClientHTTPNoPrivateIPRanges, true))
reg := &mockRegistry{base, retryablehttp.NewClient()}
token := &oauth2.Token{AccessToken: "foo", Expiry: time.Now().Add(time.Hour)}
expectedClaims := &oidc.Claims{
Issuer: "ignore-me",
Subject: "123456789012345",
Name: "John Doe",
GivenName: "John",
FamilyName: "Doe",
Nickname: "John Doe",
PreferredUsername: "John Doe",
Email: "john.doe@example.com",
EmailVerified: true,
Birthdate: "01/01/1990",
}
defaultUserinfoHandler := func(req *http.Request) (*http.Response, error) {
if head := req.Header.Get("Authorization"); len(head) == 0 {
resp, err := httpmock.NewJsonResponse(401, map[string]interface{}{"error": ""})
return resp, err
}
resp, err := httpmock.NewJsonResponse(200, expectedClaims)
return resp, err
}
for _, tc := range []struct {
name string
issuer string
userInfoEndpoint string
config *oidc.Configuration
provider oidc.Provider
userInfoHandler func(req *http.Request) (*http.Response, error)
expectedClaims *oidc.Claims
useToken *oauth2.Token
hook func(t *testing.T)
}{
{
name: "auth0",
userInfoHandler: defaultUserinfoHandler,
userInfoEndpoint: "https://www.auth0.com/userinfo",
provider: oidc.NewProviderAuth0(&oidc.Configuration{
IssuerURL: "https://www.auth0.com",
ID: "auth0",
Provider: "auth0",
}, reg),
},
{
name: "salesforce",
userInfoHandler: defaultUserinfoHandler,
userInfoEndpoint: "https://www.salesforce.com/services/oauth2/userinfo",
provider: oidc.NewProviderSalesforce(&oidc.Configuration{
IssuerURL: "https://www.salesforce.com",
ID: "salesforce",
Provider: "salesforce",
}, reg),
},
{
name: "netid",
userInfoHandler: defaultUserinfoHandler,
userInfoEndpoint: "https://broker.netid.de/userinfo",
provider: oidc.NewProviderNetID(&oidc.Configuration{
IssuerURL: "https://broker.netid.de",
ID: "netid",
Provider: "netid",
ClientID: "foo",
}, reg),
useToken: token.WithExtra(map[string]interface{}{"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZW1haWwiOiJqb2huLmRvZUBleGFtcGxlLmNvbSIsImlhdCI6MTUxNjIzOTAyMiwidGlkIjoiYTliODYzODUtZjMyYy00ODAzLWFmYzgtNGIyMzEyZmJkZjI0IiwiaXNzIjoiaHR0cHM6Ly9icm9rZXIubmV0aWQuZGUvIiwiYXVkIjpbImZvbyJdLCJleHAiOjQwNzE3Mjg1MDR9.Zt_-9jULoENQ7pq6rKrevhecBlWKR2rzNti42EJti_OelCrGbl5zyHYRfIg264VzEY0Zp9BAZTWmcF6Z-cpuD05RddTXZDrrC_47bJeYDL-bjDfKjoSZUt_1JnNFgqeyMA1Ji6HCIsEf_g8QnuSELAO0c-qb2ANmDLVL8_dY6oUmCN5oWLJS2q6xO-Iaz-AuKaGTbLZBcjCe2NB_--kIx4IrgLm78U90X3ePOV0CcYZLNvDgzEsVJ2M4ixKp87bYUaZZ3JJEjuxgnHqKRMExDKron3mvfQtC1L-dZQyeDo3-mCFJL_JhEw9zLmoWFBD7aARrVL_yAe31o26FB3S-Dg"}),
hook: func(t *testing.T) {
httpmock.RegisterResponder("GET", "https://broker.netid.de/.well-known/openid-configuration",
func(req *http.Request) (*http.Response, error) {
return httpmock.NewJsonResponse(200, map[string]interface{}{
"issuer": "https://broker.netid.de/",
"jwks_uri": "https://broker.netid.de/.well-known/jwks.json",
})
},
)
httpmock.RegisterResponder("GET", "https://broker.netid.de/.well-known/jwks.json",
func(req *http.Request) (*http.Response, error) {
return httpmock.NewJsonResponse(200, json.RawMessage(`{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"alg": "RS256",
"use": "sig",
"n": "uw0hrmZpA5CH5lDnjbCK6ZQD-I0BGxde-ChDzUz5eK4r7VtyMGvAxwD-k-mWw4FJ2NgYmT_T89sAtE6NQqT5u9HAe-CI22lf5LMvmqvMzekcmBAvXNw8VeTV_N6CbS9INJrxf20cObf-kpTxVxlYtYxYwIhYdOw3DwX8y31vI38qHQ4_OzTRo4KFVLCr68MzIHHRI4d5EHrFv1VFjiaa_ATwuwCMUfg0RMnK09FpMCgvp0E6bQeptXhBBNQVQkoC5whT1GzikfSxyeugjQ_XuTt1MKoyYsN2pmfrBdcfWrPYvV_JPgO1MkEtqErvtCByairINfXrHTMOxNWe3sYlXQ"
}
]
}`))
},
)
},
expectedClaims: &oidc.Claims{Issuer: "https://broker.netid.de/", Subject: "1234567890", Name: "John Doe", GivenName: "John", FamilyName: "Doe", LastName: "", MiddleName: "", Nickname: "John Doe", PreferredUsername: "John Doe", Profile: "", Picture: "", Website: "", Email: "john.doe@example.com", EmailVerified: true, Gender: "", Birthdate: "01/01/1990", Zoneinfo: "", Locale: "", PhoneNumber: "", PhoneNumberVerified: false, UpdatedAt: 0, HD: "", Team: ""},
},
{
name: "vk",
userInfoEndpoint: "https://api.vk.com/method/users.get",
provider: oidc.NewProviderVK(&oidc.Configuration{
IssuerURL: "https://oauth.vk.com",
ID: "vk",
Provider: "vk",
}, reg),
useToken: token.WithExtra(map[string]interface{}{"email": "john.doe@example.com"}),
userInfoHandler: func(req *http.Request) (*http.Response, error) {
if head := req.URL.Query().Get("access_token"); len(head) == 0 {
resp, err := httpmock.NewJsonResponse(401, map[string]interface{}{"error": ""})
return resp, err
}
resp, err := httpmock.NewJsonResponse(200, map[string]interface{}{
"response": []map[string]interface{}{{"id": 123456789012345}},
})
return resp, err
},
expectedClaims: &oidc.Claims{
Issuer: "https://api.vk.com/method/users.get",
Subject: "123456789012345",
Email: "john.doe@example.com",
},
},
{
name: "yandex",
userInfoEndpoint: "https://login.yandex.ru/info",
provider: oidc.NewProviderYandex(&oidc.Configuration{
IssuerURL: "https://oauth.yandex.com",
ID: "vk",
Provider: "vk",
}, reg),
useToken: token.WithExtra(map[string]interface{}{"email": "john.doe@example.com"}),
userInfoHandler: func(req *http.Request) (*http.Response, error) {
if head := req.URL.Query().Get("oauth_token"); len(head) == 0 {
resp, err := httpmock.NewJsonResponse(401, map[string]interface{}{"error": ""})
return resp, err
}
resp, err := httpmock.NewJsonResponse(200, map[string]interface{}{
"id": "123456789012345",
"default_email": "john.doe@example.com",
})
return resp, err
},
expectedClaims: &oidc.Claims{
Issuer: "https://login.yandex.ru/info",
Subject: "123456789012345",
Email: "john.doe@example.com",
Picture: "https://avatars.yandex.net/get-yapic//islands-200",
},
},
{
name: "facebook",
hook: func(t *testing.T) {
httpmock.RegisterResponder("GET", "https://www.facebook.com/.well-known/openid-configuration",
func(req *http.Request) (*http.Response, error) {
resp, err := httpmock.NewJsonResponse(200, map[string]interface{}{
"issuer": "https://www.facebook.com",
})
return resp, err
},
)
},
userInfoEndpoint: "https://graph.facebook.com/me",
provider: oidc.NewProviderFacebook(&oidc.Configuration{
ID: "facebook",
Provider: "facebook",
}, reg),
useToken: token,
userInfoHandler: func(req *http.Request) (*http.Response, error) {
if head := req.Header.Get("Authorization"); len(head) == 0 {
resp, err := httpmock.NewJsonResponse(401, map[string]interface{}{"error": ""})
return resp, err
}
if _, ok := req.URL.Query()["appsecret_proof"]; !ok {
resp, err := httpmock.NewJsonResponse(400, map[string]interface{}{"error": ""})
return resp, err
}
resp, err := httpmock.NewJsonResponse(200, map[string]interface{}{
"id": "123456789012345",
"name": "John Doe",
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"birthday": "01/01/1990",
})
return resp, err
},
expectedClaims: &oidc.Claims{
Issuer: "https://graph.facebook.com/me?fields=id,name,first_name,last_name,middle_name,email,picture,birthday,gender&appsecret_proof=0c0d98f7e3d9d45e72e8877bc1b104327efb9c07b18f2ffeced76d81307f1fff",
Subject: "123456789012345",
Name: "John Doe",
GivenName: "John",
FamilyName: "Doe",
Nickname: "John Doe",
PreferredUsername: "John Doe",
Email: "john.doe@example.com",
EmailVerified: true,
Birthdate: "01/01/1990",
},
},
{
name: "gitlab",
userInfoHandler: defaultUserinfoHandler,
userInfoEndpoint: "https://www.gitlab.com/oauth/userinfo",
provider: oidc.NewProviderGitLab(&oidc.Configuration{
IssuerURL: "https://www.gitlab.com",
ID: "gitlab",
Provider: "gitlab",
}, reg),
},
// Microsoft is a more complicated set up because it actually verifies the ID Token before using the userinfo endpoint.
{
name: "microsoft",
userInfoHandler: func(req *http.Request) (*http.Response, error) {
if head := req.Header.Get("Authorization"); len(head) == 0 {
resp, err := httpmock.NewJsonResponse(401, map[string]interface{}{"error": ""})
return resp, err
}
resp, err := httpmock.NewJsonResponse(200, json.RawMessage(`{"id":"new-id"}`))
return resp, err
},
userInfoEndpoint: "https://graph.microsoft.com/v1.0/me",
provider: oidc.NewProviderMicrosoft(&oidc.Configuration{
IssuerURL: "https://login.microsoftonline.com/",
ID: "microsoft",
Provider: "microsoft",
Tenant: "a9b86385-f32c-4803-afc8-4b2312fbdf24",
ClientID: "foo",
ClientSecret: "bar",
SubjectSource: "me",
}, reg),
useToken: token.WithExtra(map[string]interface{}{"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZW1haWwiOiJqb2huLmRvZUBleGFtcGxlLmNvbSIsImlhdCI6MTUxNjIzOTAyMiwidGlkIjoiYTliODYzODUtZjMyYy00ODAzLWFmYzgtNGIyMzEyZmJkZjI0IiwiaXNzIjoiaHR0cHM6Ly9sb2dpbi5taWNyb3NvZnRvbmxpbmUuY29tL2E5Yjg2Mzg1LWYzMmMtNDgwMy1hZmM4LTRiMjMxMmZiZGYyNC92Mi4wIiwiYXVkIjpbImZvbyJdLCJleHAiOjQwNzE3Mjg1MDR9.LRgolO5_-26uMrBo89NCzUfi87a8jf7rXlWgZVYnpfowzqn-U0JhNGVzEOXOACoPdX9HsEtYj4hZxYgYcd6z7yqgmOZXE-y58L5BHYZU1kk37O1Dl_VnN-BmeCWs_JZiXF2KEnu7BW8btYnb26qCnc3_8RGbyJwI4UU6ynbJzAmLCPUPgoMZ2Jpahx_K8vqLe-4rveLyUvVVHMoUAV16I-Wg08GcuA5cY0_91-QpA5Kq0AA58wbrUbAOEAOYOpa_63QqYcCcZLbnX_w09Z1YCXGGVSbbpzjr3cJ8EFA6doloBKRLWtNtEnGk4hyjHyp_ls89ZYqJ1ngy95AEswzwJQ"}),
hook: func(t *testing.T) {
httpmock.RegisterResponder("GET", "https://login.microsoftonline.com/a9b86385-f32c-4803-afc8-4b2312fbdf24/v2.0/.well-known/openid-configuration",
func(req *http.Request) (*http.Response, error) {
return httpmock.NewJsonResponse(200, map[string]interface{}{
"issuer": "https://login.microsoftonline.com/a9b86385-f32c-4803-afc8-4b2312fbdf24/v2.0",
"jwks_uri": "https://login.microsoftonline.com/a9b86385-f32c-4803-afc8-4b2312fbdf24/v2.0/.well-known/jwks.json",
})
},
)
httpmock.RegisterResponder("GET", "https://login.microsoftonline.com/a9b86385-f32c-4803-afc8-4b2312fbdf24/v2.0/.well-known/jwks.json",
func(req *http.Request) (*http.Response, error) {
return httpmock.NewJsonResponse(200, json.RawMessage(`{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"alg": "RS256",
"n": "xGnYx5u9S7LCrtINTI1Kav6pqzFj72JhYunqrDhnG18Iu-MJdCSmd26xiiZFY6UsJoG-te_DcUE_YHvhx6vwC0tJO-Z2uxwKRxWsMiyW8v3V0Bfbtu7jlf0kpQYeAeAIAHih7GU_v5jtxaGLNR9JIbmENUGOoIfydfTWLKHtwI0MB8tNVwjqP8e6ZSr6DNIjsKKuz98BeRlZ576jyH2GcmM9DguLZv73qU1m1OLoHFW5rAPH70-nhr9V67TTR1A1is9v85uLlKBnZgKp-EdYtBEvKRWSpw5vxCnKggJjEYATOB2H5m_c8F1e1tsJLAehaXKMKZYI-VlWWxj7KixDKw"
}
]
}`))
},
)
},
expectedClaims: &oidc.Claims{
Issuer: "https://login.microsoftonline.com/a9b86385-f32c-4803-afc8-4b2312fbdf24/v2.0", Subject: "new-id", Name: "John Doe", Email: "john.doe@example.com",
RawClaims: map[string]interface{}{"aud": []interface{}{"foo"}, "exp": 4.071728504e+09, "iat": 1.516239022e+09, "iss": "https://login.microsoftonline.com/a9b86385-f32c-4803-afc8-4b2312fbdf24/v2.0", "email": "john.doe@example.com", "name": "John Doe", "sub": "1234567890", "tid": "a9b86385-f32c-4803-afc8-4b2312fbdf24"},
},
},
{
name: "dingtalk",
userInfoHandler: func(req *http.Request) (*http.Response, error) {
if head := req.Header.Get("x-acs-dingtalk-access-token"); len(head) == 0 {
resp, err := httpmock.NewJsonResponse(401, map[string]interface{}{"error": ""})
return resp, err
}
resp, err := httpmock.NewJsonResponse(200, map[string]interface{}{
"openId": "123456789012345",
"email": "john.doe@example.com",
})
return resp, err
},
userInfoEndpoint: "https://api.dingtalk.com/v1.0/contact/users/me",
provider: oidc.NewProviderDingTalk(&oidc.Configuration{
IssuerURL: "https://www.dingtalk.com",
ID: "dingtalk",
Provider: "dingtalk",
}, reg),
expectedClaims: &oidc.Claims{
Issuer: "https://api.dingtalk.com/v1.0/contact/users/me",
Subject: "123456789012345",
Email: "john.doe@example.com",
},
},
} {
t.Run(tc.name, func(t *testing.T) {
token := token
if tc.useToken != nil {
token = tc.useToken
}
t.Run("http error is respected", func(t *testing.T) {
httpmock.ActivateNonDefault(reg.cl.HTTPClient)
t.Cleanup(httpmock.DeactivateAndReset)
if tc.hook != nil {
tc.hook(t)
}
httpmock.RegisterResponder("GET", tc.userInfoEndpoint, func(req *http.Request) (*http.Response, error) {
return httpmock.NewJsonResponse(455, map[string]interface{}{})
})
_, err := tc.provider.(oidc.OAuth2Provider).Claims(t.Context(), token, url.Values{})
var he *herodot.DefaultError
require.ErrorAs(t, err, &he)
assert.Equal(t, "OpenID Connect provider returned a 455 status code but 200 is expected.", he.Reason(), "%+v", err)
})
t.Run("call is successful", func(t *testing.T) {
httpmock.ActivateNonDefault(reg.cl.HTTPClient)
t.Cleanup(httpmock.DeactivateAndReset)
if tc.hook != nil {
tc.hook(t)
}
httpmock.RegisterResponder("GET", tc.userInfoEndpoint, tc.userInfoHandler)
claims, err := tc.provider.(oidc.OAuth2Provider).Claims(t.Context(), token, url.Values{})
require.NoError(t, err)
if tc.expectedClaims == nil {
assert.Equal(t, expectedClaims, claims)
} else {
assert.Equal(t, tc.expectedClaims, claims)
}
})
})
}
}