linkedin-v2 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +10 -0
  3. data/CONTRIBUTING.md +1 -0
  4. data/Gemfile +8 -0
  5. data/LICENSE +22 -0
  6. data/README.md +224 -0
  7. data/Rakefile +19 -0
  8. data/lib/linked_in/access_token.rb +24 -0
  9. data/lib/linked_in/api.rb +108 -0
  10. data/lib/linked_in/api_resource.rb +180 -0
  11. data/lib/linked_in/communications.rb +40 -0
  12. data/lib/linked_in/configuration.rb +41 -0
  13. data/lib/linked_in/connection.rb +35 -0
  14. data/lib/linked_in/errors.rb +73 -0
  15. data/lib/linked_in/jobs.rb +11 -0
  16. data/lib/linked_in/mash.rb +68 -0
  17. data/lib/linked_in/media.rb +13 -0
  18. data/lib/linked_in/oauth2.rb +223 -0
  19. data/lib/linked_in/organizations.rb +217 -0
  20. data/lib/linked_in/people.rb +151 -0
  21. data/lib/linked_in/raise_error.rb +28 -0
  22. data/lib/linked_in/search.rb +70 -0
  23. data/lib/linked_in/share_and_social_stream.rb +143 -0
  24. data/lib/linked_in/version.rb +3 -0
  25. data/lib/linkedin-v2.rb +52 -0
  26. data/linkedin-v2.gemspec +39 -0
  27. data/pkg/linkedin-oauth2-2.0.0.gem +0 -0
  28. data/spec/linked_in/api/api_spec.rb +41 -0
  29. data/spec/linked_in/api/communications_spec.rb +13 -0
  30. data/spec/linked_in/api/jobs_spec.rb +33 -0
  31. data/spec/linked_in/api/organizations_spec.rb +54 -0
  32. data/spec/linked_in/api/people_spec.rb +191 -0
  33. data/spec/linked_in/api/search_spec.rb +71 -0
  34. data/spec/linked_in/api/share_and_social_stream_spec.rb +87 -0
  35. data/spec/linked_in/configuration_spec.rb +46 -0
  36. data/spec/linked_in/connection_spec.rb +10 -0
  37. data/spec/linked_in/module_loading_spec.rb +23 -0
  38. data/spec/linked_in/oauth/access_token_spec.rb +27 -0
  39. data/spec/linked_in/oauth/auth_code_spec.rb +86 -0
  40. data/spec/linked_in/oauth/credentials_spec.rb +96 -0
  41. data/spec/linked_in/oauth/get_access_token_spec.rb +108 -0
  42. data/spec/spec_helper.rb +16 -0
  43. data/spec/vcr_cassettes/access_token_success.yml +99 -0
  44. data/spec/vcr_cassettes/bad_code.yml +99 -0
  45. data/spec/vcr_cassettes/organization_data.yml +51 -0
  46. data/spec/vcr_cassettes/people_picture_urls.yml +52 -0
  47. data/spec/vcr_cassettes/people_profile_connections_fields.yml +52 -0
  48. data/spec/vcr_cassettes/people_profile_connections_other.yml +52 -0
  49. data/spec/vcr_cassettes/people_profile_connections_self.yml +52 -0
  50. data/spec/vcr_cassettes/people_profile_fields_complex.yml +52 -0
  51. data/spec/vcr_cassettes/people_profile_fields_simple.yml +52 -0
  52. data/spec/vcr_cassettes/people_profile_lang_spanish.yml +53 -0
  53. data/spec/vcr_cassettes/people_profile_multiple_fields.yml +52 -0
  54. data/spec/vcr_cassettes/people_profile_multiple_uids.yml +52 -0
  55. data/spec/vcr_cassettes/people_profile_multiple_uids_and_urls.yml +52 -0
  56. data/spec/vcr_cassettes/people_profile_multiple_urls.yml +52 -0
  57. data/spec/vcr_cassettes/people_profile_new_connections_fields.yml +52 -0
  58. data/spec/vcr_cassettes/people_profile_new_connections_other.yml +52 -0
  59. data/spec/vcr_cassettes/people_profile_new_connections_self.yml +52 -0
  60. data/spec/vcr_cassettes/people_profile_other_uid.yml +57 -0
  61. data/spec/vcr_cassettes/people_profile_other_url.yml +54 -0
  62. data/spec/vcr_cassettes/people_profile_own.yml +57 -0
  63. data/spec/vcr_cassettes/people_profile_own_secure.yml +53 -0
  64. data/spec/vcr_cassettes/people_profile_skills.yml +52 -0
  65. data/spec/vcr_cassettes/unavailable.yml +99 -0
  66. metadata +285 -0
