linkedin-oauth2 0.1.1

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,6 @@
1
+ # AUTHENTICATE FIRST found in examples/authenticate.rb
2
+
3
+ # client is a LinkedIn::Client
4
+
5
+ # update status for the authenticated user
6
+ client.add_share(:comment => 'is playing with the LinkedIn Ruby gem')
@@ -0,0 +1,6 @@
1
+ module LinkedIn
2
+ module Api
3
+ autoload :QueryMethods, "linked_in/api/query_methods"
4
+ autoload :UpdateMethods, "linked_in/api/update_methods"
5
+ end
6
+ end
@@ -0,0 +1,123 @@
1
+ module LinkedIn
2
+ module Api
3
+
4
+ module QueryMethods
5
+
6
+ def profile(options={})
7
+ path = person_path(options)
8
+ simple_query(path, options)
9
+ end
10
+
11
+ def connections(options={})
12
+ path = "#{person_path(options)}/connections"
13
+ simple_query(path, options)
14
+ end
15
+
16
+ def network_updates(options={})
17
+ path = "#{person_path(options)}/network/updates"
18
+ simple_query(path, options)
19
+ end
20
+
21
+ def company(options = {})
22
+ path = company_path(options)
23
+ simple_query(path, options)
24
+ end
25
+
26
+ def job(options = {})
27
+ path = jobs_path(options)
28
+ simple_query(path, options)
29
+ end
30
+
31
+ def job_bookmarks(options = {})
32
+ path = "#{person_path(options)}/job-bookmarks"
33
+ simple_query(path, options)
34
+ end
35
+
36
+ def job_suggestions(options = {})
37
+ path = "#{person_path(options)}/suggestions/job-suggestions"
38
+ simple_query(path, options)
39
+ end
40
+
41
+ def group_memberships(options = {})
42
+ path = "#{person_path(options)}/group-memberships"
43
+ simple_query(path, options)
44
+ end
45
+
46
+ def shares(options={})
47
+ path = "#{person_path(options)}/network/updates?type=SHAR&scope=self"
48
+ simple_query(path, options)
49
+ end
50
+
51
+ def share_comments(update_key, options={})
52
+ path = "#{person_path(options)}/network/updates/key=#{update_key}/update-comments"
53
+ simple_query(path, options)
54
+ end
55
+
56
+ def share_likes(update_key, options={})
57
+ path = "#{person_path(options)}/network/updates/key=#{update_key}/likes"
58
+ simple_query(path, options)
59
+ end
60
+
61
+ private
62
+
63
+ def simple_query(path, options={})
64
+ fields = options.delete(:fields) || LinkedIn.default_profile_fields
65
+
66
+ if options.delete(:public)
67
+ path +=":public"
68
+ elsif fields
69
+ path +=":(#{fields.map{ |f| f.to_s.gsub("_","-") }.join(',')})"
70
+ end
71
+
72
+ headers = options.delete(:headers) || {}
73
+
74
+ params = to_query(options)
75
+ path += "?#{params}" if !params.empty?
76
+
77
+ Mash.from_json(get(path, headers))
78
+ end
79
+
80
+ def person_path(options)
81
+ path = "/people"
82
+ if id = options.delete(:id)
83
+ if id.is_a? Array
84
+ path += "::(#{id.join(",")})"
85
+ else
86
+ path += "/id=#{id}"
87
+ end
88
+ elsif url = options.delete(:url)
89
+ path += "/url=#{CGI.escape(url)}"
90
+ else
91
+ path += "/~"
92
+ end
93
+ end
94
+
95
+ def company_path(options)
96
+ path = "/companies"
97
+
98
+ if domain = options.delete(:domain)
99
+ path += "?email-domain=#{CGI.escape(domain)}"
100
+ elsif id = options.delete(:id)
101
+ path += "/id=#{id}"
102
+ elsif url = options.delete(:url)
103
+ path += "/url=#{CGI.escape(url)}"
104
+ elsif name = options.delete(:name)
105
+ path += "/universal-name=#{CGI.escape(name)}"
106
+ else
107
+ path += "/~"
108
+ end
109
+ end
110
+
111
+ def jobs_path(options)
112
+ path = "/jobs"
113
+ if id = options.delete(:id)
114
+ path += "/id=#{id}"
115
+ else
116
+ path += "/~"
117
+ end
118
+ end
119
+
120
+ end
121
+
122
+ end
123
+ end
@@ -0,0 +1,76 @@
1
+ module LinkedIn
2
+ module Api
3
+
4
+ module UpdateMethods
5
+
6
+ def add_share(share)
7
+ path = "/people/~/shares"
8
+ defaults = {:visibility => {:code => "anyone"}}
9
+ post(path, defaults.merge(share).to_json, "Content-Type" => "application/json")
10
+ end
11
+
12
+ def join_group(group_id)
13
+ path = "/people/~/group-memberships/#{group_id}"
14
+ body = {'membership-state' => {'code' => 'member' }}
15
+ put(path, body.to_json, "Content-Type" => "application/json")
16
+ end
17
+
18
+ def add_job_bookmark(bookmark)
19
+ path = "/people/~/job-bookmarks"
20
+ body = {'job' => {'id' => bookmark}}
21
+ post(path, body.to_json, "Content-Type" => "application/json")
22
+ end
23
+
24
+ # def share(options={})
25
+ # path = "/people/~/shares"
26
+ # defaults = { :visability => 'anyone' }
27
+ # post(path, share_to_xml(defaults.merge(options)))
28
+ # end
29
+ #
30
+ def update_comment(network_key, comment)
31
+ path = "/people/~/network/updates/key=#{network_key}/update-comments"
32
+ body = {'comment' => comment}
33
+ post(path, body.to_json, "Content-Type" => "application/json")
34
+ end
35
+ #
36
+ # def update_network(message)
37
+ # path = "/people/~/person-activities"
38
+ # post(path, network_update_to_xml(message))
39
+ # end
40
+ #
41
+
42
+ def like_share(network_key)
43
+ path = "/people/~/network/updates/key=#{network_key}/is-liked"
44
+ put(path, 'true', "Content-Type" => "application/json")
45
+ end
46
+
47
+ def unlike_share(network_key)
48
+ path = "/people/~/network/updates/key=#{network_key}/is-liked"
49
+ put(path, 'false', "Content-Type" => "application/json")
50
+ end
51
+
52
+ def send_message(subject, body, recipient_paths)
53
+ path = "/people/~/mailbox"
54
+
55
+ message = {
56
+ 'subject' => subject,
57
+ 'body' => body,
58
+ 'recipients' => {
59
+ 'values' => recipient_paths.map do |profile_path|
60
+ { 'person' => { '_path' => "/people/#{profile_path}" } }
61
+ end
62
+ }
63
+ }
64
+ post(path, message.to_json, "Content-Type" => "application/json")
65
+ end
66
+ #
67
+ # def clear_status
68
+ # path = "/people/~/current-status"
69
+ # delete(path).code
70
+ # end
71
+ #
72
+
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,31 @@
1
+ require 'cgi'
2
+
3
+ module LinkedIn
4
+
5
+ class Client
6
+ include Helpers::Request
7
+ include Helpers::Authorization
8
+ include Api::QueryMethods
9
+ include Api::UpdateMethods
10
+ include Search
11
+
12
+ attr_reader :client_id, :client_secret, :access_token
13
+
14
+ # The first two arguments must be your client_id, and client_secret.
15
+ # The third option may either be an access_token or an options hash.
16
+ def initialize(client_id=LinkedIn.client_id,
17
+ client_secret=LinkedIn.client_secret,
18
+ initial_access_token=nil,
19
+ options={})
20
+ @client_id = client_id
21
+ @client_secret = client_secret
22
+ if initial_access_token.is_a? Hash
23
+ @client_options = initial_access_token
24
+ else
25
+ @client_options = options
26
+ self.set_access_token initial_access_token
27
+ end
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,19 @@
1
+ module LinkedIn
2
+ module Errors
3
+ class LinkedInError < StandardError
4
+ attr_reader :data
5
+ def initialize(data)
6
+ @data = data
7
+ super
8
+ end
9
+ end
10
+
11
+ class UnauthorizedError < LinkedInError; end
12
+ class GeneralError < LinkedInError; end
13
+ class AccessDeniedError < LinkedInError; end
14
+
15
+ class UnavailableError < StandardError; end
16
+ class InformLinkedInError < StandardError; end
17
+ class NotFoundError < StandardError; end
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ module LinkedIn
2
+ module Helpers
3
+ autoload :Authorization, "linked_in/helpers/authorization"
4
+ autoload :Request, "linked_in/helpers/request"
5
+ end
6
+ end
@@ -0,0 +1,106 @@
1
+
2
+ # TODO
3
+ # `consumer` renamed to `client`
4
+ #
5
+ # rename OAuth to OAuth2
6
+ #
7
+ # deprecate `request_token`
8
+ # deprecate `access_token`
9
+ #
10
+ # deprecate `authorize_from_request`
11
+ # deprecate `authorize_from_access`
12
+ #
13
+ # cleanup bottom
14
+ # deprecate `oauth_callback`
15
+ #
16
+ # DONE
17
+ # `consumer_token renamed to `client_id`
18
+ # `consumer_secret renamed to `client_secret`
19
+ # request_token_url is deprecated. Replaced with token_url
20
+ # access_token_url is deprecated. Replaced with token_url
21
+ # request_token_path is deprecated
22
+ # access_token_path has changed
23
+ # authorize_path has been deprecated
24
+
25
+ module LinkedIn
26
+ module Helpers
27
+
28
+ module Authorization
29
+
30
+ DEFAULT_OAUTH2_OPTIONS = {
31
+ authorize_path: "/uas/oauth2/authorization",
32
+ access_token_path: "/uas/oauth2/accessToken",
33
+ api_host: "https://api.linkedin.com",
34
+ auth_host: "https://www.linkedin.com"
35
+ }
36
+
37
+ def oauth2_client
38
+ @oauth2_client ||= ::OAuth2::Client.new(@client_id,
39
+ @client_secret,
40
+ parse_oauth2_options)
41
+ end
42
+
43
+ # A way to fetch the authorize_url
44
+ # @param :redirect_uri - Where you want it to redirect to after
45
+ # @param :scope - A list of member permissions you would like to
46
+ # request.
47
+ def authorize_url(params={})
48
+ # response_type param included by default by using the OAuth 2.0
49
+ # auth_code strategy
50
+ # client_id param included automatically by the OAuth 2.0 gem
51
+ params[:state] ||= state
52
+ params[:redirect_uri] ||= "http://localhost"
53
+ oauth2_client.auth_code.authorize_url(params)
54
+ rescue OAuth2::Error => e
55
+ raise LinkedIn::Errors::UnauthorizedError.new(e.code), e.description
56
+ end
57
+
58
+ # Fetches the access_token given the auth_code fetched by
59
+ # navigating to `authorize_url`
60
+ # @param :redirect_uri - Where you want to redirect after you have
61
+ # fetched the token.
62
+ def request_access_token(code, params={})
63
+ params[:redirect_uri] ||= "http://localhost"
64
+ opts = {}
65
+ opts[:mode] = :query
66
+ opts[:param_name] = "oauth2_access_token"
67
+ @access_token = oauth2_client.auth_code.get_token(code, params, opts)
68
+ rescue OAuth2::Error => e
69
+ raise LinkedIn::Errors::UnauthorizedError.new(e.code), e.description
70
+ end
71
+
72
+ # If one already has an access_token string, it can be set here and
73
+ # turned into an OAuth2::AccessToken object.
74
+ def set_access_token(token, options={})
75
+ options[:access_token] = token
76
+ options[:mode] = :query
77
+ options[:param_name] = "oauth2_access_token"
78
+ @access_token = OAuth2::AccessToken.from_hash oauth2_client, options
79
+ end
80
+
81
+ # NOTE: There is an attr_reader for :access_token.
82
+
83
+ private
84
+
85
+ # The keys of this hash are designed to match the OAuth2
86
+ # initialize spec.
87
+ def parse_oauth2_options
88
+ default = {site: DEFAULT_OAUTH2_OPTIONS[:api_host],
89
+ token_url: full_oauth_url_for(:access_token, :auth_host),
90
+ authorize_url: full_oauth_url_for(:authorize, :auth_host)}
91
+ return default.merge(@client_options)
92
+ end
93
+
94
+ def full_oauth_url_for(url_type, host_type)
95
+ host = DEFAULT_OAUTH2_OPTIONS[host_type]
96
+ path = DEFAULT_OAUTH2_OPTIONS["#{url_type}_path".to_sym]
97
+ "#{host}#{path}"
98
+ end
99
+
100
+ def state
101
+ o = [('a'..'z'),('A'..'Z')].map{|i| i.to_a}.flatten
102
+ @state ||= (0...50).map{ o[rand(o.length)] }.join
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,93 @@
1
+ module LinkedIn
2
+ module Helpers
3
+
4
+ module Request
5
+
6
+ DEFAULT_HEADERS = {
7
+ 'x-li-format' => 'json'
8
+ }
9
+
10
+ API_PATH = '/v1'
11
+
12
+ protected
13
+
14
+ def get(path, options={})
15
+ response = access_token.get("#{API_PATH}#{path}", headers: DEFAULT_HEADERS.merge(options))
16
+ raise_errors(response)
17
+ response.body
18
+ rescue OAuth2::Error => e
19
+ raise LinkedIn::Errors::AccessDeniedError.new(e.code), e.description
20
+ end
21
+
22
+ def post(path, body='', options={})
23
+ response = access_token.post("#{API_PATH}#{path}", body: body, headers: DEFAULT_HEADERS.merge(options))
24
+ raise_errors(response)
25
+ response
26
+ rescue OAuth2::Error => e
27
+ raise LinkedIn::Errors::AccessDeniedError.new(e.code), e.description
28
+ end
29
+
30
+ def put(path, body, options={})
31
+ response = access_token.put("#{API_PATH}#{path}", body: body, headers: DEFAULT_HEADERS.merge(options))
32
+ raise_errors(response)
33
+ response
34
+ rescue OAuth2::Error => e
35
+ raise LinkedIn::Errors::AccessDeniedError.new(e.code), e.description
36
+ end
37
+
38
+ def delete(path, options={})
39
+ response = access_token.delete("#{API_PATH}#{path}", headers: DEFAULT_HEADERS.merge(options))
40
+ raise_errors(response)
41
+ response
42
+ rescue OAuth2::Error => e
43
+ raise LinkedIn::Errors::AccessDeniedError.new(e.code), e.description
44
+ end
45
+
46
+ private
47
+
48
+ def raise_errors(response)
49
+ # Even if the json answer contains the HTTP status code, LinkedIn also sets this status
50
+ # in the HTTP answer (thankfully).
51
+ case response.status.to_i
52
+ when 401
53
+ data = Mash.from_json(response.body)
54
+ raise LinkedIn::Errors::UnauthorizedError.new(data), "(#{data.status}): #{data.message}"
55
+ when 400
56
+ data = Mash.from_json(response.body)
57
+ raise LinkedIn::Errors::GeneralError.new(data), "(#{data.status}): #{data.message}"
58
+ when 403
59
+ data = Mash.from_json(response.body)
60
+ raise LinkedIn::Errors::AccessDeniedError.new(data), "(#{data.status}): #{data.message}"
61
+ when 404
62
+ raise LinkedIn::Errors::NotFoundError, "(#{response.status}): #{response.message}"
63
+ when 500
64
+ raise LinkedIn::Errors::InformLinkedInError, "LinkedIn had an internal error. Please let them know in the forum. (#{response.status}): #{response.message}"
65
+ when 502..503
66
+ raise LinkedIn::Errors::UnavailableError, "(#{response.status}): #{response.message}"
67
+ end
68
+ end
69
+
70
+
71
+ # Stolen from Rack::Util.build_query
72
+ def to_query(params)
73
+ params.map { |k, v|
74
+ if v.class == Array
75
+ to_query(v.map { |x| [k, x] })
76
+ else
77
+ v.nil? ? escape(k) : "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"
78
+ end
79
+ }.join("&")
80
+ end
81
+
82
+ def to_uri(path, options)
83
+ uri = URI.parse(path)
84
+
85
+ if options && options != {}
86
+ uri.query = to_query(options)
87
+ end
88
+ uri.to_s
89
+ end
90
+ end
91
+
92
+ end
93
+ end