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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4ea84838fd42d284acc2313a1d05036cda8c08fc86a1e6a87378d73a0884bc67
4
+ data.tar.gz: e264a8647ac465472c35c132cc9d449ea7c1cf8fa26aba4e6eef01b1ac893a8f
5
+ SHA512:
6
+ metadata.gz: 75786e0fc5a60ca0759ac1f51ce635fcf55ca39d52693789f2de733ec5311b9e8a40f78bc587983fe686d318cab3dc58d07dada7d96e0a73b3650dde450964e7
7
+ data.tar.gz: 2b2fa114d47313d85605cb2874b33568727412460504ce1662f8eb307c931dff0fd2972570fa32da7d07e2a5560dba46bb7317929cdf53015feb040fd5a6407f
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.3.0
7
+ before_install: gem install bundler -v 1.16.6
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at prakash@mallow-tech.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in linkedin-ruby.gemspec
6
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 MallowTech Developers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,148 @@
1
+ # Linkedin
2
+
3
+ Ruby-wrapper for LinkedIn API version 2 with Oauth2 support.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'linkedin-ruby'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install linkedin-ruby
20
+
21
+ ## Usage
22
+
23
+ **[Step 1:](#step-1-register-your-application)** [Register](https://www.linkedin.com/secure/developer) your
24
+ application with LinkedIn. They will give you a **Client ID** (aka API
25
+ Key) and a **Client Secret** (aka Secret Key)
26
+
27
+ **[Step 2:](#step-2-getting-an-access-token)** Use your **Client ID** and **Client Secret** to obtain an **Access Token** from some user.
28
+
29
+ **[Step 3:](#step-3-using-linkedins-api)** Use an **Access Token** to query the API.
30
+
31
+ ```ruby
32
+ api = LinkedIn::API.new(access_token)
33
+ me = api.profile
34
+ ```
35
+
36
+ ## Step 1: Register your Application
37
+
38
+ You first need to create and register an application with LinkedIn
39
+ [here](https://www.linkedin.com/secure/developer).
40
+
41
+ You will not be able to use any part of the API without registering first.
42
+
43
+ Once you have registered you will need to take note of a few key items on
44
+ your Application Details page.
45
+
46
+ 1. **API Key** - We refer to this as your client id or `client_id`
47
+ 1. **Secret Key** - We refer to this as your client secret or
48
+ `client_secret`
49
+ 1. **Default Scope** - This is the set of permissions you request from
50
+ users when they connect to your app. If you want to set this on a
51
+ request-by-request basis, you can use the `scope` option with the
52
+ `auth_code_url` method.
53
+ 1. **OAuth 2.0 Redirect URLs** - For security reasons, the url you enter
54
+ in this box must exactly match the `redirect_uri` you use in this gem.
55
+
56
+ You do NOT need **OAuth User Token** nor **OAuth User Secret**. That is
57
+ for OAuth 1.0. This gem is for OAuth 2.0.
58
+
59
+ ## Step 2: Getting An Access Token
60
+
61
+ All LinkedIn API requests must be made in the context of an access token.
62
+ The access token encodes what LinkedIn information your AwesomeApp® can
63
+ gather on behalf of "John Doe".
64
+
65
+ There are a few different ways to get an access token from a user.
66
+
67
+ 1. You can use [LinkedIn's Javascript API](https://developer.linkedin.com/documents/javascript-api-reference-0) to authenticate on the front-end and then pass the access token to the backend via [this procedure](https://developer.linkedin.com/documents/exchange-jsapi-tokens-rest-api-oauth-tokens).
68
+
69
+ 1. If you use OmniAuth, I would recommend looking at [decioferreira/omniauth-linkedin-oauth2](https://github.com/decioferreira/omniauth-linkedin-oauth2) to help automate authentication.
70
+
71
+ 1. You can do it manually using this linkedin-ruby gem and the steps
72
+ below.
73
+
74
+ Here is how to get an access token using this linkedin-ruby gem:
75
+
76
+ ### Step 2A: Configuration
77
+
78
+ You will need to configure the following items:
79
+
80
+ 1. Your **client id** (aka API Key)
81
+ 1. Your **client secret** (aka Secret Key)
82
+ 1. Your **redirect uri**. On LinkedIn's website you must input a list of
83
+ valid redirect URIs. If you use the same one each time, you can set it
84
+ in the `LinkedIn.configure` block. If your redirect uris change
85
+ depending on business logic, you can pass it into the `auth_code_url`
86
+ method.
87
+
88
+ ```ruby
89
+ # It's best practice to keep secret credentials out of source code.
90
+ # You can, of course, hardcode dev keys or directly pass them in as the
91
+ # first two arguments of LinkedIn::OAuth2.new
92
+ LinkedIn.configure do |config|
93
+ config.client_id = ENV["LINKEDIN_CLIENT_ID"]
94
+ config.client_secret = ENV["LINKEDIN_CLIENT_SECRET"]
95
+
96
+ # This must exactly match the redirect URI you set on your application's
97
+ # settings page. If your redirect_uri is dynamic, pass it into
98
+ # `auth_code_url` instead.
99
+ config.redirect_uri = "https://example.io/linkedin/oauth2"
100
+ end
101
+ ```
102
+
103
+ ### Step 2B: Get Auth Code URL
104
+
105
+ ```ruby
106
+ oauth = LinkedIn::OAuth2.new
107
+
108
+ url = oauth.auth_code_url
109
+ ```
110
+
111
+ ### Step 2C: User Sign In
112
+
113
+ You must now load url from Step 2B in a browser. It will pull up the
114
+ LinkedIn sign in box. Once LinkedIn user credentials are entered, the box
115
+ will close and redirect to your redirect url, passing along with it the
116
+ **OAuth code** as the `code` GET param.
117
+
118
+ Be sure to read the extended documentation around the LinkedIn::OAuth2
119
+ module for more options you can set.
120
+
121
+ **Note:** The **OAuth code** only lasts for ~20 seconds!
122
+
123
+ ### Step 2D: Get Access Token
124
+
125
+ ```ruby
126
+ code = "THE_OAUTH_CODE_LINKEDIN_GAVE_ME"
127
+
128
+ access_token = oauth.get_access_token(code)
129
+ ```
130
+
131
+ Now that you have an access token, you can use it to query the API.
132
+
133
+ The `LinkedIn::OAuth2` inherits from [intreda/oauth2](https://github.com/intridea/oauth2)'s `OAuth2::Client` class. See that gem's [documentation](https://github.com/intridea/oauth2/blob/master/lib/oauth2/client.rb) for more usage examples.
134
+
135
+
136
+ ## Development
137
+
138
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
139
+
140
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
141
+
142
+ ## Contributing
143
+
144
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/linkedin-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
145
+
146
+ ## Code of Conduct
147
+
148
+ Everyone interacting in the Linkedin project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/linkedin-ruby/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,24 @@
1
+ module LinkedIn
2
+ # A simple data object to contain the token string and expiration data.
3
+ class AccessToken
4
+ attr_accessor :token, :expires_in, :expires_at
5
+
6
+ # Creates a simple data wrapper for an access token.
7
+ #
8
+ # LinkedIn returns only an `expires_in` value. This calculates and
9
+ # sets and `expires_at` field for convenience.
10
+ #
11
+ # @param [String] token the access token
12
+ # @param [FixNum] expires_in number of seconds the token lasts for
13
+ # @param [Time] expires_at when the token will expire.
14
+ def initialize(token=nil, expires_in=nil, expires_at=nil)
15
+ self.token = token
16
+ self.expires_in = expires_in
17
+ if expires_at.nil? and not self.expires_in.nil?
18
+ self.expires_at = Time.now + expires_in
19
+ else
20
+ self.expires_at = expires_at
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,60 @@
1
+ module LinkedIn
2
+ class API
3
+
4
+ attr_accessor :access_token
5
+
6
+ def initialize(access_token = nil)
7
+ access_token = parse_access_token(access_token)
8
+ verify_access_token!(access_token)
9
+ @access_token = access_token
10
+
11
+ @connection = LinkedIn::Connection.new headers: default_headers do |conn|
12
+ conn.request :multipart
13
+ conn.adapter Faraday.default_adapter
14
+ end
15
+
16
+ initialize_endpoints
17
+ end
18
+
19
+ extend Forwardable # Composition over inheritance
20
+
21
+ def_delegators :@people, :profile
22
+
23
+ def_delegators :@organizations, :organization,
24
+ :organization_acls
25
+
26
+ def_delegators :@share_and_social_stream, :share
27
+
28
+ private ##############################################################
29
+
30
+ def initialize_endpoints
31
+ @people = LinkedIn::People.new(@connection)
32
+ @organizations = LinkedIn::Organizations.new(@connection)
33
+ @share_and_social_stream = LinkedIn::ShareAndSocialStream.new(@connection)
34
+ end
35
+
36
+ def default_headers
37
+ # https://developer.linkedin.com/documents/api-requests-json
38
+ return {"x-li-format" => "json", "Authorization" => "Bearer #{@access_token.token}"}
39
+ end
40
+
41
+ def verify_access_token!(access_token)
42
+ unless access_token.is_a? LinkedIn::AccessToken
43
+ raise no_access_token_error
44
+ end
45
+ end
46
+
47
+ def parse_access_token(access_token)
48
+ if access_token.is_a? LinkedIn::AccessToken
49
+ return access_token
50
+ elsif access_token.is_a? String
51
+ return LinkedIn::AccessToken.new(access_token)
52
+ end
53
+ end
54
+
55
+ def no_access_token_error
56
+ msg = LinkedIn::ErrorMessages.no_access_token
57
+ LinkedIn::InvalidRequest.new(msg)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,180 @@
1
+ module LinkedIn
2
+ # The abstract class all API endpoints inherit from. Providers common
3
+ # builder methods across all endpoints.
4
+ #
5
+ # @!macro profile_options
6
+ # @options opts [String] :id LinkedIn ID to fetch profile for
7
+ # @options opts [String] :url The profile url
8
+ # @options opts [String] :lang Requests the language of the profile.
9
+ # Options are: en, fr, de, it, pt, es
10
+ # @options opts [Array, Hash] :fields fields to fetch. The list of
11
+ # fields can be found at
12
+ # https://developer.linkedin.com/documents/profile-fields
13
+ # @options opts [String] :secure (true) specify if urls in the
14
+ # response should be https
15
+ # @options opts [String] :"secure-urls" (true) alias to secure option
16
+ #
17
+ # @!macro share_input_fields
18
+ # @param [Hash] share content of the share
19
+ # @option share [String] :comment
20
+ # @option share [String] :content
21
+ # @option share [String] :title
22
+ # @option share [String] :submitted-url
23
+ # @option share [String] :submitted-image-url
24
+ # @option share [String] :description
25
+ # @option share [String] :visibility
26
+ # @option share [String] :code
27
+ #
28
+ # @!macro organization_path_options
29
+ # @param [Hash] options identifies the organization profile you want
30
+ # @option options [String] :email_domain organization email domain
31
+ # @option options [String] :id organization ID
32
+ # @option options [String] :urn organization URN
33
+ # @option options [String] :vanity_name organization vanity name
34
+ #
35
+ # @!macro brand_path_options
36
+ # @param [Hash] options identifies the brand profile you want
37
+ # @option options [String] :id brand ID
38
+ # @option options [String] :vanity_name brand vanity name
39
+ # @option options [String] :parent_id brand's parent organization ID.
40
+ class APIResource
41
+
42
+ def initialize(connection)
43
+ @connection = connection
44
+ end
45
+
46
+ def urn_to_id(urn)
47
+ urn.split(':').last
48
+ end
49
+
50
+ def id_to_urn(resource, id)
51
+ ['urn', 'li', resource, id].join(':')
52
+ end
53
+
54
+ protected ############################################################
55
+
56
+ def get(path, options={})
57
+ url, params, headers = prepare_connection_params(path, options)
58
+
59
+ response = @connection.get(url, params, headers)
60
+ Mash.from_json(response.body)
61
+ end
62
+
63
+ def post(path=nil, body=nil, headers=nil, &block)
64
+ @connection.post(prepend_prefix(path), body, headers, &block)
65
+ end
66
+
67
+ def put(path=nil, body=nil, headers=nil, &block)
68
+ @connection.put(prepend_prefix(path), body, headers, &block)
69
+ Mash.from_json(response.body)
70
+ end
71
+
72
+ def delete(path=nil, body=nil, headers=nil, &block)
73
+ # @connection.delete(prepend_prefix(path), params, headers, &block)
74
+ # To be able to DELETE with a body:
75
+ response = @connection.run_request(:delete, prepend_prefix(path), body, headers, &block)
76
+
77
+ Mash.from_json(response.body)
78
+ end
79
+
80
+ def deprecated
81
+ LinkedIn::Deprecated.new(LinkedIn::ErrorMessages.deprecated)
82
+ end
83
+
84
+ private ##############################################################
85
+
86
+ def prepend_prefix(path)
87
+ return @connection.path_prefix + path
88
+ end
89
+
90
+ def prepare_connection_params(path, options)
91
+ path = prepend_prefix(path)
92
+ path += generate_field_selectors(options)
93
+
94
+ headers = options.delete(:headers) || {}
95
+
96
+ params = format_options_for_query(options)
97
+
98
+ return [path, params, headers]
99
+ end
100
+
101
+ # Dasherizes the param keys
102
+ def format_options_for_query(options)
103
+ options.reduce({}) do |list, kv|
104
+ key, value = kv.first.to_s.gsub("_","-"), kv.last
105
+ list[key] = value
106
+ list
107
+ end
108
+ end
109
+
110
+ def generate_field_selectors(options)
111
+ default = LinkedIn.config.default_profile_fields || {}
112
+ fields = options.delete(:fields) || default
113
+ if options.delete(:public)
114
+ return ":public"
115
+ elsif fields.empty?
116
+ return ""
117
+ else
118
+ return "?projection=(#{fields})"
119
+ end
120
+ end
121
+
122
+ def build_fields_params(fields)
123
+ if fields.is_a?(Hash) && !fields.empty?
124
+ fields.map {|v| "(#{build_fields_params(v)})" }.join(',')
125
+ elsif fields.respond_to?(:each)
126
+ fields.map {|field| build_fields_params(field) }.join(',')
127
+ else
128
+ fields.to_s.gsub("_", "-")
129
+ end
130
+ end
131
+
132
+ def profile_path(options={}, allow_multiple=true)
133
+ path = "/people"
134
+
135
+ id = options.delete(:id)
136
+ url = options.delete(:url)
137
+
138
+ ids = options.delete(:ids)
139
+ urls = options.delete(:urls)
140
+
141
+ if options.delete(:email) then raise deprecated end
142
+
143
+ if (id or url)
144
+ path += single_person_path(id, url)
145
+ elsif allow_multiple and (ids or urls)
146
+ path += multiple_people_path(ids, urls)
147
+ else
148
+ path = "/me"
149
+ end
150
+ end
151
+
152
+ def single_person_path(id=nil, url=nil)
153
+ if id
154
+ return "/id=#{id}"
155
+ elsif url
156
+ return "/url=#{CGI.escape(url)}"
157
+ else
158
+ return "/me"
159
+ end
160
+ end
161
+
162
+ # See syntax here: https://developer.linkedin.com/documents/field-selectors
163
+ def multiple_people_path(ids=[], urls=[])
164
+ if ids.nil? then ids = [] end
165
+ if urls.nil? then urls = [] end
166
+
167
+ ids = ids.map do |id|
168
+ if is_self(id) then "me" else "id=#{id}" end
169
+ end
170
+ urls = urls.map do |url|
171
+ if is_self(url) then "me" else "url=#{CGI.escape(url)}" end
172
+ end
173
+ return "::(#{(ids+urls).join(",")})"
174
+ end
175
+
176
+ def is_self(str)
177
+ str == "self" or str == "me"
178
+ end
179
+ end
180
+ end