@@ -0,0 +1,40 @@
1
+ module LinkedIn
2
+ # Communications APIs
3
+ #
4
+ # @see http://developer.linkedin.com/documents/communications
5
+ class Communications < APIResource
6
+
7
+ # (Create) send a message from the authenticated user to a
8
+ # connection
9
+ #
10
+ # Permissions: w_messages
11
+ #
12
+ # @see http://developer.linkedin.com/documents/messaging-between-connections-api
13
+ # @see http://developer.linkedin.com/documents/invitation-api Invitation API
14
+ #
15
+ # @example
16
+ # api.send_message("subject", "body", ["person_1_id", "person_2_id"])
17
+ #
18
+ # @param [String] subject Subject of the message
19
+ # @param [String] body Body of the message, plain text only
20
+ # @param [Array<String>] recipient_paths a collection of
21
+ # profile paths that identify the users who will receive the
22
+ # message
23
+ # @return [void]
24
+ def send_message(subject, body, recipient_paths)
25
+ path = "/people/~/mailbox"
26
+
27
+ message = {
28
+ subject: subject,
29
+ body: body,
30
+ recipients: {
31
+ values: recipient_paths.map do |profile_path|
32
+ {person: {_path: "/people/#{profile_path}"} }
33
+ end
34
+ }
35
+ }
36
+
37
+ post(path, MultiJson.dump(message), "Content-Type" => "application/json")
38
+ end
39
+ end
40
+ end
@@ -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 = "/uas/oauth2/accessToken"
17
+ # * LinkedIn.config.authorize_url = "/uas/oauth2/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 = "/uas/oauth2/accessToken"
38
+ @authorize_url = "/uas/oauth2/authorization"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,35 @@
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
+ logger = Logger.new $stderr
21
+ logger.level = Logger::DEBUG
22
+ self.response :logger, logger
23
+
24
+ self.response :linkedin_raise_error
25
+ end
26
+
27
+
28
+ private ##############################################################
29
+
30
+
31
+ def default_url
32
+ LinkedIn.config.api + LinkedIn.config.api_version
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,73 @@
1
+ module LinkedIn
2
+ # Thanks https://github.com/ResultadosDigitais/linkedin-oauth2 !
3
+
4
+ class Error < StandardError
5
+ attr_reader :data
6
+
7
+ def initialize(data)
8
+ @data = data
9
+ super
10
+ end
11
+ end
12
+
13
+ # Raised when users call a deprecated function
14
+ class Deprecated < LinkedIn::Error; end
15
+
16
+ # Raised when we know requests will be malformed and LinkedIn returns
17
+ # a 400 status code
18
+ class InvalidRequest < LinkedIn::Error; end
19
+
20
+ # Raised when LinkedIn returns a 401 status code during an API
21
+ # request.
22
+ class UnauthorizedError < LinkedIn::Error; end
23
+
24
+ # Raised when LinkedIn returns a 403 status code during an API
25
+ # request.
26
+ class AccessDeniedError < LinkedIn::Error; end
27
+
28
+ # Raised when LinkedIn returns a 404 status code during an API
29
+ # request.
30
+ class NotFoundError < LinkedIn::Error; end
31
+
32
+ # Raised when LinkedIn returns a 500 status code during an API
33
+ # request.
34
+ class InformLinkedInError < LinkedIn::Error; end
35
+
36
+ # Raised when LinkedIn returns a 502+ status code during an API
37
+ # request.
38
+ class UnavailableError < LinkedIn::Error; end
39
+
40
+ # Raised when LinkedIn returns a non 400+ status code during an OAuth
41
+ # request.
42
+ class OAuthError < OAuth2::Error; end
43
+
44
+ module ErrorMessages
45
+ class << self
46
+ attr_reader :deprecated,
47
+ :redirect_uri,
48
+ :no_auth_code,
49
+ :no_access_token,
50
+ :credentials_missing,
51
+ :redirect_uri_mismatch,
52
+ :throttled
53
+ end
54
+
55
+ @deprecated = "This has been deprecated by LinkedIn. Check https://developer.linkedin.com to see the latest available API calls"
56
+
57
+ @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."
58
+
59
+ @no_auth_code = "You must provide the authorization code passed to your redirect uri in the url params"
60
+
61
+ @no_access_token = "You must have an access token to use LinkedIn's API. Use the LinkedIn::OAuth2 module to obtain an access token"
62
+
63
+ @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"
64
+
65
+ @redirect_uri_mismatch = "Throttle limit for calls to this resource is reached"
66
+
67
+ def klass
68
+
69
+ end
70
+
71
+
72
+ end
73
+ end
@@ -0,0 +1,11 @@
1
+ module LinkedIn
2
+ # Jobs API
3
+ #
4
+ # @see http://developer.linkedin.com/documents/job-lookup-api-and-fields Job Lookup API and Fields
5
+ # @see http://developer.linkedin.com/documents/job-bookmarks-and-suggestions Job Bookmarks and Suggestions
6
+ #
7
+ # I do not have access to the Jobs-related endpoints.
8
+ # [(contribute here)](https://github.com/mdesjardins/linkedin-v2)
9
+ class Jobs < APIResource
10
+ end
11
+ 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,13 @@
1
+ module LinkedIn
2
+ # Rich Media APIs
3
+ #
4
+ # @see https://developer.linkedin.com/docs/guide/v2/shares/rich-media-shares
5
+ #
6
+ # [(contribute here)](https://github.com/mdesjardins/linkedin-v2)
7
+ class Media < APIResource
8
+ def summary(options = {})
9
+ path = "/richMediaSummariesV2/#{options.delete(:id)}"
10
+ get(path, options)
11
+ end
12
+ end
13
+ 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)
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