From 1a00f2c0ac05e1e260bb57c8938e8bff6799991a Mon Sep 17 00:00:00 2001 From: Dejan Filipovic Date: Mon, 27 Jun 2022 22:47:00 +0200 Subject: [PATCH 1/3] fix: add support for verified Graph API calls for facebook oidc provider --- selfservice/strategy/oidc/provider_facebook.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/selfservice/strategy/oidc/provider_facebook.go b/selfservice/strategy/oidc/provider_facebook.go index 15e45cc78555..7dd133087348 100644 --- a/selfservice/strategy/oidc/provider_facebook.go +++ b/selfservice/strategy/oidc/provider_facebook.go @@ -2,7 +2,11 @@ package oidc import ( "context" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" "encoding/json" + "fmt" "net/url" "github.com/hashicorp/go-retryablehttp" @@ -34,6 +38,15 @@ func NewProviderFacebook( } } +func (g *ProviderFacebook) generateAppSecretProof(ctx context.Context, exchange *oauth2.Token) string { + secret := g.config.ClientSecret + data := exchange.AccessToken + + h := hmac.New(sha256.New, []byte(secret)) + h.Write([]byte(data)) + return hex.EncodeToString(h.Sum(nil)) +} + func (g *ProviderFacebook) OAuth2(ctx context.Context) (*oauth2.Config, error) { p, err := g.provider(ctx) if err != nil { @@ -52,8 +65,9 @@ func (g *ProviderFacebook) Claims(ctx context.Context, exchange *oauth2.Token, q return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err)) } + appSecretProof := g.generateAppSecretProof(ctx, exchange) client := g.reg.HTTPClient(ctx, httpx.ResilientClientWithClient(o.Client(ctx, exchange))) - u, err := url.Parse("https://graph.facebook.com/me?fields=id,name,first_name,last_name,middle_name,email,picture,birthday,gender") + u, err := url.Parse(fmt.Sprintf("https://graph.facebook.com/me?fields=id,name,first_name,last_name,middle_name,email,picture,birthday,gender&appsecret_proof=%s", appSecretProof)) if err != nil { return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("%s", err)) } From 52bffeeeab311fbbaf66f3713ec8c19629cf3ae5 Mon Sep 17 00:00:00 2001 From: Dejan Filipovic Date: Tue, 28 Jun 2022 18:54:20 +0200 Subject: [PATCH 2/3] fix: add test for Facebook provider --- go.mod | 1 + go.sum | 1 + .../strategy/oidc/provider_facebook_test.go | 90 +++++++++++++++++++ .../strategy/oidc/stub/oidc.facebook.jsonnet | 13 +++ 4 files changed, 105 insertions(+) create mode 100644 selfservice/strategy/oidc/provider_facebook_test.go create mode 100644 selfservice/strategy/oidc/stub/oidc.facebook.jsonnet diff --git a/go.mod b/go.mod index db50ded5817e..1669ac017013 100644 --- a/go.mod +++ b/go.mod @@ -58,6 +58,7 @@ require ( github.com/hashicorp/golang-lru v0.5.4 github.com/imdario/mergo v0.3.12 github.com/inhies/go-bytesize v0.0.0-20210819104631-275770b98743 + github.com/jarcoal/httpmock v1.0.5 github.com/jteeuwen/go-bindata v3.0.7+incompatible github.com/julienschmidt/httprouter v1.3.0 github.com/knadh/koanf v1.4.0 diff --git a/go.sum b/go.sum index e2ba2e6c1705..57e1e72adf6c 100644 --- a/go.sum +++ b/go.sum @@ -1142,6 +1142,7 @@ github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv github.com/jandelgado/gcov2lcov v1.0.4/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3cchlbtgFYZSss= github.com/jandelgado/gcov2lcov v1.0.5 h1:rkBt40h0CVK4oCb8Dps950gvfd1rYvQ8+cWa346lVU0= github.com/jandelgado/gcov2lcov v1.0.5/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3cchlbtgFYZSss= +github.com/jarcoal/httpmock v1.0.5 h1:cHtVEcTxRSX4J0je7mWPfc9BpDpqzXSJ5HbymZmyHck= github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jcchavezs/porto v0.1.0/go.mod h1:fESH0gzDHiutHRdX2hv27ojnOVFco37hg1W6E9EZF4A= diff --git a/selfservice/strategy/oidc/provider_facebook_test.go b/selfservice/strategy/oidc/provider_facebook_test.go new file mode 100644 index 000000000000..33d28012b7ce --- /dev/null +++ b/selfservice/strategy/oidc/provider_facebook_test.go @@ -0,0 +1,90 @@ +package oidc_test + +import ( + "context" + "net/http" + "net/url" + "testing" + "time" + + "github.com/jarcoal/httpmock" + + "github.com/ory/kratos/internal" + "github.com/ory/kratos/selfservice/strategy/oidc" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/oauth2" +) + +const fakeIDToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjk5OTk5OTk5OTksImF1ZCI6ImFiY2QiLCJpc3MiOiJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vYWVuZWFzci9wcml2YXRlLW9pZGMvbWFzdGVyL3Rva2VuIn0.G9v8pJXJrEOgdJ5ecE6sIIcTH_p-RKkBaImfZY5DDVCl7h5GEis1n3GKKYbL_O3fj8Fu-WzI2mquI8S8BOVCQ6wN0XtrqJv22iX_nzeVHc4V_JWV1q7hg2gPpoFFcnF3KKtxZLvDOA8ujsDbAXmoBu0fEBdwCN56xLOOKQDzULyfijuAa8hrCwespZ9HaqcHzD3iHf_Utd4nHqlTM-6upWpKIMkplS_NGcxrfIRIWusZ0wob6ryy8jECD9QeZpdTGUozq-YM64lZfMOZzuLuqichH_PCMKFyB_tOZb6lDIiiSX4Irz7_YF-DP-LmfxgIW4934RqTCeFGGIP64h4xAA" + +func TestProviderFacebook_Claims(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + httpmock.RegisterResponder("GET", "https://graph.facebook.com/me", + func(req *http.Request) (*http.Response, error) { + if _, ok := req.URL.Query()["appsecret_proof"]; !ok { + resp, err := httpmock.NewJsonResponse(400, map[string]interface{}{ + "error": map[string]interface{}{ + "message": "API calls from the server require an appsecret_proof argument", + "type": "GraphMethodException", + "code": 100, + "fbtrace_id": "Ay8LR3n5BsHm809VYpJ3eDM", + }, + }) + 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 + }, + ) + + 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 + }, + ) + + _, reg := internal.NewFastRegistryWithMocks(t) + + c := &oidc.Configuration{ + ID: "facebook", + Provider: "facebook", + ClientID: "abcd", + ClientSecret: "secret", + Mapper: "file://./stub/oidc.facebook.jsonnet", + Scope: []string{"email"}, + } + facebook := oidc.NewProviderFacebook(c, reg) + + claims, err := facebook.Claims(context.Background(), (&oauth2.Token{AccessToken: "foo", Expiry: time.Now().Add(+time.Hour)}).WithExtra(map[string]interface{}{ + "id_token": fakeIDToken, + }), url.Values{}) + require.NoError(t, err) + + expected := &oidc.Claims{ + Issuer: "https://graph.facebook.com/me?fields=id,name,first_name,last_name,middle_name,email,picture,birthday,gender&appsecret_proof=773ba44693c7553d6ee20f61ea5d2757a9a4f4a44d2841ae4e95b52e4cd62db4", + 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", + } + assert.Equal(t, claims, expected) + +} diff --git a/selfservice/strategy/oidc/stub/oidc.facebook.jsonnet b/selfservice/strategy/oidc/stub/oidc.facebook.jsonnet new file mode 100644 index 000000000000..4a1c10265aa9 --- /dev/null +++ b/selfservice/strategy/oidc/stub/oidc.facebook.jsonnet @@ -0,0 +1,13 @@ +local claims = std.extVar('claims'); + +if std.length(claims.sub) == 0 then + error 'claim sub not set' +else + { + identity: { + traits: { + subject: claims.sub, + [if "email" in claims then "email" else null]: claims.email, + }, + }, + } From 0a3c46bbb604aeda641a95ca9263a50ec16824c1 Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Tue, 5 Jul 2022 15:05:02 +0100 Subject: [PATCH 3/3] chore: code review --- .../strategy/oidc/provider_facebook_test.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/selfservice/strategy/oidc/provider_facebook_test.go b/selfservice/strategy/oidc/provider_facebook_test.go index 33d28012b7ce..99062edcaa60 100644 --- a/selfservice/strategy/oidc/provider_facebook_test.go +++ b/selfservice/strategy/oidc/provider_facebook_test.go @@ -57,7 +57,6 @@ func TestProviderFacebook_Claims(t *testing.T) { ) _, reg := internal.NewFastRegistryWithMocks(t) - c := &oidc.Configuration{ ID: "facebook", Provider: "facebook", @@ -68,12 +67,14 @@ func TestProviderFacebook_Claims(t *testing.T) { } facebook := oidc.NewProviderFacebook(c, reg) - claims, err := facebook.Claims(context.Background(), (&oauth2.Token{AccessToken: "foo", Expiry: time.Now().Add(+time.Hour)}).WithExtra(map[string]interface{}{ - "id_token": fakeIDToken, - }), url.Values{}) + actual, err := facebook.Claims( + context.Background(), + (&oauth2.Token{AccessToken: "foo", Expiry: time.Now().Add(time.Hour)}).WithExtra(map[string]interface{}{"id_token": fakeIDToken}), + url.Values{}, + ) require.NoError(t, err) - expected := &oidc.Claims{ + assert.Equal(t, &oidc.Claims{ Issuer: "https://graph.facebook.com/me?fields=id,name,first_name,last_name,middle_name,email,picture,birthday,gender&appsecret_proof=773ba44693c7553d6ee20f61ea5d2757a9a4f4a44d2841ae4e95b52e4cd62db4", Subject: "123456789012345", Name: "John Doe", @@ -84,7 +85,5 @@ func TestProviderFacebook_Claims(t *testing.T) { Email: "john.doe@example.com", EmailVerified: true, Birthdate: "01/01/1990", - } - assert.Equal(t, claims, expected) - + }, actual) }