Skip to content

Commit 2ae6cf7

Browse files
committed
Upgrade to v2 API
* switch to use `r_liteprofile` (reduce number of fields) * parse `profilePicture` response (use last picture in the array) Email parsing comes next.
1 parent 2ad7910 commit 2ae6cf7

File tree

2 files changed

+79
-46
lines changed

2 files changed

+79
-46
lines changed

lib/omniauth/strategies/linkedin.rb

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,31 @@ class LinkedIn < OmniAuth::Strategies::OAuth2
1414
:token_url => 'https://www.linkedin.com/oauth/v2/accessToken'
1515
}
1616

17-
option :scope, 'r_basicprofile r_emailaddress'
18-
option :fields, ['id', 'email-address', 'first-name', 'last-name', 'headline', 'location', 'industry', 'picture-url', 'public-profile-url']
17+
option :scope, 'r_liteprofile r_emailaddress'
18+
option :fields, ['id', 'firstName', 'lastName', 'profilePicture(displayImage~:playableStreams)']
1919

2020
# These are called after authentication has succeeded. If
2121
# possible, you should try to set the UID without making
2222
# additional calls (if the user id is returned with the token
2323
# or as a URI parameter). This may not be possible with all
2424
# providers.
25-
uid { raw_info['id'] }
25+
uid do
26+
raw_info['id']
27+
end
2628

2729
info do
2830
{
29-
:name => user_name,
30-
:email => raw_info['emailAddress'],
31-
:nickname => user_name,
32-
:first_name => raw_info['firstName'],
33-
:last_name => raw_info['lastName'],
34-
:location => raw_info['location'],
35-
:description => raw_info['headline'],
36-
:image => raw_info['pictureUrl'],
37-
:urls => {
38-
'public_profile' => raw_info['publicProfileUrl']
39-
}
31+
:email => nil,
32+
:first_name => localized_field('firstName'),
33+
:last_name => localized_field('lastName'),
34+
:picture_url => picture_url
4035
}
4136
end
4237

4338
extra do
44-
{ 'raw_info' => raw_info }
39+
{
40+
'raw_info' => raw_info
41+
}
4542
end
4643

4744
def callback_url
@@ -52,22 +49,50 @@ def callback_url
5249

5350
def access_token
5451
::OAuth2::AccessToken.new(client, oauth2_access_token.token, {
55-
:mode => :query,
56-
:param_name => 'oauth2_access_token',
5752
:expires_in => oauth2_access_token.expires_in,
5853
:expires_at => oauth2_access_token.expires_at
5954
})
6055
end
6156

6257
def raw_info
63-
@raw_info ||= access_token.get("/v1/people/~:(#{options.fields.join(',')})?format=json").parsed
58+
@raw_info ||= access_token.get(profile_endpoint).parsed
6459
end
6560

6661
private
6762

68-
def user_name
69-
name = "#{raw_info['firstName']} #{raw_info['lastName']}".strip
70-
name.empty? ? nil : name
63+
def localized_field field_name
64+
return unless localized_field_available? field_name
65+
66+
raw_info[field_name]['localized'][field_locale(field_name)]
67+
end
68+
69+
def field_locale field_name
70+
"#{ raw_info[field_name]['preferredLocale']['language'] }_" \
71+
"#{ raw_info[field_name]['preferredLocale']['country'] }"
72+
end
73+
74+
def localized_field_available? field_name
75+
raw_info[field_name] && raw_info[field_name]['localized']
76+
end
77+
78+
def picture_url
79+
return unless picture_available?
80+
81+
picture_references.last['identifiers'].first['identifier']
82+
end
83+
84+
def picture_available?
85+
raw_info['profilePicture'] &&
86+
raw_info['profilePicture']['displayImage~'] &&
87+
picture_references
88+
end
89+
90+
def picture_references
91+
raw_info['profilePicture']['displayImage~']['elements']
92+
end
93+
94+
def profile_endpoint
95+
"/v2/me?projection=(#{ options.fields.join(',') })"
7196
end
7297
end
7398
end

spec/omniauth/strategies/linkedin_spec.rb

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
describe OmniAuth::Strategies::LinkedIn do
55
subject { OmniAuth::Strategies::LinkedIn.new(nil) }
66

7-
it 'should add a camelization for itself' do
7+
it 'adds camelization for itself' do
88
expect(OmniAuth::Utils.camelize('linkedin')).to eq('LinkedIn')
99
end
1010

@@ -13,11 +13,11 @@
1313
expect(subject.client.site).to eq('https://api.linkedin.com')
1414
end
1515

