Repository: oauth2-proxy/oauth2-proxy
Pull request #1315 (2021-08-09 17:49:18)

linkedidn: Update provider to v2
<!--- Provide a general summary of your changes in the Title above -->

## Description
Update the linkedin provider code to work with v2 of the API.

<!--- Describe your changes in detail -->

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->
Linkedin provider was no longer working

## How Has This Been Tested?

It is being used at https://try.ntropy.network - docker image built with this working: `wuurrd/oauth2-proxy` 
  
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] My change requires a change to the documentation or CHANGELOG.
- [x] I have updated the documentation/CHANGELOG accordingly.
- [x] I have created a feature (non-master) branch for my PR.


JoelSpeed: Do you happen to have any reference that shows that the old APIs were deprecated and/or removed? Could you expand on why this is broken today?

If this is completely broken then we can accept this as a fix, but if it does still work then this would be considered a breaking change
wuurrd: I have no reference, but it is broken (getting 404 not found and similar)
JoelSpeed: This is probably a sufficient reference, they dropped support in May 2019, so we definitely need to update the provider
https://engineering.linkedin.com/blog/2018/12/developer-program-updates
NickMeves: > This is probably a sufficient reference, they dropped support in May 2019, so we definitely need to update the provider
> https://engineering.linkedin.com/blog/2018/12/developer-program-updates

Guess we had no users of this provider if it has been broken for 2 years 🤣 

I would say can we drop it, but @wuurrd being here with this PR is proof that they are a potential user 😄 
wuurrd: It works with this PR, so it's probably worth keeping it?
JoelSpeed: Might also be worth double checking our docs in case they have any specific references that need updating :thinking: 
NickMeves: Good idea -- section is short: https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/#linkedin-auth-provider

@wuurrd Are those setup instructions still up to date or have things changed with LinkedIn?
wuurrd: @NickMeves only difference is 

`In default scope, select r_basicprofile and r_emailaddress.`

this should now say

`In default scope, select r_liteprofile and r_emailaddress.`
wuurrd: @JoelSpeed merge?
NickMeves: > @JoelSpeed merge?

We need a CHANGELOG entry added then we can merge 👍 
wuurrd: @NickMeves can I leave this to you? I'm not sure where in the changelog you'd like that.
JoelSpeed: It should be under this section https://github.com/oauth2-proxy/oauth2-proxy/blob/master/CHANGELOG.md#changes-since-v713
wuurrd: @JoelSpeed this is ready for another review

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5e2ee2a6..727ebd46 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@
 
 ## Changes since v7.1.3
 
