linkedin-oauth2 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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