omnicontacts 0.3.7 → 0.3.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile.lock +5 -2
- data/README.md +34 -6
- data/lib/omnicontacts.rb +1 -1
- data/lib/omnicontacts/builder.rb +2 -1
- data/lib/omnicontacts/importer.rb +1 -0
- data/lib/omnicontacts/importer/facebook.rb +4 -4
- data/lib/omnicontacts/importer/gmail.rb +13 -15
- data/lib/omnicontacts/importer/hotmail.rb +6 -10
- data/lib/omnicontacts/importer/outlook.rb +112 -0
- data/lib/omnicontacts/integration_test.rb +17 -12
- data/lib/omnicontacts/middleware/base_oauth.rb +19 -7
- data/omnicontacts.gemspec +1 -1
- data/spec/omnicontacts/importer/gmail_spec.rb +143 -14
- data/spec/omnicontacts/importer/outlook_spec.rb +150 -0
- data/spec/omnicontacts/integration_test_spec.rb +14 -5
- data/spec/omnicontacts/middleware/base_oauth_spec.rb +32 -3
- metadata +24 -34
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f04a2a074a319b00ef72e6ad8a66aa6f36df36d80dd4287858e7f69afda4ee07
|
4
|
+
data.tar.gz: 7f42b858981c8b127d3fedbaff922fc118ff36f7b99d96131419bf7266b0382b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b870ef5a5e649857aadd7f470a64ab521667e78b58d3c2c88d55b7e00b4c4f61876f8883ad5e81cfaac3b582d769f017b27add5d0feef81516636f4a69fb32bb
|
7
|
+
data.tar.gz: a61f7a7d58d0d67df5a7ee118720e830435c5705ba953934c26b88086e24c512cb3e35d49fb84d7cf50e143599b5178de89aa387a18b67dc22faf69146fc82f7
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
omnicontacts (0.3.
|
4
|
+
omnicontacts (0.3.9)
|
5
5
|
json
|
6
6
|
rack
|
7
7
|
|
@@ -9,7 +9,7 @@ GEM
|
|
9
9
|
remote: http://rubygems.org/
|
10
10
|
specs:
|
11
11
|
diff-lcs (1.1.3)
|
12
|
-
json (1.8.
|
12
|
+
json (1.8.3)
|
13
13
|
multi_json (1.1.0)
|
14
14
|
rack (1.4.1)
|
15
15
|
rack-test (0.6.1)
|
@@ -37,3 +37,6 @@ DEPENDENCIES
|
|
37
37
|
rake
|
38
38
|
rspec
|
39
39
|
simplecov
|
40
|
+
|
41
|
+
BUNDLED WITH
|
42
|
+
1.10.6
|
data/README.md
CHANGED
@@ -32,9 +32,10 @@ require "omnicontacts"
|
|
32
32
|
|
33
33
|
Rails.application.middleware.use OmniContacts::Builder do
|
34
34
|
importer :gmail, "client_id", "client_secret", {:redirect_path => "/oauth2callback", :ssl_ca_file => "/etc/ssl/certs/curl-ca-bundle.crt"}
|
35
|
-
importer :yahoo, "consumer_id", "consumer_secret", {:callback_path =>
|
35
|
+
importer :yahoo, "consumer_id", "consumer_secret", {:callback_path => "/callback"}
|
36
36
|
importer :linkedin, "consumer_id", "consumer_secret", {:redirect_path => "/oauth2callback", :state => '<long_unique_string_value>'}
|
37
37
|
importer :hotmail, "client_id", "client_secret"
|
38
|
+
importer :outlook, "app_id", "app_secret"
|
38
39
|
importer :facebook, "client_id", "client_secret"
|
39
40
|
end
|
40
41
|
|
@@ -49,17 +50,20 @@ On the other hand it makes things much easier to leave the default value for `:r
|
|
49
50
|
|
50
51
|
* For Gmail : [Google API Console](https://code.google.com/apis/console/)
|
51
52
|
|
52
|
-
* For Yahoo : [Yahoo Developer Network](https://developer.
|
53
|
+
* For Yahoo : [Yahoo Developer Network](https://developer.yahoo.com/social/contacts/)
|
53
54
|
|
54
55
|
* For Hotmail : [Microsoft Developer Network](https://account.live.com/developers/applications/index)
|
55
56
|
|
57
|
+
* For Outlook : [Microsoft Application Registration Portal](https://apps.dev.microsoft.com/)
|
58
|
+
|
56
59
|
* For Facebook : [Facebook Developers](https://developers.facebook.com/apps)
|
57
60
|
|
58
|
-
* For Linkedin : [Linkedin Developer Network](https://www.linkedin.com/secure/developer)
|
61
|
+
* For Linkedin : [Linkedin Developer Network](https://www.linkedin.com/secure/developer)
|
59
62
|
|
60
63
|
|
61
64
|
##### Note:
|
62
|
-
Please go through [MSDN](http://msdn.microsoft.com/en-us/library/cc287659.aspx) if above Hotmail link will not work.
|
65
|
+
Please go through [MSDN](http://msdn.microsoft.com/en-us/library/cc287659.aspx) if above Hotmail link will not work.
|
66
|
+
Outlook is a newer Microsoft API which allows to retrieve real email address instead of `email_hashes` when using Hotmail, it also works with all kinds of MS accounts (Office 365, Hotmail.com, Live.com, MSN.com, Outlook.com, and Passport.com).
|
63
67
|
|
64
68
|
## Integrating with your Application
|
65
69
|
|
@@ -172,7 +176,26 @@ The following table shows which fields are supported by which provider:
|
|
172
176
|
<td></td>
|
173
177
|
</tr>
|
174
178
|
<tr>
|
175
|
-
|
179
|
+
<td>Outlook</td>
|
180
|
+
<td>X</td>
|
181
|
+
<td>X</td>
|
182
|
+
<td></td>
|
183
|
+
<td>X</td>
|
184
|
+
<td>X</td>
|
185
|
+
<td>X</td>
|
186
|
+
<td>X</td>
|
187
|
+
<td></td>
|
188
|
+
<td>X</td>
|
189
|
+
<td>X</td>
|
190
|
+
<td>X</td>
|
191
|
+
<td>X</td>
|
192
|
+
<td></td>
|
193
|
+
<td>X</td>
|
194
|
+
<td></td>
|
195
|
+
<td></td>
|
196
|
+
</tr>
|
197
|
+
<tr>
|
198
|
+
<td>Linkedin</td>
|
176
199
|
<td></td>
|
177
200
|
<td>X</td>
|
178
201
|
<td>X</td>
|
@@ -214,7 +237,7 @@ If the user does not authorize your application to access his/her contacts list,
|
|
214
237
|
|
215
238
|
OmniContacts supports OAuth 1.0 and OAuth 2.0 token refresh, but for both it needs to persist data between requests. OmniContacts stores access tokens in the session. If you hit the 4KB cookie storage limit you better opt for the Memcache or the Active Record storage.
|
216
239
|
|
217
|
-
Gmail requires you to register the redirect_path on their website along with your application. Make sure to use the same value present in the configuration file, or `/contacts/gmail/callback` if using the default.
|
240
|
+
Gmail requires you to register the redirect_path on their website along with your application. Make sure to use the same value present in the configuration file, or `/contacts/gmail/callback` if using the default. Also make sure that your full url is used including "www" if your site redirects from the root domain.
|
218
241
|
|
219
242
|
To configure the max number of contacts to download from Gmail, just add a max results parameter in your initializer:
|
220
243
|
|
@@ -246,6 +269,11 @@ The `mock` method allows to configure per-provider the result to return:
|
|
246
269
|
|
247
270
|
You can either pass a single hash or an array of hashes. If you pass a string, an error will be triggered with subsequent redirect to `/contacts/failure?error_message=internal_error`
|
248
271
|
|
272
|
+
You can also pass a user to fill `omnicontacts.user` (optional)
|
273
|
+
```ruby
|
274
|
+
OmniContacts.integration_test.mock(:provider_name, {:email => "contact@example.com"}, {:email => "user@example.com"})
|
275
|
+
```
|
276
|
+
|
249
277
|
Follows a full example of an integration test:
|
250
278
|
|
251
279
|
```ruby
|
data/lib/omnicontacts.rb
CHANGED
data/lib/omnicontacts/builder.rb
CHANGED
@@ -4,6 +4,7 @@ module OmniContacts
|
|
4
4
|
autoload :Gmail, "omnicontacts/importer/gmail"
|
5
5
|
autoload :Yahoo, "omnicontacts/importer/yahoo"
|
6
6
|
autoload :Hotmail, "omnicontacts/importer/hotmail"
|
7
|
+
autoload :Outlook, "omnicontacts/importer/outlook"
|
7
8
|
autoload :Facebook, "omnicontacts/importer/facebook"
|
8
9
|
autoload :Linkedin, "omnicontacts/importer/linkedin"
|
9
10
|
|
@@ -13,12 +13,12 @@ module OmniContacts
|
|
13
13
|
super *args
|
14
14
|
@auth_host = 'graph.facebook.com'
|
15
15
|
@authorize_path = '/oauth/authorize'
|
16
|
-
@scope = 'email,user_relationships,user_birthday'
|
16
|
+
@scope = 'email,user_relationships,user_birthday,user_friends'
|
17
17
|
@auth_token_path = '/oauth/access_token'
|
18
18
|
@contacts_host = 'graph.facebook.com'
|
19
|
-
@friends_path = '/me/friends'
|
20
|
-
@family_path = '/me/family'
|
21
|
-
@self_path = '/me'
|
19
|
+
@friends_path = '/v2.5/me/friends'
|
20
|
+
@family_path = '/v2.5/me/family'
|
21
|
+
@self_path = '/v2.5/me'
|
22
22
|
end
|
23
23
|
|
24
24
|
def fetch_contacts_using_access_token access_token, access_token_secret
|
@@ -13,18 +13,18 @@ module OmniContacts
|
|
13
13
|
@auth_host = "accounts.google.com"
|
14
14
|
@authorize_path = "/o/oauth2/auth"
|
15
15
|
@auth_token_path = "/o/oauth2/token"
|
16
|
-
@scope = (args[3] && args[3][:scope]) || "https://www.
|
16
|
+
@scope = (args[3] && args[3][:scope]) || "https://www.googleapis.com/auth/contacts.readonly https://www.googleapis.com/auth/userinfo#email https://www.googleapis.com/auth/userinfo.profile"
|
17
17
|
@contacts_host = "www.google.com"
|
18
18
|
@contacts_path = "/m8/feeds/contacts/default/full"
|
19
19
|
@max_results = (args[3] && args[3][:max_results]) || 100
|
20
20
|
@self_host = "www.googleapis.com"
|
21
|
-
@profile_path = "/oauth2/
|
21
|
+
@profile_path = "/oauth2/v3/userinfo"
|
22
22
|
end
|
23
23
|
|
24
24
|
def fetch_contacts_using_access_token access_token, token_type
|
25
25
|
fetch_current_user(access_token, token_type)
|
26
26
|
contacts_response = https_get(@contacts_host, @contacts_path, contacts_req_params, contacts_req_headers(access_token, token_type))
|
27
|
-
contacts_from_response
|
27
|
+
contacts_from_response(contacts_response, access_token)
|
28
28
|
end
|
29
29
|
|
30
30
|
def fetch_current_user access_token, token_type
|
@@ -43,7 +43,7 @@ module OmniContacts
|
|
43
43
|
{"GData-Version" => "3.0", "Authorization" => "#{token_type} #{token}"}
|
44
44
|
end
|
45
45
|
|
46
|
-
def contacts_from_response
|
46
|
+
def contacts_from_response(response_as_json, access_token)
|
47
47
|
response = JSON.parse(response_as_json)
|
48
48
|
|
49
49
|
return [] if response['feed'].nil? || response['feed']['entry'].nil?
|
@@ -115,7 +115,7 @@ module OmniContacts
|
|
115
115
|
|
116
116
|
new_address[:address_1] = address['gd$street']['$t'] if address['gd$street']
|
117
117
|
new_address[:address_1] = address['gd$formattedAddress']['$t'] if new_address[:address_1].nil? && address['gd$formattedAddress']
|
118
|
-
if new_address[:address_1].index("\n")
|
118
|
+
if !new_address[:address_1].nil? && new_address[:address_1].index("\n")
|
119
119
|
parts = new_address[:address_1].split("\n")
|
120
120
|
new_address[:address_1] = parts.first
|
121
121
|
# this may contain city/state/zip if user jammed it all into one string.... :-(
|
@@ -151,11 +151,13 @@ module OmniContacts
|
|
151
151
|
# Support older versions of the gem by keeping singular entries around
|
152
152
|
contact[:phone_number] = contact[:phone_numbers][0][:number] if contact[:phone_numbers][0]
|
153
153
|
|
154
|
-
if entry[
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
154
|
+
if entry["link"] && entry["link"].is_a?(Array)
|
155
|
+
entry["link"].each do |link|
|
156
|
+
if link["type"] == 'image/*' && link["gd$etag"]
|
157
|
+
contact[:profile_picture] = link["href"] + "?&access_token=" + access_token
|
158
|
+
break
|
159
|
+
end
|
160
|
+
end
|
159
161
|
end
|
160
162
|
|
161
163
|
if entry['gContact$event']
|
@@ -180,15 +182,11 @@ module OmniContacts
|
|
180
182
|
contacts
|
181
183
|
end
|
182
184
|
|
183
|
-
def image_url gmail_id
|
184
|
-
return "https://profiles.google.com/s2/photos/profile/" + gmail_id if gmail_id
|
185
|
-
end
|
186
|
-
|
187
185
|
def current_user me, access_token, token_type
|
188
186
|
return nil if me.nil?
|
189
187
|
me = JSON.parse(me)
|
190
188
|
user = {:id => me['id'], :email => me['email'], :name => me['name'], :first_name => me['given_name'],
|
191
|
-
:last_name => me['family_name'], :gender => me['gender'], :birthday => birthday(me['birthday']), :profile_picture =>
|
189
|
+
:last_name => me['family_name'], :gender => me['gender'], :birthday => birthday(me['birthday']), :profile_picture => me["picture"],
|
192
190
|
:access_token => access_token, :token_type => token_type
|
193
191
|
}
|
194
192
|
user
|
@@ -13,7 +13,7 @@ module OmniContacts
|
|
13
13
|
super app, client_id, client_secret, options
|
14
14
|
@auth_host = "login.live.com"
|
15
15
|
@authorize_path = "/oauth20_authorize.srf"
|
16
|
-
@scope = options[:permissions] || "wl.signin, wl.basic, wl.birthday , wl.emails ,wl.contacts_birthday , wl.contacts_photos"
|
16
|
+
@scope = options[:permissions] || "wl.signin, wl.basic, wl.birthday , wl.emails ,wl.contacts_birthday , wl.contacts_photos, wl.contacts_emails"
|
17
17
|
@auth_token_path = "/oauth20_token.srf"
|
18
18
|
@contacts_host = "apis.live.net"
|
19
19
|
@contacts_path = "/v5.0/me/contacts"
|
@@ -41,14 +41,10 @@ module OmniContacts
|
|
41
41
|
# creating nil fields to keep the fields consistent across other networks
|
42
42
|
contact = {:id => nil, :first_name => nil, :last_name => nil, :name => nil, :email => nil, :gender => nil, :birthday => nil, :profile_picture=> nil, :relation => nil, :email_hashes => []}
|
43
43
|
contact[:id] = entry['user_id'] ? entry['user_id'] : entry['id']
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
contact[:first_name] = normalize_name(entry['first_name'])
|
49
|
-
contact[:last_name] = normalize_name(entry['last_name'])
|
50
|
-
contact[:name] = normalize_name(entry['name'])
|
51
|
-
end
|
44
|
+
contact[:email] = parse_email(entry['emails']) if valid_email? parse_email(entry['emails'])
|
45
|
+
contact[:first_name] = normalize_name(entry['first_name'])
|
46
|
+
contact[:last_name] = normalize_name(entry['last_name'])
|
47
|
+
contact[:name] = normalize_name(entry['name'])
|
52
48
|
contact[:birthday] = birthday_format(entry['birth_month'], entry['birth_day'], entry['birth_year'])
|
53
49
|
contact[:gender] = entry['gender']
|
54
50
|
contact[:profile_picture] = image_url(entry['user_id'])
|
@@ -60,7 +56,7 @@ module OmniContacts
|
|
60
56
|
|
61
57
|
def parse_email(emails)
|
62
58
|
return nil if emails.nil?
|
63
|
-
emails['account']
|
59
|
+
emails['account'] || emails['preferred'] || emails['personal'] || emails['business'] || emails['other']
|
64
60
|
end
|
65
61
|
|
66
62
|
def current_user me
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require "omnicontacts/middleware/oauth2"
|
2
|
+
require "omnicontacts/parse_utils"
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
# API Docs: https://msdn.microsoft.com/en-us/office/office365/api/api-catalog#Outlookcontacts
|
6
|
+
module OmniContacts
|
7
|
+
module Importer
|
8
|
+
class Outlook < Middleware::OAuth2
|
9
|
+
include ParseUtils
|
10
|
+
|
11
|
+
attr_reader :auth_host, :authorize_path, :auth_token_path, :scope
|
12
|
+
|
13
|
+
def initialize app, client_id, client_secret, options ={}
|
14
|
+
super app, client_id, client_secret, options
|
15
|
+
@auth_host = "login.microsoftonline.com"
|
16
|
+
@authorize_path = "/common/oauth2/v2.0/authorize"
|
17
|
+
@scope = options[:permissions] || "https://outlook.office.com/contacts.read"
|
18
|
+
@auth_token_path = "/common/oauth2/v2.0/token"
|
19
|
+
@contacts_host = "outlook.office.com"
|
20
|
+
@contacts_path = "/api/v2.0/me/contacts"
|
21
|
+
@self_path = "/api/v2.0/me"
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch_contacts_using_access_token access_token, token_type
|
25
|
+
fetch_current_user(access_token, token_type)
|
26
|
+
contacts_response = https_get(@contacts_host, @contacts_path, {}, contacts_req_headers(access_token, token_type))
|
27
|
+
contacts_from_response contacts_response
|
28
|
+
end
|
29
|
+
|
30
|
+
def fetch_current_user access_token, token_type
|
31
|
+
self_response = https_get(@contacts_host, @self_path, {}, contacts_req_headers(access_token, token_type))
|
32
|
+
user = current_user self_response
|
33
|
+
set_current_user user
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def contacts_req_headers token, token_type
|
39
|
+
{ "Authorization" => "#{token_type} #{token}" }
|
40
|
+
end
|
41
|
+
|
42
|
+
def current_user me
|
43
|
+
return nil if me.nil?
|
44
|
+
me = JSON.parse(me)
|
45
|
+
|
46
|
+
name_splitted = me["DisplayName"].split(" ")
|
47
|
+
first_name = name_splitted.first
|
48
|
+
last_name = name_splitted.last if name_splitted.size > 1
|
49
|
+
|
50
|
+
user = empty_contact
|
51
|
+
user[:id] = me["Id"]
|
52
|
+
user[:email] = me["EmailAddress"]
|
53
|
+
user[:name] = me["DisplayName"]
|
54
|
+
user[:first_name] = normalize_name(first_name)
|
55
|
+
user[:last_name] = normalize_name(last_name)
|
56
|
+
user
|
57
|
+
end
|
58
|
+
|
59
|
+
def contacts_from_response response_as_json
|
60
|
+
response = JSON.parse(response_as_json)
|
61
|
+
contacts = []
|
62
|
+
response["value"].each do |entry|
|
63
|
+
contact = empty_contact
|
64
|
+
# Full fields reference:
|
65
|
+
# https://msdn.microsoft.com/office/office365/api/complex-types-for-mail-contacts-calendar#RESTAPIResourcesContact
|
66
|
+
contact[:id] = entry["Id"]
|
67
|
+
contact[:first_name] = entry["GivenName"]
|
68
|
+
contact[:last_name] = entry["Surname"]
|
69
|
+
contact[:name] = entry["DisplayName"]
|
70
|
+
contact[:email] = parse_email(entry["EmailAddresses"])
|
71
|
+
contact[:birthday] = birthday(entry["Birthday"])
|
72
|
+
|
73
|
+
address = [entry["HomeAddress"], entry["BusinessAddress"], entry["OtherAddress"]].reject(&:empty?).first
|
74
|
+
if address
|
75
|
+
contact[:address_1] = address["Street"]
|
76
|
+
contact[:city] = address["City"]
|
77
|
+
contact[:region] = address["State"]
|
78
|
+
contact[:postcode] = address["PostalCode"]
|
79
|
+
contact[:country] = address["CountryOrRegion"]
|
80
|
+
end
|
81
|
+
|
82
|
+
contacts << contact if contact[:name] || contact[:first_name]
|
83
|
+
end
|
84
|
+
contacts
|
85
|
+
end
|
86
|
+
|
87
|
+
def empty_contact
|
88
|
+
{ :id => nil, :first_name => nil, :last_name => nil, :name => nil, :email => nil,
|
89
|
+
:gender => nil, :birthday => nil, :profile_picture => nil, :address_1 => nil,
|
90
|
+
:address_2 => nil, :city => nil, :region => nil, :postcode => nil, :relation => nil }
|
91
|
+
end
|
92
|
+
|
93
|
+
def parse_email emails
|
94
|
+
return nil if emails.nil?
|
95
|
+
emails.map! { |email| email["Address"] }
|
96
|
+
emails.select! { |email| valid_email? email }
|
97
|
+
emails.first
|
98
|
+
end
|
99
|
+
|
100
|
+
def birthday dob
|
101
|
+
return nil if dob.nil?
|
102
|
+
birthday = dob[0..9].split("-")
|
103
|
+
birthday[0] = nil if birthday[0].to_i < 1900 # if year is not set it returns 1604
|
104
|
+
return birthday_format(birthday[1], birthday[2], birthday[0])
|
105
|
+
end
|
106
|
+
|
107
|
+
def valid_email? value
|
108
|
+
/\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/.match(value)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -2,28 +2,30 @@ require 'singleton'
|
|
2
2
|
|
3
3
|
class IntegrationTest
|
4
4
|
include Singleton
|
5
|
-
|
5
|
+
|
6
6
|
attr_accessor :enabled
|
7
|
-
|
7
|
+
|
8
8
|
def initialize
|
9
9
|
enabled = false
|
10
10
|
clear_mocks
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
def clear_mocks
|
14
|
-
@
|
14
|
+
@user_mocks = {}
|
15
|
+
@contact_mocks = {}
|
15
16
|
end
|
16
|
-
|
17
|
-
def mock provider,
|
18
|
-
@
|
17
|
+
|
18
|
+
def mock provider, contacts, user = {}
|
19
|
+
@contact_mocks[provider.to_sym] = contacts
|
20
|
+
@user_mocks[provider.to_sym] = user
|
19
21
|
end
|
20
|
-
|
22
|
+
|
21
23
|
def mock_authorization_from_user provider
|
22
24
|
[302, {"Content-Type" => "application/x-www-form-urlencoded", "location" => provider.redirect_path}, []]
|
23
25
|
end
|
24
|
-
|
26
|
+
|
25
27
|
def mock_fetch_contacts provider
|
26
|
-
result = @
|
28
|
+
result = @contact_mocks[provider.class_name.to_sym] || []
|
27
29
|
if result.is_a? Array
|
28
30
|
result
|
29
31
|
elsif result.is_a? Hash
|
@@ -32,5 +34,8 @@ class IntegrationTest
|
|
32
34
|
raise result.to_s
|
33
35
|
end
|
34
36
|
end
|
35
|
-
|
36
|
-
|
37
|
+
|
38
|
+
def mock_fetch_user provider
|
39
|
+
@user_mocks[provider.class_name.to_sym] || {}
|
40
|
+
end
|
41
|
+
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# Extending classes are required to implement
|
5
5
|
# the following methods:
|
6
|
-
# * request_authorization_from_user
|
6
|
+
# * request_authorization_from_user
|
7
7
|
# * fetch_contatcs
|
8
8
|
module OmniContacts
|
9
9
|
module Middleware
|
@@ -22,9 +22,9 @@ module OmniContacts
|
|
22
22
|
end
|
23
23
|
|
24
24
|
# Rack callback. It handles three cases:
|
25
|
-
# * user visit middleware entry point.
|
25
|
+
# * user visit middleware entry point.
|
26
26
|
# In this case request_authorization_from_user is called
|
27
|
-
# * user is redirected back to the application
|
27
|
+
# * user is redirected back to the application
|
28
28
|
# from the authorization site. In this case the list
|
29
29
|
# of contacts is fetched and stored in the variables
|
30
30
|
# omnicontacts.contacts within the Rack env variable.
|
@@ -34,8 +34,10 @@ module OmniContacts
|
|
34
34
|
def call env
|
35
35
|
@env = env
|
36
36
|
if env["PATH_INFO"] =~ /^#{@listening_path}\/?$/
|
37
|
+
session['omnicontacts.params'] = Rack::Request.new(env).params
|
37
38
|
handle_initial_request
|
38
39
|
elsif env["PATH_INFO"] =~ /^#{redirect_path}/
|
40
|
+
env['omnicontacts.params'] = session.delete('omnicontacts.params')
|
39
41
|
handle_callback
|
40
42
|
else
|
41
43
|
@app.call(env)
|
@@ -43,7 +45,7 @@ module OmniContacts
|
|
43
45
|
end
|
44
46
|
|
45
47
|
private
|
46
|
-
|
48
|
+
|
47
49
|
def test_mode?
|
48
50
|
IntegrationTest.instance.enabled
|
49
51
|
end
|
@@ -65,6 +67,7 @@ module OmniContacts
|
|
65
67
|
else
|
66
68
|
fetch_contacts
|
67
69
|
end
|
70
|
+
set_current_user IntegrationTest.instance.mock_fetch_user(self) if test_mode?
|
68
71
|
@app.call(@env)
|
69
72
|
end
|
70
73
|
end
|
@@ -74,7 +77,7 @@ module OmniContacts
|
|
74
77
|
end
|
75
78
|
|
76
79
|
# This method rescues executes a block of code and
|
77
|
-
# rescue all exceptions. In case of an exception the
|
80
|
+
# rescue all exceptions. In case of an exception the
|
78
81
|
# user is redirected to the failure endpoint.
|
79
82
|
def execute_and_rescue_exceptions
|
80
83
|
yield
|
@@ -89,7 +92,8 @@ module OmniContacts
|
|
89
92
|
def handle_error error_type, exception
|
90
93
|
logger.puts("Error #{error_type} while processing #{@env["PATH_INFO"]}: #{exception.message}") if logger
|
91
94
|
failure_url = "#{ MOUNT_PATH }failure?error_message=#{error_type}&importer=#{class_name}"
|
92
|
-
|
95
|
+
params_url = append_request_params(failure_url)
|
96
|
+
target_url = append_state_query(params_url)
|
93
97
|
[302, {"Content-Type" => "text/html", "location" => target_url}, []]
|
94
98
|
end
|
95
99
|
|
@@ -106,9 +110,17 @@ module OmniContacts
|
|
106
110
|
"omnicontacts." + class_name
|
107
111
|
end
|
108
112
|
|
113
|
+
def append_request_params(target_url)
|
114
|
+
return target_url unless @env['omnicontacts.params']
|
115
|
+
params = Rack::Utils.build_query(@env['omnicontacts.params'])
|
116
|
+
unless params.nil? or params.empty?
|
117
|
+
target_url = target_url + (target_url.include?("?")?"&":"?") + params
|
118
|
+
end
|
119
|
+
return target_url
|
120
|
+
end
|
121
|
+
|
109
122
|
def append_state_query(target_url)
|
110
123
|
state = Rack::Utils.parse_query(@env['QUERY_STRING'])['state']
|
111
|
-
|
112
124
|
unless state.nil?
|
113
125
|
target_url = target_url + (target_url.include?("?")?"&":"?") + 'state=' + state
|
114
126
|
end
|
data/omnicontacts.gemspec
CHANGED
@@ -4,7 +4,7 @@ require File.expand_path('../lib/omnicontacts', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.name = 'omnicontacts'
|
6
6
|
gem.description = %q{A generalized Rack middleware for importing contacts from major email providers.}
|
7
|
-
gem.authors = ['Diego Castorina', 'Jordan Lance']
|
7
|
+
gem.authors = ['Diego Castorina', 'Jordan Lance', 'Asma Tameem', 'Randy Villanueva']
|
8
8
|
gem.email = ['diegocastorina@gmail.com', 'voorruby@gmail.com']
|
9
9
|
|
10
10
|
gem.add_runtime_dependency 'rack'
|
@@ -2,11 +2,23 @@ require "spec_helper"
|
|
2
2
|
require "omnicontacts/importer/gmail"
|
3
3
|
|
4
4
|
describe OmniContacts::Importer::Gmail do
|
5
|
-
|
6
5
|
let(:gmail) { OmniContacts::Importer::Gmail.new({}, "client_id", "client_secret") }
|
7
6
|
|
8
|
-
let(:gmail_with_scope_args) {
|
9
|
-
|
7
|
+
let(:gmail_with_scope_args) {
|
8
|
+
OmniContacts::Importer::Gmail.new(
|
9
|
+
{},
|
10
|
+
"client_id",
|
11
|
+
"client_secret",
|
12
|
+
{
|
13
|
+
scope: %w(
|
14
|
+
https://www.googleapis.com/auth/contacts.readonly
|
15
|
+
https://www.googleapis.com/auth/userinfo#email
|
16
|
+
https://www.googleapis.com/auth/userinfo.profile
|
17
|
+
).join(" ")
|
18
|
+
}
|
19
|
+
)
|
20
|
+
}
|
21
|
+
|
10
22
|
let(:self_response) {
|
11
23
|
'{
|
12
24
|
"id":"16482944006464829443",
|
@@ -39,6 +51,8 @@ describe OmniContacts::Importer::Gmail do
|
|
39
51
|
|
40
52
|
"title":{"$t":"Users\'s Contacts"},
|
41
53
|
"link":[
|
54
|
+
{"rel":"http://schemas.google.com/contacts/2008/rel#photo","type":"image/*",
|
55
|
+
"href":"https://www.google.com/m8/feeds/photos/media/logged_in_user%40gmail.com/6b41d030b05abc","gd$etag":"\"VSxuN0cISit7I2A1UVUSdy12KHwgBFkE333.\""},
|
42
56
|
{"rel":"alternate","type":"text/html","href":"http://www.google.com/"},
|
43
57
|
{"rel":"http://schemas.google.com/g/2005#feed","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full"},
|
44
58
|
{"rel":"http://schemas.google.com/g/2005#post","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full"},
|
@@ -60,7 +74,8 @@ describe OmniContacts::Importer::Gmail do
|
|
60
74
|
"category":[{"scheme":"http://schemas.google.com/g/2005#kind","term":"http://schemas.google.com/contact/2008#contact"}],
|
61
75
|
"title":{"$t":"Edward Bennet"},
|
62
76
|
"link":[
|
63
|
-
{"rel":"http://schemas.google.com/contacts/2008/rel#photo","type":"image/*",
|
77
|
+
{"rel":"http://schemas.google.com/contacts/2008/rel#photo","type":"image/*",
|
78
|
+
"href":"https://www.google.com/m8/feeds/photos/media/logged_in_user%40gmail.com/6b41d030b05abc", "gd$etag":"\"VSxuN0cISit7I2A1UVUSdy12KHwgBFkE333.\""},
|
64
79
|
{"rel":"self","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"},
|
65
80
|
{"rel":"edit","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"}
|
66
81
|
],
|
@@ -127,9 +142,9 @@ describe OmniContacts::Importer::Gmail do
|
|
127
142
|
contacts_as_json
|
128
143
|
end
|
129
144
|
gmail.fetch_contacts_using_access_token token, token_type
|
130
|
-
|
131
|
-
gmail.scope.should eq "https://www.
|
132
|
-
gmail_with_scope_args.scope.should eq "https://www.
|
145
|
+
|
146
|
+
gmail.scope.should eq "https://www.googleapis.com/auth/contacts.readonly https://www.googleapis.com/auth/userinfo#email https://www.googleapis.com/auth/userinfo.profile"
|
147
|
+
gmail_with_scope_args.scope.should eq "https://www.googleapis.com/auth/contacts.readonly https://www.googleapis.com/auth/userinfo#email https://www.googleapis.com/auth/userinfo.profile"
|
133
148
|
end
|
134
149
|
|
135
150
|
it "should correctly parse id, name, email, gender, birthday, profile picture and relation for 1st contact" do
|
@@ -144,9 +159,9 @@ describe OmniContacts::Importer::Gmail do
|
|
144
159
|
result.first[:name].should eq("Edward Bennet")
|
145
160
|
result.first[:email].should eq("bennet@gmail.com")
|
146
161
|
result.first[:gender].should eq("male")
|
147
|
-
result.first[:birthday].should eq({:day=>02, :month=>07, :year=>1954})
|
162
|
+
result.first[:birthday].should eq({ :day => 02, :month => 07, :year => 1954 })
|
148
163
|
result.first[:relation].should eq('father')
|
149
|
-
result.first[:profile_picture].should eq("https://
|
164
|
+
result.first[:profile_picture].should eq("https://www.google.com/m8/feeds/photos/media/logged_in_user%40gmail.com/6b41d030b05abc?&access_token=token")
|
150
165
|
result.first[:dates][0][:name].should eq("anniversary")
|
151
166
|
end
|
152
167
|
|
@@ -161,8 +176,8 @@ describe OmniContacts::Importer::Gmail do
|
|
161
176
|
result.last[:name].should eq("Emilia Fox")
|
162
177
|
result.last[:email].should eq("emilia.fox@gmail.com")
|
163
178
|
result.last[:gender].should eq("female")
|
164
|
-
result.last[:birthday].should eq({:day=>10, :month=>02, :year=>1974})
|
165
|
-
result.last[:profile_picture].should
|
179
|
+
result.last[:birthday].should eq({ :day => 10, :month => 02, :year => 1974 })
|
180
|
+
result.last[:profile_picture].should be_nil
|
166
181
|
result.last[:relation].should eq('spouse')
|
167
182
|
result.first[:address_1].should eq('1313 Trashview Court')
|
168
183
|
result.first[:address_2].should eq('Apt. 13')
|
@@ -187,8 +202,122 @@ describe OmniContacts::Importer::Gmail do
|
|
187
202
|
user[:name].should eq("Chris Johnson")
|
188
203
|
user[:email].should eq("chrisjohnson@gmail.com")
|
189
204
|
user[:gender].should eq("male")
|
190
|
-
user[:birthday].should eq({:day=>21, :month=>06, :year=>1982})
|
191
|
-
user[:profile_picture].should eq("https://
|
205
|
+
user[:birthday].should eq({ :day => 21, :month => 06, :year => 1982 })
|
206
|
+
user[:profile_picture].should eq("https://lh3.googleusercontent.com/-b8aFbTBM/AAAAAAI/IWA/vsek/photo.jpg")
|
207
|
+
end
|
208
|
+
|
209
|
+
context "when address_1 is nil" do
|
210
|
+
let(:contacts_as_json) {
|
211
|
+
'{"version":"1.0","encoding":"UTF-8",
|
212
|
+
"feed":{
|
213
|
+
"xmlns":"http://www.w3.org/2005/Atom",
|
214
|
+
"xmlns$openSearch":"http://a9.com/-/spec/opensearch/1.1/",
|
215
|
+
"xmlns$gContact":"http://schemas.google.com/contact/2008",
|
216
|
+
"xmlns$batch":"http://schemas.google.com/gdata/batch",
|
217
|
+
"xmlns$gd":"http://schemas.google.com/g/2005",
|
218
|
+
"gd$etag":"W/\"C0YHRno7fSt7I2A9WhBSQ0Q.\"",
|
219
|
+
|
220
|
+
"id":{"$t":"logged_in_user@gmail.com"},
|
221
|
+
"updated":{"$t":"2013-02-20T20:12:17.405Z"},
|
222
|
+
"category":[{
|
223
|
+
"scheme":"http://schemas.google.com/g/2005#kind",
|
224
|
+
"term":"http://schemas.google.com/contact/2008#contact"
|
225
|
+
}],
|
226
|
+
|
227
|
+
"title":{"$t":"Users\'s Contacts"},
|
228
|
+
"link":[
|
229
|
+
{"rel":"http://schemas.google.com/contacts/2008/rel#photo","type":"image/*",
|
230
|
+
"href":"https://www.google.com/m8/feeds/photos/media/logged_in_user%40gmail.com/6b41d030b05abc","gd$etag":"\"VSxuN0cISit7I2A1UVUSdy12KHwgBFkE333.\""},
|
231
|
+
{"rel":"alternate","type":"text/html","href":"http://www.google.com/"},
|
232
|
+
{"rel":"http://schemas.google.com/g/2005#feed","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full"},
|
233
|
+
{"rel":"http://schemas.google.com/g/2005#post","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full"},
|
234
|
+
{"rel":"http://schemas.google.com/g/2005#batch","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/batch"},
|
235
|
+
{"rel":"self","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full?alt\u003djson\u0026max-results\u003d1"},
|
236
|
+
{"rel":"next","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full?alt\u003djson\u0026start-index\u003d2\u0026max-results\u003d1"}
|
237
|
+
],
|
238
|
+
"author":[{"name":{"$t":"Edward"},"email":{"$t":"logged_in_user@gmail.com"}}],
|
239
|
+
"generator":{"version":"1.0","uri":"http://www.google.com/m8/feeds","$t":"Contacts"},
|
240
|
+
"openSearch$totalResults":{"$t":"1007"},
|
241
|
+
"openSearch$startIndex":{"$t":"1"},
|
242
|
+
"openSearch$itemsPerPage":{"$t":"1"},
|
243
|
+
"entry":[
|
244
|
+
{
|
245
|
+
"gd$etag":"\"R3oyfDVSLyt7I2A9WhBTSEULRA0.\"",
|
246
|
+
"id":{"$t":"http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1"},
|
247
|
+
"updated":{"$t":"2013-02-14T22:36:36.494Z"},
|
248
|
+
"app$edited":{"xmlns$app":"http://www.w3.org/2007/app","$t":"2013-02-14T22:36:36.494Z"},
|
249
|
+
"category":[{"scheme":"http://schemas.google.com/g/2005#kind","term":"http://schemas.google.com/contact/2008#contact"}],
|
250
|
+
"title":{"$t":"Edward Bennet"},
|
251
|
+
"link":[
|
252
|
+
{"rel":"http://schemas.google.com/contacts/2008/rel#photo","type":"image/*",
|
253
|
+
"href":"https://www.google.com/m8/feeds/photos/media/logged_in_user%40gmail.com/6b41d030b05abc", "gd$etag":"\"VSxuN0cISit7I2A1UVUSdy12KHwgBFkE333.\""},
|
254
|
+
{"rel":"self","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"},
|
255
|
+
{"rel":"edit","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"}
|
256
|
+
],
|
257
|
+
"gd$name":{
|
258
|
+
"gd$fullName":{"$t":"Edward Bennet"},
|
259
|
+
"gd$givenName":{"$t":"Edward"},
|
260
|
+
"gd$familyName":{"$t":"Bennet"}
|
261
|
+
},
|
262
|
+
"gd$organization":[{"rel":"http://schemas.google.com/g/2005#other","gd$orgName":{"$t":"Google"},"gd$orgTitle":{"$t":"Master Developer"}}],
|
263
|
+
"gContact$birthday":{"when":"1954-07-02"},
|
264
|
+
"gContact$relation":{"rel":"father"},
|
265
|
+
"gContact$gender":{"value":"male"},
|
266
|
+
"gContact$event":[{"rel":"anniversary","gd$when":{"startTime":"1983-04-21"}},{"label":"New Job","gd$when":{"startTime":"2014-12-01"}}],
|
267
|
+
"gd$email":[{"rel":"http://schemas.google.com/g/2005#other","address":"bennet@gmail.com","primary":"true"}],
|
268
|
+
"gContact$groupMembershipInfo":[{"deleted":"false","href":"http://www.google.com/m8/feeds/groups/logged_in_user%40gmail.com/base/6"}],
|
269
|
+
"gd$structuredPostalAddress":[{"rel":"http://schemas.google.com/g/2005#home","gd$formattedAddress":{},"gd$street":{},"gd$postcode":{"$t":"66666"},"gd$country":{"code":"VA","$t":"Valoran"},"gd$city":{"$t":"Nowheresville"},"gd$region":{"$t":"OK"}}],
|
270
|
+
"gd$phoneNumber":[{"rel":"http://schemas.google.com/g/2005#mobile","uri":"tel:+34-653-15-76-88","$t":"653157688"}]
|
271
|
+
},
|
272
|
+
{
|
273
|
+
"gd$etag":"\"R3oyfDVSLyt7I2A9WhBTSEULRA0.\"",
|
274
|
+
"id":{"$t":"http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1"},
|
275
|
+
"updated":{"$t":"2013-02-15T22:36:36.494Z"},
|
276
|
+
"app$edited":{"xmlns$app":"http://www.w3.org/2007/app","$t":"2013-02-15T22:36:36.494Z"},
|
277
|
+
"category":[{"scheme":"http://schemas.google.com/g/2005#kind","term":"http://schemas.google.com/contact/2008#contact"}],
|
278
|
+
"title":{"$t":"Emilia Fox"},
|
279
|
+
"link":[
|
280
|
+
{"rel":"http://schemas.google.com/contacts/2008/rel#photo","type":"image/*","href":"https://www.google.com/m8/feeds/photos/media/logged_in_user%40gmail.com/1"},
|
281
|
+
{"rel":"self","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"},
|
282
|
+
{"rel":"edit","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"}
|
283
|
+
],
|
284
|
+
"gd$name":{
|
285
|
+
"gd$fullName":{"$t":"Emilia Fox"},
|
286
|
+
"gd$givenName":{"$t":"Emilia"},
|
287
|
+
"gd$familyName":{"$t":"Fox"}
|
288
|
+
},
|
289
|
+
"gContact$birthday":{"when":"1974-02-10"},
|
290
|
+
"gContact$relation":[{"rel":"spouse"}],
|
291
|
+
"gContact$gender":{"value":"female"},
|
292
|
+
"gd$email":[{"rel":"http://schemas.google.com/g/2005#other","address":"emilia.fox@gmail.com","primary":"true"}],
|
293
|
+
"gContact$groupMembershipInfo":[{"deleted":"false","href":"http://www.google.com/m8/feeds/groups/logged_in_user%40gmail.com/base/6"}]
|
294
|
+
}]
|
295
|
+
}
|
296
|
+
}'
|
297
|
+
}
|
298
|
+
|
299
|
+
it "should correctly parse id, name, email, gender, birthday, profile picture, snailmail address, phone and relation for 2nd contact" do
|
300
|
+
gmail.should_receive(:https_get)
|
301
|
+
gmail.should_receive(:https_get).and_return(contacts_as_json)
|
302
|
+
result = gmail.fetch_contacts_using_access_token token, token_type
|
303
|
+
result.size.should be(2)
|
304
|
+
result.last[:id].should eq('http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1')
|
305
|
+
result.last[:first_name].should eq('Emilia')
|
306
|
+
result.last[:last_name].should eq('Fox')
|
307
|
+
result.last[:name].should eq("Emilia Fox")
|
308
|
+
result.last[:email].should eq("emilia.fox@gmail.com")
|
309
|
+
result.last[:gender].should eq("female")
|
310
|
+
result.last[:birthday].should eq({ :day => 10, :month => 02, :year => 1974 })
|
311
|
+
result.last[:profile_picture].should be_nil
|
312
|
+
result.last[:relation].should eq('spouse')
|
313
|
+
result.first[:address_1].should eq(nil)
|
314
|
+
result.first[:address_2].should eq(nil)
|
315
|
+
result.first[:city].should eq('Nowheresville')
|
316
|
+
result.first[:region].should eq('OK')
|
317
|
+
result.first[:country].should eq('VA')
|
318
|
+
result.first[:postcode].should eq('66666')
|
319
|
+
result.first[:phone_number].should eq('653157688')
|
320
|
+
end
|
192
321
|
end
|
193
322
|
end
|
194
|
-
end
|
323
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "omnicontacts/importer/outlook"
|
3
|
+
|
4
|
+
describe OmniContacts::Importer::Outlook do
|
5
|
+
|
6
|
+
let(:permissions) { "Contacts.Read" }
|
7
|
+
let(:outlook) { OmniContacts::Importer::Outlook.new({}, "app_id", "app_secret", {:permissions => permissions}) }
|
8
|
+
|
9
|
+
let(:self_response) {
|
10
|
+
'{
|
11
|
+
"@odata.context": "https://outlook.office.com/api/v2.0/$metadata#Me",
|
12
|
+
"@odata.id": "https://outlook.office.com/api/v2.0/Users(\'00034001-df52-d3d5-0000-000000000000@84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa\')",
|
13
|
+
"Id": "00034001-df52-d3d5-0000-000000000000@84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa",
|
14
|
+
"EmailAddress": "test.user@outlook.com",
|
15
|
+
"DisplayName": "Test User",
|
16
|
+
"Alias": "puid-00034001DF52D3D5",
|
17
|
+
"MailboxGuid": "00034001-df52-d3d5-0000-000000000000"
|
18
|
+
}'
|
19
|
+
}
|
20
|
+
|
21
|
+
let(:contacts_as_json) {
|
22
|
+
'{
|
23
|
+
"@odata.context": "https://outlook.office.com/api/v2.0/$metadata#Me/Contacts",
|
24
|
+
"value": [{
|
25
|
+
"@odata.id": "https://outlook.office.com/api/v2.0/Users(\'00034001-df52-d3d5-0000-000000000000@84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa\')/Contacts(\'AQMkADAwATM0MDAAMS1kZjUyLWQzZDUtMDACLTAwCgBGAAADQ1hAWLJpwk6DZYyOhnclvgcAxCL3G7jnpkiRUVmiNrhjJgAAAgEOAAAAxCL3G7jnpkiRUVmiNrhjJgAAAixsAAAA\')",
|
26
|
+
"@odata.etag": "W/\"EQAAABYAAADEIvcbuOemSJFRWaI2uGMmAAAAlo4t\"",
|
27
|
+
"Id": "AQMkADAwATM0MDAAMS1kZjUyLWQzZDUtMDACLTAwCgBGAAADQ1hAWLJpwk6DZYyOhnclvgcAxCL3G7jnpkiRUVmiNrhjJgAAAgEOAAAAxCL3G7jnpkiRUVmiNrhjJgAAAixsAAAA",
|
28
|
+
"CreatedDateTime": "2016-04-13T21:25:24Z",
|
29
|
+
"LastModifiedDateTime": "2016-04-14T19:36:55Z",
|
30
|
+
"ChangeKey": "EQAAABYAAADEIvcbuOemSJFRWaI2uGMmAAAAlo4t",
|
31
|
+
"Categories": [],
|
32
|
+
"ParentFolderId": "AQMkADAwATM0MDAAMS1kZjUyLWQzZDUtMDACLTAwCgAuAAADQ1hAWLJpwk6DZYyOhnclvgEAxCL3G7jnpkiRUVmiNrhjJgAAAgEOAAAA",
|
33
|
+
"Birthday": "1604-08-14T00:00:00Z",
|
34
|
+
"FileAs": "Contact, First",
|
35
|
+
"DisplayName": "First Contact",
|
36
|
+
"GivenName": "First",
|
37
|
+
"Initials": null,
|
38
|
+
"MiddleName": null,
|
39
|
+
"NickName": null,
|
40
|
+
"Surname": "Contact",
|
41
|
+
"Title": null,
|
42
|
+
"YomiGivenName": null,
|
43
|
+
"YomiSurname": null,
|
44
|
+
"YomiCompanyName": null,
|
45
|
+
"Generation": null,
|
46
|
+
"EmailAddresses": [{
|
47
|
+
"Name": "contact.first@email.com",
|
48
|
+
"Address": "contact.first@email.com"
|
49
|
+
}, {
|
50
|
+
"Name": "contact.second@email.com",
|
51
|
+
"Address": "contact.second@email.com"
|
52
|
+
}],
|
53
|
+
"ImAddresses": [],
|
54
|
+
"JobTitle": null,
|
55
|
+
"CompanyName": null,
|
56
|
+
"Department": null,
|
57
|
+
"OfficeLocation": null,
|
58
|
+
"Profession": null,
|
59
|
+
"BusinessHomePage": null,
|
60
|
+
"AssistantName": null,
|
61
|
+
"Manager": null,
|
62
|
+
"HomePhones": [],
|
63
|
+
"MobilePhone1": null,
|
64
|
+
"BusinessPhones": [],
|
65
|
+
"HomeAddress": {
|
66
|
+
"Street": "address1",
|
67
|
+
"City": "city",
|
68
|
+
"State": "state",
|
69
|
+
"CountryOrRegion": "US",
|
70
|
+
"PostalCode": "89111"
|
71
|
+
},
|
72
|
+
"BusinessAddress": {},
|
73
|
+
"OtherAddress": {},
|
74
|
+
"SpouseName": null,
|
75
|
+
"PersonalNotes": null,
|
76
|
+
"Children": []
|
77
|
+
}]
|
78
|
+
}'
|
79
|
+
}
|
80
|
+
|
81
|
+
describe "fetch_contacts_using_access_token" do
|
82
|
+
|
83
|
+
let(:access_token) { "access_token" }
|
84
|
+
let(:token_type) { "token_type" }
|
85
|
+
|
86
|
+
before(:each) do
|
87
|
+
outlook.instance_variable_set(:@env, {"HTTP_HOST" => "http://example.com"})
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should request the contacts by providing the authorization header with token_type and access_token" do
|
91
|
+
outlook.should_receive(:https_get) do |host, path, params, headers|
|
92
|
+
params.should eq({})
|
93
|
+
headers["Authorization"].should eq("token_type access_token")
|
94
|
+
self_response
|
95
|
+
end
|
96
|
+
|
97
|
+
outlook.should_receive(:https_get) do |host, path, params, headers|
|
98
|
+
params.should eq({})
|
99
|
+
headers["Authorization"].should eq("token_type access_token")
|
100
|
+
contacts_as_json
|
101
|
+
end
|
102
|
+
outlook.fetch_contacts_using_access_token access_token, token_type
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should set requested permissions in the authorization url" do
|
106
|
+
outlook.authorization_url.should match(/scope=#{Regexp.quote(CGI.escape(permissions))}/)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should correctly parse id, name and email" do
|
110
|
+
outlook.should_receive(:https_get).and_return(self_response)
|
111
|
+
outlook.should_receive(:https_get).and_return(contacts_as_json)
|
112
|
+
result = outlook.fetch_contacts_using_access_token access_token, token_type
|
113
|
+
|
114
|
+
result.size.should be(1)
|
115
|
+
result.first[:id].should eq('AQMkADAwATM0MDAAMS1kZjUyLWQzZDUtMDACLTAwCgBGAAADQ1hAWLJpwk6DZYyOhnclvgcAxCL3G7jnpkiRUVmiNrhjJgAAAgEOAAAAxCL3G7jnpkiRUVmiNrhjJgAAAixsAAAA')
|
116
|
+
result.first[:first_name].should eq("First")
|
117
|
+
result.first[:last_name].should eq("Contact")
|
118
|
+
result.first[:name].should eq("First Contact")
|
119
|
+
result.first[:email].should eq("contact.first@email.com")
|
120
|
+
result.first[:birthday].should eq({ :day => 14, :month => 8, :year => nil })
|
121
|
+
result.first[:address_1].should eq("address1")
|
122
|
+
result.first[:address_2].should be_nil
|
123
|
+
result.first[:city].should eq("city")
|
124
|
+
result.first[:region].should eq("state")
|
125
|
+
result.first[:postcode].should eq("89111")
|
126
|
+
result.first[:country].should eq("US")
|
127
|
+
result.first[:gender].should be_nil
|
128
|
+
result.first[:profile_picture].should be_nil
|
129
|
+
result.first[:relation].should be_nil
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should correctly parse and set logged in user information" do
|
133
|
+
outlook.should_receive(:https_get).and_return(self_response)
|
134
|
+
outlook.should_receive(:https_get).and_return(contacts_as_json)
|
135
|
+
|
136
|
+
outlook.fetch_contacts_using_access_token access_token, token_type
|
137
|
+
|
138
|
+
user = outlook.instance_variable_get(:@env)["omnicontacts.user"]
|
139
|
+
user.should_not be_nil
|
140
|
+
user[:id].should eq('00034001-df52-d3d5-0000-000000000000@84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa')
|
141
|
+
user[:first_name].should eq("Test")
|
142
|
+
user[:last_name].should eq("User")
|
143
|
+
user[:name].should eq("Test User")
|
144
|
+
user[:email].should eq("test.user@outlook.com")
|
145
|
+
user[:gender].should be_nil
|
146
|
+
user[:birthday].should be_nil
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
@@ -11,7 +11,7 @@ describe IntegrationTest do
|
|
11
11
|
IntegrationTest.instance.mock_authorization_from_user(provider)[1]["location"].should eq(redirect_path)
|
12
12
|
end
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
context "mock_callback" do
|
16
16
|
|
17
17
|
before(:each) {
|
@@ -20,11 +20,11 @@ describe IntegrationTest do
|
|
20
20
|
@provider.stub(:class_name => "test")
|
21
21
|
IntegrationTest.instance.clear_mocks
|
22
22
|
}
|
23
|
-
|
23
|
+
|
24
24
|
it "should return an empty contacts list" do
|
25
25
|
IntegrationTest.instance.mock_fetch_contacts(@provider).should be_empty
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
it "should return a configured list of contacts " do
|
29
29
|
contacts = [:name => 'John Doe', :email => 'john@doe.com']
|
30
30
|
IntegrationTest.instance.mock('test', contacts)
|
@@ -42,10 +42,19 @@ describe IntegrationTest do
|
|
42
42
|
result.first[:email].should eq(contact[:email])
|
43
43
|
result.first[:name].should eq(contact[:name])
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
|
+
it "should return a user" do
|
47
|
+
contact = {:name => 'John Doe', :email => 'john@doe.com'}
|
48
|
+
user = {:name => 'Mary Smith', :email => 'mary@smith.com'}
|
49
|
+
IntegrationTest.instance.mock('test', contact, user)
|
50
|
+
result = IntegrationTest.instance.mock_fetch_user(@provider)
|
51
|
+
result[:email].should eq(user[:email])
|
52
|
+
result[:name].should eq(user[:name])
|
53
|
+
end
|
54
|
+
|
46
55
|
it "should throw an exception" do
|
47
56
|
IntegrationTest.instance.mock('test', :some_error)
|
48
57
|
expect {IntegrationTest.instance.mock_fetch_contacts(@provider)}.to raise_error
|
49
58
|
end
|
50
59
|
end
|
51
|
-
end
|
60
|
+
end
|
@@ -13,6 +13,14 @@ describe OmniContacts::Middleware::BaseOAuth do
|
|
13
13
|
def redirect_path
|
14
14
|
"#{ MOUNT_PATH }testprovider/callback"
|
15
15
|
end
|
16
|
+
|
17
|
+
def self.mock_session
|
18
|
+
@mock_session ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def session
|
22
|
+
TestProvider.mock_session
|
23
|
+
end
|
16
24
|
end
|
17
25
|
OmniContacts.integration_test.enabled = true
|
18
26
|
end
|
@@ -31,7 +39,7 @@ describe OmniContacts::Middleware::BaseOAuth do
|
|
31
39
|
last_request.env["omnicontacts.contacts"].first[:email].should eq("user@example.com")
|
32
40
|
end
|
33
41
|
|
34
|
-
it "should
|
42
|
+
it "should redirect to failure url" do
|
35
43
|
OmniContacts.integration_test.mock(:testprovider, "some_error" )
|
36
44
|
get "#{ MOUNT_PATH }testprovider"
|
37
45
|
get "#{MOUNT_PATH }testprovider/callback"
|
@@ -44,10 +52,31 @@ describe OmniContacts::Middleware::BaseOAuth do
|
|
44
52
|
get "#{MOUNT_PATH }testprovider/callback?state=/parent/resource/id"
|
45
53
|
last_response.headers["location"].should eq("#{ MOUNT_PATH }failure?error_message=internal_error&importer=testprovider&state=/parent/resource/id")
|
46
54
|
end
|
55
|
+
|
56
|
+
it "should store request params in session" do
|
57
|
+
OmniContacts.integration_test.mock(:testprovider, :email => "user@example.com")
|
58
|
+
get "#{ MOUNT_PATH }testprovider?foo=bar"
|
59
|
+
app.session['omnicontacts.params'].should eq({'foo' => 'bar'})
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should pass the params from session to callback environment " do
|
63
|
+
OmniContacts.integration_test.mock(:testprovider, :email => "user@example.com")
|
64
|
+
app.session.merge!({'omnicontacts.params' => {'foo' => 'bar'}})
|
65
|
+
get "#{MOUNT_PATH }testprovider/callback?state=/parent/resource/id"
|
66
|
+
last_request.env["omnicontacts.params"].should eq({'foo' => 'bar'})
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should pass the params from session on failure" do
|
70
|
+
OmniContacts.integration_test.mock(:testprovider, "some_error" )
|
71
|
+
get "#{ MOUNT_PATH }testprovider"
|
72
|
+
app.session.merge!({'omnicontacts.params' => {'foo' => 'bar'}})
|
73
|
+
get "#{MOUNT_PATH }testprovider/callback"
|
74
|
+
last_response.should be_redirect
|
75
|
+
last_response.headers["location"].should be_include("foo=bar")
|
76
|
+
end
|
47
77
|
|
48
78
|
after(:all) do
|
49
79
|
OmniContacts.integration_test.enabled = false
|
50
80
|
OmniContacts.integration_test.clear_mocks
|
51
81
|
end
|
52
|
-
|
53
|
-
end
|
82
|
+
end
|
metadata
CHANGED
@@ -1,111 +1,100 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: omnicontacts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
5
|
-
prerelease:
|
4
|
+
version: 0.3.10
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Diego Castorina
|
9
8
|
- Jordan Lance
|
9
|
+
- Asma Tameem
|
10
|
+
- Randy Villanueva
|
10
11
|
autorequire:
|
11
12
|
bindir: bin
|
12
13
|
cert_chain: []
|
13
|
-
date:
|
14
|
+
date: 2018-04-23 00:00:00.000000000 Z
|
14
15
|
dependencies:
|
15
16
|
- !ruby/object:Gem::Dependency
|
16
17
|
name: rack
|
17
18
|
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
19
|
requirements:
|
20
|
-
- -
|
20
|
+
- - ">="
|
21
21
|
- !ruby/object:Gem::Version
|
22
22
|
version: '0'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
26
|
requirements:
|
28
|
-
- -
|
27
|
+
- - ">="
|
29
28
|
- !ruby/object:Gem::Version
|
30
29
|
version: '0'
|
31
30
|
- !ruby/object:Gem::Dependency
|
32
31
|
name: json
|
33
32
|
requirement: !ruby/object:Gem::Requirement
|
34
|
-
none: false
|
35
33
|
requirements:
|
36
|
-
- -
|
34
|
+
- - ">="
|
37
35
|
- !ruby/object:Gem::Version
|
38
36
|
version: '0'
|
39
37
|
type: :runtime
|
40
38
|
prerelease: false
|
41
39
|
version_requirements: !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
40
|
requirements:
|
44
|
-
- -
|
41
|
+
- - ">="
|
45
42
|
- !ruby/object:Gem::Version
|
46
43
|
version: '0'
|
47
44
|
- !ruby/object:Gem::Dependency
|
48
45
|
name: simplecov
|
49
46
|
requirement: !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
47
|
requirements:
|
52
|
-
- -
|
48
|
+
- - ">="
|
53
49
|
- !ruby/object:Gem::Version
|
54
50
|
version: '0'
|
55
51
|
type: :development
|
56
52
|
prerelease: false
|
57
53
|
version_requirements: !ruby/object:Gem::Requirement
|
58
|
-
none: false
|
59
54
|
requirements:
|
60
|
-
- -
|
55
|
+
- - ">="
|
61
56
|
- !ruby/object:Gem::Version
|
62
57
|
version: '0'
|
63
58
|
- !ruby/object:Gem::Dependency
|
64
59
|
name: rake
|
65
60
|
requirement: !ruby/object:Gem::Requirement
|
66
|
-
none: false
|
67
61
|
requirements:
|
68
|
-
- -
|
62
|
+
- - ">="
|
69
63
|
- !ruby/object:Gem::Version
|
70
64
|
version: '0'
|
71
65
|
type: :development
|
72
66
|
prerelease: false
|
73
67
|
version_requirements: !ruby/object:Gem::Requirement
|
74
|
-
none: false
|
75
68
|
requirements:
|
76
|
-
- -
|
69
|
+
- - ">="
|
77
70
|
- !ruby/object:Gem::Version
|
78
71
|
version: '0'
|
79
72
|
- !ruby/object:Gem::Dependency
|
80
73
|
name: rack-test
|
81
74
|
requirement: !ruby/object:Gem::Requirement
|
82
|
-
none: false
|
83
75
|
requirements:
|
84
|
-
- -
|
76
|
+
- - ">="
|
85
77
|
- !ruby/object:Gem::Version
|
86
78
|
version: '0'
|
87
79
|
type: :development
|
88
80
|
prerelease: false
|
89
81
|
version_requirements: !ruby/object:Gem::Requirement
|
90
|
-
none: false
|
91
82
|
requirements:
|
92
|
-
- -
|
83
|
+
- - ">="
|
93
84
|
- !ruby/object:Gem::Version
|
94
85
|
version: '0'
|
95
86
|
- !ruby/object:Gem::Dependency
|
96
87
|
name: rspec
|
97
88
|
requirement: !ruby/object:Gem::Requirement
|
98
|
-
none: false
|
99
89
|
requirements:
|
100
|
-
- -
|
90
|
+
- - ">="
|
101
91
|
- !ruby/object:Gem::Version
|
102
92
|
version: '0'
|
103
93
|
type: :development
|
104
94
|
prerelease: false
|
105
95
|
version_requirements: !ruby/object:Gem::Requirement
|
106
|
-
none: false
|
107
96
|
requirements:
|
108
|
-
- -
|
97
|
+
- - ">="
|
109
98
|
- !ruby/object:Gem::Version
|
110
99
|
version: '0'
|
111
100
|
description: A generalized Rack middleware for importing contacts from major email
|
@@ -117,7 +106,7 @@ executables: []
|
|
117
106
|
extensions: []
|
118
107
|
extra_rdoc_files: []
|
119
108
|
files:
|
120
|
-
- .gitignore
|
109
|
+
- ".gitignore"
|
121
110
|
- Gemfile
|
122
111
|
- Gemfile.lock
|
123
112
|
- README.md
|
@@ -132,6 +121,7 @@ files:
|
|
132
121
|
- lib/omnicontacts/importer/gmail.rb
|
133
122
|
- lib/omnicontacts/importer/hotmail.rb
|
134
123
|
- lib/omnicontacts/importer/linkedin.rb
|
124
|
+
- lib/omnicontacts/importer/outlook.rb
|
135
125
|
- lib/omnicontacts/importer/yahoo.rb
|
136
126
|
- lib/omnicontacts/integration_test.rb
|
137
127
|
- lib/omnicontacts/middleware/base_oauth.rb
|
@@ -146,6 +136,7 @@ files:
|
|
146
136
|
- spec/omnicontacts/importer/gmail_spec.rb
|
147
137
|
- spec/omnicontacts/importer/hotmail_spec.rb
|
148
138
|
- spec/omnicontacts/importer/linkedin_spec.rb
|
139
|
+
- spec/omnicontacts/importer/outlook_spec.rb
|
149
140
|
- spec/omnicontacts/importer/yahoo_spec.rb
|
150
141
|
- spec/omnicontacts/integration_test_spec.rb
|
151
142
|
- spec/omnicontacts/middleware/base_oauth_spec.rb
|
@@ -155,26 +146,25 @@ files:
|
|
155
146
|
- spec/spec_helper.rb
|
156
147
|
homepage: http://github.com/Diego81/omnicontacts
|
157
148
|
licenses: []
|
149
|
+
metadata: {}
|
158
150
|
post_install_message:
|
159
151
|
rdoc_options: []
|
160
152
|
require_paths:
|
161
153
|
- lib
|
162
154
|
required_ruby_version: !ruby/object:Gem::Requirement
|
163
|
-
none: false
|
164
155
|
requirements:
|
165
|
-
- -
|
156
|
+
- - ">="
|
166
157
|
- !ruby/object:Gem::Version
|
167
158
|
version: '0'
|
168
159
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
|
-
none: false
|
170
160
|
requirements:
|
171
|
-
- -
|
161
|
+
- - ">="
|
172
162
|
- !ruby/object:Gem::Version
|
173
163
|
version: 1.3.6
|
174
164
|
requirements: []
|
175
165
|
rubyforge_project:
|
176
|
-
rubygems_version:
|
166
|
+
rubygems_version: 2.7.4
|
177
167
|
signing_key:
|
178
|
-
specification_version:
|
168
|
+
specification_version: 4
|
179
169
|
summary: A generalized Rack middleware for importing contacts from major email providers.
|
180
170
|
test_files: []
|