linkedin-ruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.md +148 -0
- data/Rakefile +6 -0
- data/lib/linked_in/access_token.rb +24 -0
- data/lib/linked_in/api.rb +60 -0
- data/lib/linked_in/api_resource.rb +180 -0
- data/lib/linked_in/configuration.rb +41 -0
- data/lib/linked_in/connection.rb +32 -0
- data/lib/linked_in/errors.rb +48 -0
- data/lib/linked_in/mash.rb +68 -0
- data/lib/linked_in/oauth2.rb +223 -0
- data/lib/linked_in/organizations.rb +52 -0
- data/lib/linked_in/people.rb +62 -0
- data/lib/linked_in/raise_error.rb +16 -0
- data/lib/linked_in/share_and_social_stream.rb +47 -0
- data/lib/linked_in/version.rb +3 -0
- data/lib/linkedin-ruby.rb +47 -0
- data/linkedin-ruby.gemspec +50 -0
- metadata +178 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
# Configuration for the LinkedIn gem.
|
3
|
+
#
|
4
|
+
# LinkedIn.configure do |config|
|
5
|
+
# config.client_id = ENV["LINKEDIN_CLIENT_ID"]
|
6
|
+
# config.client_secret = ENV["LINKEDIN_CLIENT_SECRET"]
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# The default endpoints for LinkedIn are also stored here.
|
10
|
+
#
|
11
|
+
# LinkedIn uses the term "API key" to refer to "client id". They also
|
12
|
+
# use the term "Secret Key" to refer to "client_secret". We alias those
|
13
|
+
# terms in the config.
|
14
|
+
#
|
15
|
+
# * LinkedIn.config.site = "https://www.linkedin.com"
|
16
|
+
# * LinkedIn.config.token_url = "/oauth/v2/accessToken"
|
17
|
+
# * LinkedIn.config.authorize_url = "/oauth/v2/authorization"
|
18
|
+
class Configuration
|
19
|
+
attr_accessor :api,
|
20
|
+
:site,
|
21
|
+
:scope,
|
22
|
+
:client_id,
|
23
|
+
:token_url,
|
24
|
+
:api_version,
|
25
|
+
:redirect_uri,
|
26
|
+
:authorize_url,
|
27
|
+
:client_secret,
|
28
|
+
:default_profile_fields
|
29
|
+
|
30
|
+
alias_method :api_key, :client_id
|
31
|
+
alias_method :secret_key, :client_secret
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@api = "https://api.linkedin.com"
|
35
|
+
@api_version = "/v2"
|
36
|
+
@site = "https://www.linkedin.com"
|
37
|
+
@token_url = "/oauth/v2/accessToken"
|
38
|
+
@authorize_url = "/oauth/v2/authorization"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
# Used to perform requests against LinkedIn's API.
|
3
|
+
class Connection < ::Faraday::Connection
|
4
|
+
|
5
|
+
def initialize(url=nil, options=nil, &block)
|
6
|
+
|
7
|
+
if url.is_a? Hash
|
8
|
+
options = url
|
9
|
+
url = options[:url]
|
10
|
+
end
|
11
|
+
|
12
|
+
url = default_url if url.nil?
|
13
|
+
|
14
|
+
super url, options, &block
|
15
|
+
|
16
|
+
# We need to use the FlatParamsEncoder so we can pass multiple of
|
17
|
+
# the same param to certain endpoints (like the search API).
|
18
|
+
self.options.params_encoder = ::Faraday::FlatParamsEncoder
|
19
|
+
|
20
|
+
middleware = Faraday::RackBuilder::Handler.new(LinkedIn::RaiseError)
|
21
|
+
self.builder.handlers.push(middleware)
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
private ##############################################################
|
26
|
+
|
27
|
+
|
28
|
+
def default_url
|
29
|
+
LinkedIn.config.api + LinkedIn.config.api_version
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
|
3
|
+
# Raised when users call a deprecated function
|
4
|
+
class Deprecated < StandardError; end
|
5
|
+
|
6
|
+
# Raised when we know requests will be malformed
|
7
|
+
class InvalidRequest < StandardError; end
|
8
|
+
|
9
|
+
# Raised when we get a throttle error from the API
|
10
|
+
class ThrottleError < StandardError; end
|
11
|
+
|
12
|
+
# Raised when LinkedIn returns a non 400+ status code during an OAuth
|
13
|
+
# request.
|
14
|
+
class OAuthError < OAuth2::Error; end
|
15
|
+
|
16
|
+
# Raised when LinkedIn returns a non 400+ status code during an API
|
17
|
+
# request
|
18
|
+
class APIError < OAuth2::Error; end
|
19
|
+
|
20
|
+
module ErrorMessages
|
21
|
+
class << self
|
22
|
+
attr_reader :deprecated,
|
23
|
+
:redirect_uri,
|
24
|
+
:no_auth_code,
|
25
|
+
:no_access_token,
|
26
|
+
:credentials_missing,
|
27
|
+
:redirect_uri_mismatch,
|
28
|
+
:throttled
|
29
|
+
end
|
30
|
+
|
31
|
+
@deprecated = "This has been deprecated by LinkedIn. Check https://developer.linkedin.com to see the latest available API calls"
|
32
|
+
|
33
|
+
@redirect_uri = "You must provide a redirect_uri. Set it in LinkedIn.configure or pass it in as the redirect_uri option. It must exactly match the redirect_uri you set on your application's settings page on LinkedIn's website."
|
34
|
+
|
35
|
+
@no_auth_code = "You must provide the authorization code passed to your redirect uri in the url params"
|
36
|
+
|
37
|
+
@no_access_token = "You must have an access token to use LinkedIn's API. Use the LinkedIn::OAuth2 module to obtain an access token"
|
38
|
+
|
39
|
+
@credentials_missing = "Client credentials do not exist. Please either pass your client_id and client_secret to the LinkedIn::Oauth.new constructor or set them via LinkedIn.configure"
|
40
|
+
|
41
|
+
@redirect_uri_mismatch = "Redirect URI mismatch error. Please check your the redierct uri params"
|
42
|
+
|
43
|
+
def klass
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
# Coerces LinkedIn JSON to a nice Ruby hash
|
3
|
+
# LinkedIn::Mash inherits from Hashie::Mash
|
4
|
+
class Mash < ::Hashie::Mash
|
5
|
+
|
6
|
+
# a simple helper to convert a json string to a Mash
|
7
|
+
def self.from_json(json_string)
|
8
|
+
result_hash = JSON.load(json_string)
|
9
|
+
new(result_hash)
|
10
|
+
end
|
11
|
+
|
12
|
+
# returns a Date if we have year, month and day, and no conflicting key
|
13
|
+
def to_date
|
14
|
+
if !self.has_key?('to_date') && contains_date_fields?
|
15
|
+
Date.civil(self.year, self.month, self.day)
|
16
|
+
else
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def timestamp
|
22
|
+
value = self['timestamp']
|
23
|
+
if value.kind_of? Integer
|
24
|
+
value = value / 1000 if value > 9999999999
|
25
|
+
Time.at(value)
|
26
|
+
else
|
27
|
+
value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
protected ############################################################
|
33
|
+
|
34
|
+
|
35
|
+
def contains_date_fields?
|
36
|
+
self.year? && self.month? && self.day?
|
37
|
+
end
|
38
|
+
|
39
|
+
# overload the convert_key mash method so that the LinkedIn
|
40
|
+
# keys are made a little more ruby-ish
|
41
|
+
def convert_key(key)
|
42
|
+
case key.to_s
|
43
|
+
when '_key'
|
44
|
+
'id'
|
45
|
+
when '_total'
|
46
|
+
'total'
|
47
|
+
when 'values'
|
48
|
+
'all'
|
49
|
+
when 'numResults'
|
50
|
+
'total_results'
|
51
|
+
else
|
52
|
+
underscore(key)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# borrowed from ActiveSupport
|
57
|
+
# no need require an entire lib when we only need one method
|
58
|
+
def underscore(camel_cased_word)
|
59
|
+
word = camel_cased_word.to_s.dup
|
60
|
+
word.gsub!(/::/, '/')
|
61
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
62
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
63
|
+
word.tr!("-", "_")
|
64
|
+
word.downcase!
|
65
|
+
word
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
# The LinkedIn::OAuth2::Client class. Inherits directly from [intreda/oauth2](https://github.com/intridea/oauth2)'s `OAuth2::Client`
|
3
|
+
#
|
4
|
+
# LinkedIn::OAuth2 sets the following default options:
|
5
|
+
#
|
6
|
+
# * site = "https://www.linkedin.com"
|
7
|
+
# * token_url = "/uas/oauth2/accessToken"
|
8
|
+
# * authorize_url = "/uas/oauth2/authorization"
|
9
|
+
#
|
10
|
+
# More details on LinkedIn's Authorization process can be found here: https://developer.linkedin.com/documents/authentication
|
11
|
+
class OAuth2 < ::OAuth2::Client
|
12
|
+
|
13
|
+
attr_accessor :access_token
|
14
|
+
|
15
|
+
# Instantiate a new OAuth 2.0 client using your client ID (aka API
|
16
|
+
# Key) and client secret (aka Secret Key).
|
17
|
+
#
|
18
|
+
# You should set the client_id and client_secret in the config.
|
19
|
+
#
|
20
|
+
# LinkedIn.configure do |config|
|
21
|
+
# config.client_id = ENV["LINKEDIN_CLIENT_ID"]
|
22
|
+
# config.client_secret = ENV["LINKEDIN_CLIENT_SECRET"]
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# This will let you initialize with zero arguments.
|
26
|
+
#
|
27
|
+
# If you have already set the `client_id` and `client_secret` in your
|
28
|
+
# config, the first and only argument can be the `options` hash.
|
29
|
+
#
|
30
|
+
# @param [String] client_id the client_id value
|
31
|
+
# @param [String] client_secret the client_secret value
|
32
|
+
# @param [Hash] options the options to create the client with
|
33
|
+
# @option options [Symbol] :token_method (:post) HTTP method to use to
|
34
|
+
# request token (:get or :post)
|
35
|
+
# @option options [Hash] :connection_opts ({}) Hash of connection options
|
36
|
+
# to pass to initialize Faraday with
|
37
|
+
# @option options [FixNum] :max_redirects (5) maximum number of redirects
|
38
|
+
# to follow
|
39
|
+
# @option options [Boolean] :raise_errors (true) whether or not to
|
40
|
+
# raise an error on malformed responses
|
41
|
+
# @yield [builder] The Faraday connection builder
|
42
|
+
def initialize(client_id=LinkedIn.config.client_id,
|
43
|
+
client_secret=LinkedIn.config.client_secret,
|
44
|
+
options = {}, &block)
|
45
|
+
|
46
|
+
if client_id.is_a? Hash
|
47
|
+
options = client_id
|
48
|
+
client_id = LinkedIn.config.client_id
|
49
|
+
end
|
50
|
+
|
51
|
+
options = default_oauth_options(options)
|
52
|
+
|
53
|
+
super client_id, client_secret, options, &block
|
54
|
+
|
55
|
+
@redirect_uri = options[:redirect_uri]
|
56
|
+
|
57
|
+
if self.options[:raise_errors]
|
58
|
+
check_credentials!(client_id, client_secret)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Generates the URL users use to sign into your application.
|
63
|
+
#
|
64
|
+
# Once a user enters their LinkedIn credentials, they will be
|
65
|
+
# redirected to your `redirect_uri` with the `code` parameter attached
|
66
|
+
# to it. The value of the `code` parameter can be used to get an
|
67
|
+
# access token.
|
68
|
+
#
|
69
|
+
# We recommend you set your `client_id, `client_secret`, and
|
70
|
+
# `redirect_uri` in the `LinkedIn.configure` block. They can also be
|
71
|
+
# passed in as options.
|
72
|
+
#
|
73
|
+
# @param [Hash] options the options to generate the url with
|
74
|
+
# @option options [String] :redirect_uri The url that gets redirected
|
75
|
+
# to after a successful authentication. This must exactly match the
|
76
|
+
# redirect urls setup on your LinkedIn Application Settings page.
|
77
|
+
# This option is not required if you already set the redirect_uri in
|
78
|
+
# the config.
|
79
|
+
# @option options [String] :scope A string of requested permissions
|
80
|
+
# you want from users when they authenticate with your app. If these
|
81
|
+
# are set on yoru LinkedIn Application settings page, you do not
|
82
|
+
# need to pass them in. The string must be a space-sparated,
|
83
|
+
# case-sensitive list of available scopes. See available scopes on
|
84
|
+
# LinkedIn's API documentation page.
|
85
|
+
# @option options [String] :state A long string used for CSRF
|
86
|
+
# protection. It is added as the `state` GET param in the
|
87
|
+
# redirect_uri
|
88
|
+
# @option options [Boolean] :raise_errors (true) whether or not to
|
89
|
+
# raise an error on malformed responses
|
90
|
+
def auth_code_url(options={})
|
91
|
+
options = default_auth_code_url_options(options)
|
92
|
+
|
93
|
+
if self.options[:raise_errors]
|
94
|
+
check_redirect_uri!(options)
|
95
|
+
end
|
96
|
+
|
97
|
+
@redirect_uri = options[:redirect_uri]
|
98
|
+
|
99
|
+
self.auth_code.authorize_url(options)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns the access token string for the newly authenticated user.
|
103
|
+
#
|
104
|
+
# It also sets the `access_token` field on this LinkedIn::OAuth2
|
105
|
+
# instance.
|
106
|
+
#
|
107
|
+
# The required `code`
|
108
|
+
#
|
109
|
+
# @param [String] code the auth code which is passed in as a GET
|
110
|
+
# parameter to your `redirect_uri` after users authenticate your app
|
111
|
+
# @param [Hash] options
|
112
|
+
# @option options [String] :redirect_uri You normally should not have
|
113
|
+
# to pass in the redirect_uri again. If `auth_code_url` was called
|
114
|
+
# on this LinkedIn::OAuth2 instance, then the `redirect_uri` will
|
115
|
+
# already be set. This is because the `redirect_uri` in the access
|
116
|
+
# token request must exactly match the `redirect_uri` in the auth
|
117
|
+
# code url.
|
118
|
+
# @option options [Boolean] :raise_errors (true) whether or not to
|
119
|
+
# raise an error on malformed responses
|
120
|
+
def get_access_token(code=nil, options={})
|
121
|
+
check_for_code!(code)
|
122
|
+
options = default_access_code_options(options)
|
123
|
+
|
124
|
+
if self.options[:raise_errors]
|
125
|
+
check_access_code_url!(options)
|
126
|
+
end
|
127
|
+
|
128
|
+
tok = self.auth_code.get_token(code, options.map { |key, value| [key.to_s, value] }.to_h)
|
129
|
+
self.access_token = LinkedIn::AccessToken.new(tok.token,
|
130
|
+
tok.expires_in,
|
131
|
+
tok.expires_at)
|
132
|
+
return self.access_token
|
133
|
+
rescue ::OAuth2::Error => e
|
134
|
+
raise OAuthError.new(e.response)
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
private ##############################################################
|
139
|
+
|
140
|
+
|
141
|
+
def default_access_code_options(custom_options={})
|
142
|
+
custom_options ||= {}
|
143
|
+
options = {raise_errors: true}
|
144
|
+
|
145
|
+
@redirect_uri = LinkedIn.config.redirect_uri if @redirect_uri.nil?
|
146
|
+
options[:redirect_uri] = @redirect_uri
|
147
|
+
|
148
|
+
options = options.merge custom_options
|
149
|
+
return options
|
150
|
+
end
|
151
|
+
|
152
|
+
def default_auth_code_url_options(custom_options={})
|
153
|
+
custom_options ||= {}
|
154
|
+
options = {raise_errors: true}
|
155
|
+
|
156
|
+
if not LinkedIn.config.redirect_uri.nil?
|
157
|
+
options[:redirect_uri] = LinkedIn.config.redirect_uri
|
158
|
+
end
|
159
|
+
if not LinkedIn.config.scope.nil?
|
160
|
+
options[:scope] = LinkedIn.config.scope
|
161
|
+
end
|
162
|
+
|
163
|
+
options = options.merge custom_options
|
164
|
+
|
165
|
+
if options[:state].nil?
|
166
|
+
options[:state] = generate_csrf_token
|
167
|
+
end
|
168
|
+
|
169
|
+
return options
|
170
|
+
end
|
171
|
+
|
172
|
+
def generate_csrf_token
|
173
|
+
SecureRandom.base64(32)
|
174
|
+
end
|
175
|
+
|
176
|
+
def check_access_code_url!(options={})
|
177
|
+
check_redirect_uri!(options)
|
178
|
+
if options[:redirect_uri] != @redirect_uri
|
179
|
+
raise redirect_uri_mismatch
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def check_for_code!(code)
|
184
|
+
if code.nil?
|
185
|
+
msg = ErrorMessages.no_auth_code
|
186
|
+
raise InvalidRequest.new(msg)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def check_redirect_uri!(options={})
|
191
|
+
if options[:redirect_uri].nil?
|
192
|
+
raise redirect_uri_error
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def default_oauth_options(custom_options={})
|
197
|
+
custom_options ||= {}
|
198
|
+
options = {}
|
199
|
+
options[:site] = LinkedIn.config.site
|
200
|
+
options[:token_url] = LinkedIn.config.token_url
|
201
|
+
options[:authorize_url] = LinkedIn.config.authorize_url
|
202
|
+
return options.merge custom_options
|
203
|
+
end
|
204
|
+
|
205
|
+
def check_credentials!(client_id, client_secret)
|
206
|
+
if client_id.nil? or client_secret.nil?
|
207
|
+
raise credential_error
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def redirect_uri_error
|
212
|
+
InvalidRequest.new ErrorMessages.redirect_uri
|
213
|
+
end
|
214
|
+
|
215
|
+
def credential_error
|
216
|
+
InvalidRequest.new ErrorMessages.credentials_missing
|
217
|
+
end
|
218
|
+
|
219
|
+
def redirect_uri_mismatch
|
220
|
+
InvalidRequest.new ErrorMessages.redirect_uri_mismatch
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module LinkedIn
|
2
|
+
# Organizations API
|
3
|
+
#
|
4
|
+
# @see https://developer.linkedin.com/docs/guide/v2/organizations
|
5
|
+
#
|
6
|
+
# [(contribute here)](https://github.com/mallowtechdev/linkedin-ruby)
|
7
|
+
class Organizations < APIResource
|
8
|
+
# Retrieve an Organization
|
9
|
+
#
|
10
|
+
# @see https://developer.linkedin.com/docs/guide/v2/organizations/organization-lookup-api
|
11
|
+
#
|
12
|
+
# @macro organization_path_options
|
13
|
+
# @option options [String] :scope
|
14
|
+
# @option options [String] :type
|
15
|
+
# @option options [String] :count
|
16
|
+
# @option options [String] :start
|
17
|
+
# @return [LinkedIn::Mash]
|
18
|
+
def organization(options = {})
|
19
|
+
path = organization_path(options)
|
20
|
+
get(path, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Retrieve Organization Access Control informaion
|
24
|
+
#
|
25
|
+
# @see https://developer.linkedin.com/docs/guide/v2/organizations/organization-lookup-api#acls
|
26
|
+
#
|
27
|
+
def organization_acls(options = {})
|
28
|
+
path = '/organizationalEntityAcls'
|
29
|
+
get(path, options)
|
30
|
+
end
|
31
|
+
|
32
|
+
private ##############################################################
|
33
|
+
|
34
|
+
|
35
|
+
def organization_path(options)
|
36
|
+
path = '/organizations'
|
37
|
+
|
38
|
+
if email_domain = options.delete(:email_domain)
|
39
|
+
path += "?q=emailDomain&emailDomain=#{CGI.escape(email_domain)}"
|
40
|
+
elsif id = options.delete(:id)
|
41
|
+
path += "/#{id}"
|
42
|
+
elsif urn = options.delete(:urn)
|
43
|
+
path += "/#{urn_to_id(urn)}"
|
44
|
+
elsif vanity_name = options.delete(:vanity_name)
|
45
|
+
path += "?q=vanityName&vanityName=#{CGI.escape(vanity_name)}"
|
46
|
+
else
|
47
|
+
path += "/me"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|