Skip to content

Commit 230c5bd

Browse files
committed
Fetch email address from LinkedIn API
1 parent 37a631b commit 230c5bd

File tree

2 files changed

+58
-37
lines changed

2 files changed

+58
-37
lines changed

lib/omniauth/strategies/linkedin.rb

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,24 @@
33
module OmniAuth
44
module Strategies
55
class LinkedIn < OmniAuth::Strategies::OAuth2
6-
# Give your strategy a name.
76
option :name, 'linkedin'
87

9-
# This is where you pass the options you would pass when
10-
# initializing your consumer from the OAuth gem.
118
option :client_options, {
129
:site => 'https://api.linkedin.com',
1310
:authorize_url => 'https://www.linkedin.com/oauth/v2/authorization?response_type=code',
1411
:token_url => 'https://www.linkedin.com/oauth/v2/accessToken'
1512
}
1613

1714
option :scope, 'r_liteprofile r_emailaddress'
18-
option :fields, ['id', 'first-name', 'last-name', 'picture-url']
15+
option :fields, ['id', 'first-name', 'last-name', 'picture-url', 'email-address']
1916

20-
# These are called after authentication has succeeded. If
21-
# possible, you should try to set the UID without making
22-
# additional calls (if the user id is returned with the token
23-
# or as a URI parameter). This may not be possible with all
24-
# providers.
2517
uid do
2618
raw_info['id']
2719
end
2820

2921
info do
3022
{
31-
:email => nil,
23+
:email => email_address,
3224
:first_name => localized_field('firstName'),
3325
:last_name => localized_field('lastName'),
3426
:picture_url => picture_url
@@ -60,6 +52,30 @@ def raw_info
6052

6153
private
6254

55+
def email_address
56+
if options.fields.include? 'email-address'
57+
fetch_email_address
58+
parse_email_address
59+
end
60+
end
61+
62+
def fetch_email_address
63+
@email_address_response ||= access_token.get(email_address_endpoint).parsed
64+
end
65+
66+
def parse_email_address
67+
return unless email_address_available?
68+
69+
@email_address_response['elements'].first['handle~']['emailAddress']
70+
end
71+
72+
def email_address_available?
73+
@email_address_response['elements'] &&
74+
@email_address_response['elements'].is_a?(Array) &&
75+
@email_address_response['elements'].first &&
76+
@email_address_response['elements'].first['handle~']
77+
end
78+
6379
def fields_mapping
6480
{
6581
'id' => 'id',
@@ -71,7 +87,7 @@ def fields_mapping
7187

7288
def fields
7389
options.fields.each.with_object([]) do |field, result|
74-
result << fields_mapping[field]
90+
result << fields_mapping[field] if fields_mapping.has_key? field
7591
end
7692
end
7793

@@ -106,6 +122,10 @@ def picture_references
106122
raw_info['profilePicture']['displayImage~']['elements']
107123
end
108124

125+
def email_address_endpoint
126+
'/v2/emailAddress?q=members&projection=(elements*(handle~))'
127+
end
128+
109129
def profile_endpoint
110130
"/v2/me?projection=(#{ fields.join(',') })"
111131
end

spec/omniauth/strategies/linkedin_spec.rb

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,36 @@
3838
end
3939
end
4040

41-
describe '#info' do
41+
describe '#info / #raw_info' do
42+
let(:access_token) { instance_double OAuth2::AccessToken }
43+
44+
let(:parsed_response) { Hash[:foo => 'bar'] }
45+
46+
let(:profile_endpoint) { '/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))' }
47+
let(:email_address_endpoint) { '/v2/emailAddress?q=members&projection=(elements*(handle~))' }
48+
49+
let(:email_address_response) { instance_double OAuth2::Response, parsed: parsed_response }
50+
let(:profile_response) { instance_double OAuth2::Response, parsed: parsed_response }
51+
4252
before :each do
43-
allow(subject).to receive(:raw_info) { {} }
53+
allow(subject).to receive(:access_token).and_return access_token
54+
55+
allow(access_token).to receive(:get)
56+
.with(email_address_endpoint)
57+
.and_return(email_address_response)
58+
59+
allow(access_token).to receive(:get)
60+
.with(profile_endpoint)
61+
.and_return(profile_response)
4462
end
4563

46-
context 'and therefore has all the necessary fields' do
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 }
64+
it 'returns parsed responses using access token' do
65+
expect(subject.info).to have_key :email
66+
expect(subject.info).to have_key :first_name
67+
expect(subject.info).to have_key :last_name
68+
expect(subject.info).to have_key :picture_url
69+
70+
expect(subject.raw_info).to eq({ :foo => 'bar' })
5171
end
5272
end
5373

@@ -78,25 +98,6 @@
7898
specify { expect(subject.access_token.expires_at).to eq expires_at }
7999
end
80100

81-
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-
87-
before :each do
88-
allow(subject).to receive(:access_token).and_return access_token
89-
90-
expect(access_token).to receive(:get)
91-
.with(profile_endpoint)
92-
.and_return(response)
93-
end
94-
95-
it 'returns parsed response from the access token' do
96-
expect(subject.raw_info).to eq({ :foo => 'bar' })
97-
end
98-
end
99-
100101
describe '#authorize_params' do
101102
describe 'scope' do
102103
before :each do

0 commit comments

Comments
 (0)