sundawg_contacts 0.0.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,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