Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 88 additions & 16 deletions lib/omniauth/strategies/linkedin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@
module OmniAuth
module Strategies
class LinkedIn < OmniAuth::Strategies::OAuth2
V1_TO_V2_FIELD_MAP = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I don't think making compatibility attempts with v1 make much sense given how the deprecation time is approaching. Best to just remove v1 cruft and bump major version of this gem, I think.

'id' => 'id',
'email-address' => nil,
'first-name' => 'localizedFirstName',
'last-name' => 'localizedLastName',
'headline' => 'headline',
'location' => nil,
'industry' => 'industryName',
'picture-url' => 'profilePicture(displayImage~:playableStreams)',
'public-profile-url' => 'vanityName'
}

PROFILE_ENDPOINT = {
'v1' => '/v1/people/~',
'v2' => '/v2/me'
}

# Give your strategy a name.
option :name, 'linkedin'

Expand All @@ -16,6 +33,7 @@ class LinkedIn < OmniAuth::Strategies::OAuth2

option :scope, 'r_basicprofile r_emailaddress'
option :fields, ['id', 'email-address', 'first-name', 'last-name', 'headline', 'location', 'industry', 'picture-url', 'public-profile-url']
option :api_version, 'v1'

# These are called after authentication has succeeded. If
# possible, you should try to set the UID without making
Expand All @@ -25,19 +43,35 @@ class LinkedIn < OmniAuth::Strategies::OAuth2
uid { raw_info['id'] }

