sundawg_contacts 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ module Contacts
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 2
5
+ TINY = 5
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,164 @@
1
+ require 'contacts'
2
+ require File.join(File.dirname(__FILE__), %w{.. .. vendor windows_live_login})
3
+
4
+ require 'rubygems'
5
+ require 'hpricot'
6
+ require 'uri'
7
+ require 'yaml'
8
+
9
+ module Contacts
10
+ # = How I can fetch Windows Live Contacts?
11
+ # To gain access to a Windows Live user's data in the Live Contacts service,
12
+ # a third-party developer first must ask the owner for permission. You must
13
+ # do that through Windows Live Delegated Authentication.
14
+ #
15
+ # This library give you access to Windows Live Delegated Authentication System
16
+ # and Windows Live Contacts API. Just follow the steps below and be happy!
17
+ #
18
+ # === Registering your app
19
+ # First of all, follow the steps in this
20
+ # page[http://msdn.microsoft.com/en-us/library/cc287659.aspx] to register your
21
+ # app.
22
+ #
23
+ # === Configuring your Windows Live YAML
24
+ # After registering your app, you will have an *appid*, a <b>secret key</b> and
25
+ # a <b>return URL</b>. Use their values to fill in the config/contacts.yml file.
26
+ # The policy URL field inside the YAML config file must contain the URL
27
+ # of the privacy policy of your Web site for Delegated Authentication.
28
+ #
29
+ # === Authenticating your user and fetching his contacts
30
+ #
31
+ # wl = Contacts::WindowsLive.new
32
+ # auth_url = wl.get_authentication_url
33
+ #
34
+ # Use that *auth_url* to redirect your user to Windows Live. He will authenticate
35
+ # there and Windows Live will POST to your return URL. You have to get the
36
+ # body of that POST, let's call it post_body. (if you're using Rails, you can
37
+ # get the POST body through request.raw_post, in the context of an action inside
38
+ # ActionController)
39
+ #
40
+ # Now, to fetch his contacts, just do this:
41
+ #
42
+ # contacts = wl.contacts(post_body)
43
+ # #-> [ ['Fitzgerald', 'fubar@gmail.com', 'fubar@example.com'],
44
+ # ['William Paginate', 'will.paginate@gmail.com'], ...
45
+ # ]
46
+ #--
47
+ # This class has two responsibilities:
48
+ # 1. Access the Windows Live Contacts API through Delegated Authentication
49
+ # 2. Import contacts from Windows Live and deliver it inside an Array
50
+ #
51
+ class WindowsLive
52
+ CONFIG_FILE = File.dirname(__FILE__) + '/../config/contacts.yml'
53
+
54
+ # Initialize a new WindowsLive object.
55
+ #
56
+ # ==== Paramaters
57
+ # * config_file <String>:: The contacts YAML config file name
58
+ #--
59
+ # You can check an example of a config file inside config/ directory
60
+ #
61
+ def initialize(config_file=CONFIG_FILE)
62
+ confs = YAML.load_file(config_file)['windows_live']
63
+ @wll = WindowsLiveLogin.new(confs['appid'], confs['secret'], confs['security_algorithm'],
64
+ nil, confs['policy_url'], confs['return_url'])
65
+ end
66
+
67
+
68
+ # Windows Live Contacts API need to authenticate the user that is giving you
69
+ # access to his contacts. To do that, you must give him a URL. That method
70
+ # generates that URL. The user must access that URL, and after he has done
71
+ # authentication, hi will be redirected to your application.
72
+ #
73
+ def get_authentication_url
74
+ @wll.getConsentUrl("Contacts.Invite")
75
+ end
76
+
77
+ # After the user has been authenticaded, Windows Live Delegated Authencation
78
+ # Service redirects to your application, through a POST HTTP method. Along
79
+ # with the POST, Windows Live send to you a Consent that you must process
80
+ # to access the user's contacts. This method process the Consent
81
+ # to you.
82
+ #
83
+ # ==== Paramaters
84
+ # * consent <String>:: A string containing the Consent given to you inside
85
+ # the redirection POST from Windows Live
86
+ #
87
+ def process_consent(consent)
88
+ consent.strip!
89
+ consent = URI.unescape(consent)
90
+ @consent_token = @wll.processConsent(consent)
91
+ end
92
+
93
+ # This method return the user's contacts inside an Array in the following
94
+ # format:
95
+ #
96
+ # [
97
+ # ['Brad Fitzgerald', 'fubar@gmail.com'],
98
+ # [nil, 'nagios@hotmail.com'],
99
+ # ['William Paginate', 'will.paginate@yahoo.com'] ...
100
+ # ]
101
+ #
102
+ # ==== Paramaters
103
+ # * consent <String>:: A string containing the Consent given to you inside
104
+ # the redirection POST from Windows Live
105
+ #
106
+ def contacts(consent)
107
+ process_consent(consent)
108
+ contacts_xml = access_live_contacts_api()
109
+ contacts_list = WindowsLive.parse_xml(contacts_xml)
110
+ end
111
+
112
+ # This method access the Windows Live Contacts API Web Service to get
113
+ # the XML contacts document
114
+ #
115
+ def access_live_contacts_api
116
+ http = http = Net::HTTP.new('livecontacts.services.live.com', 443)
117
+ http.use_ssl = true
118
+
119
+ response = nil
120
+ http.start do |http|
121
+ request = Net::HTTP::Get.new("/users/@L@#{@consent_token.locationid}/rest/invitationsbyemail", {"Authorization" => "DelegatedToken dt=\"#{@consent_token.delegationtoken}\""})
122
+ response = http.request(request)
123
+ end
124
+
125
+ return response.body
126
+ end
127
+
128
+ # This method parses the XML Contacts document and returns the contacts
129
+ # inside an Array
130
+ #
131
+ # ==== Paramaters
132
+ # * xml <String>:: A string containing the XML contacts document
133
+ #
134
+ def self.parse_xml(xml)
135
+ doc = Hpricot::XML(xml)
136
+
137
+ contacts = []
138
+ doc.search('/livecontacts/contacts/contact').each do |contact|
139
+ email = contact.at('/preferredemail').inner_text
140
+ email.strip!
141
+
142
+ first_name = last_name = nil
143
+ if first_name = contact.at('/profiles/personal/firstname')
144
+ first_name = first_name.inner_text.strip
145
+ end
146
+
147
+ if last_name = contact.at('/profiles/personal/lastname')
148
+ last_name = last_name.inner_text.strip
149
+ end
150
+
151
+ name = nil
152
+ if !first_name.nil? || !last_name.nil?
153
+ name = "#{first_name} #{last_name}"
154
+ name.strip!
155
+ end
156
+ new_contact = Contact.new(email, name)
157
+ contacts << new_contact
158
+ end
159
+
160
+ return contacts
161
+ end
162
+ end
163
+
164
+ end
@@ -0,0 +1,242 @@
1
+ require 'contacts'
2
+
3
+ require 'rubygems'
4
+ require 'hpricot'
5
+ require 'md5'
6
+ require 'net/https'
7
+ require 'uri'
8
+ require 'yaml'
9
+
10
+ module Contacts
11
+ # = How I can fetch Yahoo Contacts?
12
+ # To gain access to a Yahoo user's data in the Yahoo Address Book Service,
13
+ # a third-party developer first must ask the owner for permission. You must
14
+ # do that through Yahoo Browser Based Authentication (BBAuth).
15
+ #
16
+ # This library give you access to Yahoo BBAuth and Yahoo Address Book API.
17
+ # Just follow the steps below and be happy!
18
+ #
19
+ # === Registering your app
20
+ # First of all, follow the steps in this
21
+ # page[http://developer.yahoo.com/wsregapp/] to register your app. If you need
22
+ # some help with that form, you can get it
23
+ # here[http://developer.yahoo.com/auth/appreg.html]. Just two tips: inside
24
+ # <b>Required access scopes</b> in that registration form, choose
25
+ # <b>Yahoo! Address Book with Read Only access</b>. Inside
26
+ # <b>Authentication method</b> choose <b>Browser Based Authentication</b>.
27
+ #
28
+ # === Configuring your Yahoo YAML
29
+ # After registering your app, you will have an <b>application id</b> and a
30
+ # <b>shared secret</b>. Use their values to fill in the config/contacts.yml
31
+ # file.
32
+ #
33
+ # === Authenticating your user and fetching his contacts
34
+ #
35
+ # yahoo = Contacts::Yahoo.new
36
+ # auth_url = yahoo.get_authentication_url
37
+ #
38
+ # Use that *auth_url* to redirect your user to Yahoo BBAuth. He will authenticate
39
+ # there and Yahoo will redirect to your application entrypoint URL (that you provided
40
+ # while registering your app with Yahoo). You have to get the path of that
41
+ # redirect, let's call it path (if you're using Rails, you can get it through
42
+ # request.request_uri, in the context of an action inside ActionController)
43
+ #
44
+ # Now, to fetch his contacts, just do this:
45
+ #
46
+ # contacts = wl.contacts(path)
47
+ # #-> [ ['Fitzgerald', 'fubar@gmail.com', 'fubar@example.com'],
48
+ # ['William Paginate', 'will.paginate@gmail.com'], ...
49
+ # ]
50
+ #--
51
+ # This class has two responsibilities:
52
+ # 1. Access the Yahoo Address Book API through Delegated Authentication
53
+ # 2. Import contacts from Yahoo Mail and deliver it inside an Array
54
+ #
55
+ class Yahoo
56
+ AUTH_DOMAIN = "https://api.login.yahoo.com"
57
+ AUTH_PATH = "/WSLogin/V1/wslogin?appid=#appid&ts=#ts"
58
+ CREDENTIAL_PATH = "/WSLogin/V1/wspwtoken_login?appid=#appid&ts=#ts&token=#token"
59
+ ADDRESS_BOOK_DOMAIN = "address.yahooapis.com"
60
+ ADDRESS_BOOK_PATH = "/v1/searchContacts?format=json&fields=name,email&appid=#appid&WSSID=#wssid"
61
+ CONFIG_FILE = File.dirname(__FILE__) + '/../config/contacts.yml'
62
+
63
+ attr_reader :appid, :secret, :token, :wssid, :cookie
64
+
65
+ # Initialize a new Yahoo object.
66
+ #
67
+ # ==== Paramaters
68
+ # * config_file <String>:: The contacts YAML config file name
69
+ #--
70
+ # You can check an example of a config file inside config/ directory
71
+ #
72
+ def initialize(config_file=CONFIG_FILE)
73
+ confs = YAML.load_file(config_file)['yahoo']
74
+ @appid = confs['appid']
75
+ @secret = confs['secret']
76
+ end
77
+
78
+ # Yahoo Address Book API need to authenticate the user that is giving you
79
+ # access to his contacts. To do that, you must give him a URL. This method
80
+ # generates that URL. The user must access that URL, and after he has done
81
+ # authentication, hi will be redirected to your application.
82
+ #
83
+ def get_authentication_url
84
+ path = AUTH_PATH.clone
85
+ path.sub!(/#appid/, @appid)
86
+
87
+ timestamp = Time.now.utc.to_i
88
+ path.sub!(/#ts/, timestamp.to_s)
89
+
90
+ signature = MD5.hexdigest(path + @secret)
91
+ return AUTH_DOMAIN + "#{path}&sig=#{signature}"
92
+ end
93
+
94
+ # This method return the user's contacts inside an Array in the following
95
+ # format:
96
+ #
97
+ # [
98
+ # ['Brad Fitzgerald', 'fubar@gmail.com'],
99
+ # [nil, 'nagios@hotmail.com'],
100
+ # ['William Paginate', 'will.paginate@yahoo.com'] ...
101
+ # ]
102
+ #
103
+ # ==== Paramaters
104
+ # * path <String>:: The path of the redirect request that Yahoo sent to you
105
+ # after authenticating the user
106
+ #
107
+ def contacts(path, force_json_gem=false)
108
+ validate_signature(path)
109
+ credentials = access_user_credentials()
110
+ parse_credentials(credentials)
111
+ contacts_json = access_address_book_api()
112
+ Yahoo.parse_contacts(contacts_json, force_json_gem)
113
+ end
114
+
115
+ # This method processes and validates the redirect request that Yahoo send to
116
+ # you. Validation is done to verify that the request was really made by
117
+ # Yahoo. Processing is done to get the token.
118
+ #
119
+ # ==== Paramaters
120
+ # * path <String>:: The path of the redirect request that Yahoo sent to you
121
+ # after authenticating the user
122
+ #
123
+ def validate_signature(path)
124
+ path.match(/^(.+)&sig=(\w{32})$/)
125
+ path_without_sig = $1
126
+ sig = $2
127
+
128
+ if sig == MD5.hexdigest(path_without_sig + @secret)
129
+ path.match(/token=(.+?)&/)
130
+ @token = $1
131
+ return true
132
+ else
133
+ raise 'Signature not valid. This request may not have been sent from Yahoo.'
134
+ end
135
+ end
136
+
137
+ # This method accesses Yahoo to retrieve the user's credentials.
138
+ #
139
+ def access_user_credentials
140
+ url = get_credential_url()
141
+ uri = URI.parse(url)
142
+
143
+ http = http = Net::HTTP.new(uri.host, uri.port)
144
+ http.use_ssl = true
145
+
146
+ response = nil
147
+ http.start do |http|
148
+ request = Net::HTTP::Get.new("#{uri.path}?#{uri.query}")
149
+ response = http.request(request)
150
+ end
151
+
152
+ return response.body
153
+ end
154
+
155
+ # This method generates the URL that you must access to get user's
156
+ # credentials.
157
+ #
158
+ def get_credential_url
159
+ path = CREDENTIAL_PATH.clone
160
+ path.sub!(/#appid/, @appid)
161
+
162
+ path.sub!(/#token/, @token)
163
+
164
+ timestamp = Time.now.utc.to_i
165
+ path.sub!(/#ts/, timestamp.to_s)
166
+
167
+ signature = MD5.hexdigest(path + @secret)
168
+ return AUTH_DOMAIN + "#{path}&sig=#{signature}"
169
+ end
170
+
171
+ # This method parses the user's credentials to generate the WSSID and
172
+ # Coookie that are needed to give you access to user's address book.
173
+ #
174
+ # ==== Paramaters
175
+ # * xml <String>:: A String containing the user's credentials
176
+ #
177
+
178
+ # temporary hack to find out cause of xml values being nil
179
+ class XMLParseError < StandardError; end
180
+
181
+ def parse_credentials(xml)
182
+ doc = Hpricot::XML(xml)
183
+ node = doc.at('/BBAuthTokenLoginResponse/Success/WSSID')
184
+ raise XMLParseError, xml.to_s if !node
185
+ @wssid = doc.at('/BBAuthTokenLoginResponse/Success/WSSID').inner_text.strip
186
+ @cookie = doc.at('/BBAuthTokenLoginResponse/Success/Cookie').inner_text.strip
187
+ end
188
+
189
+ # This method accesses the Yahoo Address Book API and retrieves the user's
190
+ # contacts in JSON.
191
+ #
192
+ def access_address_book_api
193
+ http = http = Net::HTTP.new(ADDRESS_BOOK_DOMAIN, 80)
194
+
195
+ response = nil
196
+ http.start do |http|
197
+ path = ADDRESS_BOOK_PATH.clone
198
+ path.sub!(/#appid/, @appid)
199
+ path.sub!(/#wssid/, @wssid)
200
+
201
+ request = Net::HTTP::Get.new(path, {'Cookie' => @cookie})
202
+ response = http.request(request)
203
+ end
204
+
205
+ return response.body
206
+ end
207
+
208
+ # This method parses the JSON contacts document and returns an array
209
+ # contaning all the user's contacts.
210
+ #
211
+ # ==== Parameters
212
+ # * json <String>:: A String of user's contacts in JSON format
213
+ #
214
+ def self.parse_contacts(json, force_json_gem=false)
215
+ contacts = []
216
+ people = if !force_json_gem && defined? ActiveSupport::JSON
217
+ ActiveSupport::JSON.decode(json)
218
+ else
219
+ require 'json'
220
+ JSON.parse(json)
221
+ end
222
+
223
+ people['contacts'].each do |contact|
224
+ name = nil
225
+ email = nil
226
+ contact['fields'].each do |field|
227
+ case field['type']
228
+ when 'email'
229
+ email = field['data']
230
+ email.strip!
231
+ when 'name'
232
+ name = "#{field['first']} #{field['last']}"
233
+ name.strip!
234
+ end
235
+ end
236
+ contacts.push Contact.new(email, name)
237
+ end
238
+ return contacts
239
+ end
240
+
241
+ end
242
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+ require 'contacts'
3
+
4
+ describe Contacts::Contact do
5
+ describe 'instance' do
6
+ before do
7
+ @contact = Contacts::Contact.new('max@example.com', 'Max Power', 'maxpower')
8
+ end
9
+
10
+ it "should have email" do
11
+ @contact.email.should == 'max@example.com'
12
+ end
13
+
14
+ it "should have name" do
15
+ @contact.name.should == 'Max Power'
16
+ end
17
+
18
+ it "should support multiple emails" do
19
+ @contact.emails << 'maxpower@example.com'
20
+ @contact.email.should == 'max@example.com'
21
+ @contact.emails.should == ['max@example.com', 'maxpower@example.com']
22
+ end
23
+
24
+ it "should have username" do
25
+ @contact.username.should == 'maxpower'
26
+ end
27
+ end
28
+
29
+ describe '#inspect' do
30
+ it "should be nice" do
31
+ @contact = Contacts::Contact.new('max@example.com', 'Max Power', 'maxpower')
32
+ @contact.inspect.should == '#<Contacts::Contact "Max Power" (max@example.com)>'
33
+ end
34
+
35
+ it "should be nice without email" do
36
+ @contact = Contacts::Contact.new(nil, 'Max Power', 'maxpower')
37
+ @contact.inspect.should == '#<Contacts::Contact "Max Power">'
38
+ end
39
+ end
40
+
41
+ end