16-
it 'has correct authorize url' do
16+
it 'has correct `authorize_url`' do
1717
expect(subject.client.options[:authorize_url]).to eq('https://www.linkedin.com/oauth/v2/authorization?response_type=code')
1818
end
1919

20-
it 'has correct token url' do
20+
it 'has correct `token_url`' do
2121
expect(subject.client.options[:token_url]).to eq('https://www.linkedin.com/oauth/v2/accessToken')
2222
end
2323
end
@@ -30,7 +30,7 @@
3030

3131
describe '#uid' do
3232
before :each do
33-
allow(subject).to receive(:raw_info) { { 'id' => 'uid' } }
33+
allow(subject).to receive(:raw_info) { Hash['id' => 'uid'] }
3434
end
3535

3636
it 'returns the id from raw_info' do
@@ -44,47 +44,55 @@
4444
end
4545

4646
context 'and therefore has all the necessary fields' do
47-
it { expect(subject.info).to have_key :name }
48-
it { expect(subject.info).to have_key :email }
49-
it { expect(subject.info).to have_key :nickname }
50-
it { expect(subject.info).to have_key :first_name }
51-
it { expect(subject.info).to have_key :last_name }
52-
it { expect(subject.info).to have_key :location }
53-
it { expect(subject.info).to have_key :description }
54-
it { expect(subject.info).to have_key :image }
55-
it { expect(subject.info).to have_key :urls }
47+
specify { expect(subject.info).to have_key :email }
48+
specify { expect(subject.info).to have_key :first_name }
49+
specify { expect(subject.info).to have_key :last_name }
50+
specify { expect(subject.info).to have_key :picture_url }
5651
end
5752
end
5853

5954
describe '#extra' do
55+
let(:raw_info) { Hash[:foo => 'bar'] }
56+
6057
before :each do
61-
allow(subject).to receive(:raw_info) { { :foo => 'bar' } }
58+
allow(subject).to receive(:raw_info).and_return raw_info
6259
end
6360

64-
it { expect(subject.extra['raw_info']).to eq({ :foo => 'bar' }) }
61+
specify { expect(subject.extra['raw_info']).to eq raw_info }
6562
end
6663

6764
describe '#access_token' do
65+
let(:expires_in) { 3600 }
66+
let(:expires_at) { 946688400 }
67+
let(:token) { 'token' }
68+
let(:access_token) do
69+
instance_double OAuth2::AccessToken, :expires_in => expires_in,
70+
:expires_at => expires_at, :token => token
71+
end
72+
6873
before :each do
69-
allow(subject).to receive(:oauth2_access_token) { double('oauth2 access token', :expires_in => 3600, :expires_at => 946688400).as_null_object }
74+
allow(subject).to receive(:oauth2_access_token).and_return access_token
7075
end
7176

72-
it { expect(subject.access_token.expires_in).to eq(3600) }
73-
it { expect(subject.access_token.expires_at).to eq(946688400) }
77+
specify { expect(subject.access_token.expires_in).to eq expires_in }
78+
specify { expect(subject.access_token.expires_at).to eq expires_at }
7479
end
7580

7681
describe '#raw_info' do
82+
let(:access_token) { instance_double OAuth2::AccessToken }
83+
let(:parsed_response) { Hash[:foo => 'bar'] }
84+
let(:response) { instance_double OAuth2::Response, parsed: parsed_response }
85+
let(:profile_endpoint) { '/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))' }
86+
7787
before :each do
78-
access_token = double('access token')
79-
response = double('response', :parsed => { :foo => 'bar' })
88+
allow(subject).to receive(:access_token).and_return access_token
89+
8090
expect(access_token).to receive(:get)
81-
.with("/v1/people/~:(id,email-address,first-name,last-name,headline,location,industry,picture-url,public-profile-url)?format=json")
91+
.with(profile_endpoint)
8292
.and_return(response)
83-
84-
allow(subject).to receive(:access_token) { access_token }
8593
end
8694

87-
it 'returns parsed response from access token' do
95+
it 'returns parsed response from the access token' do
8896
expect(subject.raw_info).to eq({ :foo => 'bar' })
8997
end
9098
end
@@ -96,7 +104,7 @@
96104
end
97105

98106
it 'sets default scope' do
99-
expect(subject.authorize_params['scope']).to eq('r_basicprofile r_emailaddress')
107+
expect(subject.authorize_params['scope']).to eq('r_liteprofile r_emailaddress')
100108
end
101109
end
102110
end

0 commit comments

Comments
 (0)