info do
{
:name => user_name,
:email => raw_info['emailAddress'],
:nickname => user_name,
:first_name => raw_info['firstName'],
:last_name => raw_info['lastName'],
:location => raw_info['location'],
:description => raw_info['headline'],
:image => raw_info['pictureUrl'],
:urls => {
'public_profile' => raw_info['publicProfileUrl']
if options.api_version == "v1"
{
:name => user_name,
:email => raw_info['emailAddress'],
:nickname => user_name,
:first_name => raw_info['firstName'],
:last_name => raw_info['lastName'],
:location => raw_info['location'],
:description => raw_info['headline'],
:image => raw_info['pictureUrl'],
:urls => {
'public_profile' => raw_info['publicProfileUrl']
}
}
}
elsif options.api_version == "v2"
{
:name => user_name,
:email => nil,
:nickname => user_name,
:first_name => raw_info['localizedFirstName'],
:last_name => raw_info['localizedLastName'],
:location => nil,
:description => localized_field(raw_info['headline']),
:image => profile_picture,
:urls => {
'public_profile' => "https://www.linkedin.com/in/#{raw_info['vanityName']}"
}
}
end
end

extra do
Expand All @@ -60,21 +94,59 @@ def access_token
end

def raw_info
@raw_info ||= access_token.get("/v1/people/~:(#{option_fields.join(',')})?format=json").parsed
@raw_info ||= access_token.get(profile_endpoint).parsed
end

private

def option_fields
fields = options.fields
fields.map! { |f| f == "picture-url" ? "picture-url;secure=true" : f } if !!options[:secure_image_url]
fields
fields.map! do |f|
if options.api_version == 'v2'
V1_TO_V2_FIELD_MAP.fetch(f,f)
elsif !!options[:secure_image_url] && f == 'picture-url'
"picture-url;secure=true"
else
f
end
end
fields.compact
end

def localized_field(field)
return nil unless field
locale = "#{field['preferredLocale']['language']}_#{field['preferredLocale']['country']}"
field['localized'][locale]
end

def profile_picture
return nil if raw_info['profilePicture'].to_s.empty?
raw_info['profilePicture']['displayImage~']['elements'].first['identifiers'].first['identifier']
end

def first_name
raw_info['firstName'] || raw_info['localizedFirstName']
end

def last_name
raw_info['lastName'] || raw_info['localizedLastName']
end

def user_name
name = "#{raw_info['firstName']} #{raw_info['lastName']}".strip
name = "#{first_name} #{last_name}"
name.empty? ? nil : name
end

def profile_endpoint
suffix = case options.api_version
when 'v1'
":(#{option_fields.join(',')})?format=json"
when 'v2'
"?projection=(#{option_fields.join(',')})"
end

PROFILE_ENDPOINT[options.api_version] + suffix
end
end
end
end
Expand Down
138 changes: 109 additions & 29 deletions spec/omniauth/strategies/linkedin_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,34 @@
allow(subject).to receive(:raw_info) { {} }
end

context 'and therefore has all the necessary fields' do
it { expect(subject.info).to have_key :name }
it { expect(subject.info).to have_key :email }
it { expect(subject.info).to have_key :nickname }
it { expect(subject.info).to have_key :first_name }
it { expect(subject.info).to have_key :last_name }
it { expect(subject.info).to have_key :location }
it { expect(subject.info).to have_key :description }
it { expect(subject.info).to have_key :image }
it { expect(subject.info).to have_key :urls }
context 'api_version is v1' do
context 'and therefore has all the necessary fields' do
it { expect(subject.info).to have_key :name }
it { expect(subject.info).to have_key :email }
it { expect(subject.info).to have_key :nickname }
it { expect(subject.info).to have_key :first_name }
it { expect(subject.info).to have_key :last_name }
it { expect(subject.info).to have_key :location }
it { expect(subject.info).to have_key :description }
it { expect(subject.info).to have_key :image }
it { expect(subject.info).to have_key :urls }
end
end

context 'api_version is v2' do
before :each do
subject.stub(:options => double('options', :api_version => 'v2').as_null_object)
end

context 'and therefore has all the necessary fields' do
it { expect(subject.info).to have_key :name }
it { expect(subject.info).to have_key :nickname }
it { expect(subject.info).to have_key :first_name }
it { expect(subject.info).to have_key :last_name }
it { expect(subject.info).to have_key :description }
it { expect(subject.info).to have_key :image }
it { expect(subject.info).to have_key :urls }
end
end
end

Expand All @@ -75,16 +93,34 @@

describe '#raw_info' do
before :each do
access_token = double('access token')
response = double('response', :parsed => { :foo => 'bar' })
expect(access_token).to receive(:get).with("/v1/people/~:(baz,qux)?format=json").and_return(response)

allow(subject).to receive(:option_fields) { ['baz', 'qux'] }
allow(subject).to receive(:access_token) { access_token }
end

it 'returns parsed response from access token' do
expect(subject.raw_info).to eq({ :foo => 'bar' })
context 'api_version is v1' do
before :each do
response = double('response', :parsed => { :foo => 'bar' })
access_token = double('access token')
expect(access_token).to receive(:get).with("/v1/people/~:(baz,qux)?format=json").and_return(response)
allow(subject).to receive(:access_token) { access_token }
end

it 'returns parsed response from access token' do
expect(subject.raw_info).to eq({ :foo => 'bar' })
end
end

context 'api_version is v2' do
before :each do
response = double('response', :parsed => { :foo => 'bar' })
access_token = double('access token')
expect(access_token).to receive(:get).with("/v2/me?projection=(baz,qux)").and_return(response)
allow(subject).to receive(:access_token) { access_token }
subject.stub(:options => double('options', :api_version => 'v2').as_null_object)
end

it 'returns parsed response from access token' do
expect(subject.raw_info).to eq({ :foo => 'bar' })
end
end
end

Expand All @@ -101,21 +137,65 @@
end

describe '#option_fields' do
it 'returns options fields' do
subject.stub(:options => double('options', :fields => ['foo', 'bar']).as_null_object)
expect(subject.send(:option_fields)).to eq(['foo', 'bar'])
end
context 'api_version is v1' do
it 'returns options fields' do
subject.stub(:options => double('options', :fields => ['foo', 'bar'], :api_version => 'v1').as_null_object)
expect(subject.send(:option_fields)).to eq(['foo', 'bar'])
end

it 'http avatar image by default' do
subject.stub(:options => double('options', :fields => ['picture-url'], :api_version => 'v1'))
allow(subject.options).to receive(:[]).with(:secure_image_url).and_return(false)
expect(subject.send(:option_fields)).to eq(['picture-url'])
end

it 'http avatar image by default' do
subject.stub(:options => double('options', :fields => ['picture-url']))
allow(subject.options).to receive(:[]).with(:secure_image_url).and_return(false)
expect(subject.send(:option_fields)).to eq(['picture-url'])
it 'https avatar image if secure_image_url truthy' do
subject.stub(:options => double('options', :fields => ['picture-url'], :api_version => 'v1'))
allow(subject.options).to receive(:[]).with(:secure_image_url).and_return(true)
expect(subject.send(:option_fields)).to eq(['picture-url;secure=true'])
end
end

it 'https avatar image if secure_image_url truthy' do
subject.stub(:options => double('options', :fields => ['picture-url']))
allow(subject.options).to receive(:[]).with(:secure_image_url).and_return(true)
expect(subject.send(:option_fields)).to eq(['picture-url;secure=true'])
context 'api_version is v2' do
it 'returns options fields' do
subject.stub(:options => double('options', :fields => ['foo', 'bar'], :api_version => 'v2').as_null_object)
expect(subject.send(:option_fields)).to eq(['foo', 'bar'])
end

it 'converts picture-url to the profilePicture projection' do
subject.stub(:options => double('options', :fields => ['picture-url'], :api_version => 'v2').as_null_object)
expect(subject.send(:option_fields)).to eq(['profilePicture(displayImage~:playableStreams)'])
end

it 'converts first-name to the localizedFirstName projection' do
subject.stub(:options => double('options', :fields => ['first-name'], :api_version => 'v2').as_null_object)
expect(subject.send(:option_fields)).to eq(['localizedFirstName'])
end

it 'converts last-name to the localizedLastName projection' do
subject.stub(:options => double('options', :fields => ['last-name'], :api_version => 'v2').as_null_object)
expect(subject.send(:option_fields)).to eq(['localizedLastName'])
end

it 'converts industry to the industryName projection' do
subject.stub(:options => double('options', :fields => ['industry'], :api_version => 'v2').as_null_object)
expect(subject.send(:option_fields)).to eq(['industryName'])
end

it 'converts public-profile-url to the vanityName projection' do
subject.stub(:options => double('options', :fields => ['public-profile-url'], :api_version => 'v2').as_null_object)
expect(subject.send(:option_fields)).to eq(['vanityName'])
end

it 'ignores email-address' do
subject.stub(:options => double('options', :fields => ['email-address'], :api_version => 'v2').as_null_object)
expect(subject.send(:option_fields)).to eq([])
end

it 'ignores location' do
subject.stub(:options => double('options', :fields => ['location'], :api_version => 'v2').as_null_object)
expect(subject.send(:option_fields)).to eq([])
end
end
end
end