+- [#1315](https://github.com/oauth2-proxy/oauth2-proxy/pull/1315) linkedin: Update provider to v2 (@wuurrd)
 - [#1348](https://github.com/oauth2-proxy/oauth2-proxy/pull/1348) Using the native httputil proxy code for websockets rather than yhat/wsutil to properly handle HTTP-level failures (@thetrime)
 - [#1379](https://github.com/oauth2-proxy/oauth2-proxy/pull/1379) Fix the manual sign in with --htpasswd-user-group switch (@janrotter)
 - [#1337](https://github.com/oauth2-proxy/oauth2-proxy/pull/1337) Changing user field type to text when using htpasswd (@pburgisser)
diff --git a/providers/linkedin.go b/providers/linkedin.go
index 115d4c99..9e5135ab 100644
--- a/providers/linkedin.go
+++ b/providers/linkedin.go
@@ -19,7 +19,7 @@ var _ Provider = (*LinkedInProvider)(nil)
 
 const (
 	linkedinProviderName = "LinkedIn"
-	linkedinDefaultScope = "r_emailaddress r_basicprofile"
+	linkedinDefaultScope = "r_emailaddress r_liteprofile"
 )
 
 var (
@@ -28,7 +28,7 @@ var (
 	linkedinDefaultLoginURL = &url.URL{
 		Scheme: "https",
 		Host:   "www.linkedin.com",
-		Path:   "/uas/oauth2/authorization",
+		Path:   "/oauth/v2/authorization",
 	}
 
 	// Default Redeem URL for LinkedIn.
@@ -43,8 +43,8 @@ var (
 	// Pre-parsed URL of https://www.linkedin.com/v1/people/~/email-address.
 	linkedinDefaultProfileURL = &url.URL{
 		Scheme: "https",
-		Host:   "www.linkedin.com",
-		Path:   "/v1/people/~/email-address",
+		Host:   "api.linkedin.com",
+		Path:   "/v2/emailAddress",
 	}
 )
 
@@ -76,7 +76,7 @@ func (p *LinkedInProvider) GetEmailAddress(ctx context.Context, s *sessions.Sess
 		return "", errors.New("missing access token")
 	}
 
-	requestURL := p.ProfileURL.String() + "?format=json"
+	requestURL := p.ProfileURL.String() + "?q=members&projection=(elements*(handle~))"
 	json, err := requests.New(requestURL).
 		WithContext(ctx).
 		WithHeaders(makeLinkedInHeader(s.AccessToken)).
@@ -85,8 +85,7 @@ func (p *LinkedInProvider) GetEmailAddress(ctx context.Context, s *sessions.Sess
 	if err != nil {
 		return "", err
 	}
-
-	email, err := json.String()
+	email, err := json.Get("elements").GetIndex(0).Get("handle~").Get("emailAddress").String()
 	if err != nil {
 		return "", err
 	}


Test patch:
diff --git a/providers/linkedin_test.go b/providers/linkedin_test.go
index 1ba0c184..1d5bd90a 100644
--- a/providers/linkedin_test.go
+++ b/providers/linkedin_test.go
@@ -30,7 +30,7 @@ func testLinkedInProvider(hostname string) *LinkedInProvider {
 }
 
 func testLinkedInBackend(payload string) *httptest.Server {
-	path := "/v1/people/~/email-address"
+	path := "/v2/emailAddress"
 
 	return httptest.NewServer(http.HandlerFunc(
 		func(w http.ResponseWriter, r *http.Request) {
@@ -51,11 +51,11 @@ func TestNewLinkedInProvider(t *testing.T) {
 	// Test that defaults are set when calling for a new provider with nothing set
 	providerData := NewLinkedInProvider(&ProviderData{}).Data()
 	g.Expect(providerData.ProviderName).To(Equal("LinkedIn"))
-	g.Expect(providerData.LoginURL.String()).To(Equal("https://www.linkedin.com/uas/oauth2/authorization"))
+	g.Expect(providerData.LoginURL.String()).To(Equal("https://www.linkedin.com/oauth/v2/authorization"))
 	g.Expect(providerData.RedeemURL.String()).To(Equal("https://www.linkedin.com/uas/oauth2/accessToken"))
-	g.Expect(providerData.ProfileURL.String()).To(Equal("https://www.linkedin.com/v1/people/~/email-address"))
-	g.Expect(providerData.ValidateURL.String()).To(Equal("https://www.linkedin.com/v1/people/~/email-address"))
-	g.Expect(providerData.Scope).To(Equal("r_emailaddress r_basicprofile"))
+	g.Expect(providerData.ProfileURL.String()).To(Equal("https://api.linkedin.com/v2/emailAddress"))
+	g.Expect(providerData.ValidateURL.String()).To(Equal("https://api.linkedin.com/v2/emailAddress"))
+	g.Expect(providerData.Scope).To(Equal("r_emailaddress r_liteprofile"))
 }
 
 func TestLinkedInProviderOverrides(t *testing.T) {
@@ -92,7 +92,7 @@ func TestLinkedInProviderOverrides(t *testing.T) {
 }
 
 func TestLinkedInProviderGetEmailAddress(t *testing.T) {
-	b := testLinkedInBackend(`"user@linkedin.com"`)
+	b := testLinkedInBackend(`{"elements":[{"handle~":{"emailAddress": "user@linkedin.com"}}]}`)
 	defer b.Close()
 
 	bURL, _ := url.Parse(b.URL)