Repository: Diego81/omnicontacts
Branch: master
Commit: 105b40f79cac
Files: 37
Total size: 131.9 KB
Directory structure:
gitextract_m1xfdr8k/
├── .gitignore
├── Gemfile
├── README.md
├── Rakefile
├── lib/
│ ├── omnicontacts/
│ │ ├── authorization/
│ │ │ ├── oauth1.rb
│ │ │ └── oauth2.rb
│ │ ├── builder.rb
│ │ ├── http_utils.rb
│ │ ├── importer/
│ │ │ ├── facebook.rb
│ │ │ ├── gmail.rb
│ │ │ ├── hotmail.rb
│ │ │ ├── linkedin.rb
│ │ │ ├── outlook.rb
│ │ │ └── yahoo.rb
│ │ ├── importer.rb
│ │ ├── integration_test.rb
│ │ ├── middleware/
│ │ │ ├── base_oauth.rb
│ │ │ ├── oauth1.rb
│ │ │ └── oauth2.rb
│ │ └── parse_utils.rb
│ └── omnicontacts.rb
├── omnicontacts.gemspec
└── spec/
├── omnicontacts/
│ ├── authorization/
│ │ ├── oauth1_spec.rb
│ │ └── oauth2_spec.rb
│ ├── http_utils_spec.rb
│ ├── importer/
│ │ ├── facebook_spec.rb
│ │ ├── gmail_spec.rb
│ │ ├── hotmail_spec.rb
│ │ ├── linkedin_spec.rb
│ │ ├── outlook_spec.rb
│ │ └── yahoo_spec.rb
│ ├── integration_test_spec.rb
│ ├── middleware/
│ │ ├── base_oauth_spec.rb
│ │ ├── oauth1_spec.rb
│ │ └── oauth2_spec.rb
│ └── parse_utils_spec.rb
└── spec_helper.rb
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
coverage
*.iml
.idea
.rvmrc
.DS_Store
nbproject
================================================
FILE: Gemfile
================================================
source 'http://rubygems.org'
gemspec
================================================
FILE: README.md
================================================
# OmniContacts
Inspired by the popular OmniAuth, OmniContacts is a library that enables users of an application to import contacts
from their email or Facebook accounts. The email providers currently supported are Gmail, Yahoo and Hotmail.
OmniContacts is a Rack middleware, therefore you can use it with Rails, Sinatra and any other Rack-based framework.
OmniContacts uses the OAuth protocol to communicate with the contacts provider. Yahoo still uses OAuth 1.0, while
Facebook, Gmail and Hotmail support OAuth 2.0.
In order to use OmniContacts, it is therefore necessary to first register your application with the provider and to obtain client_id and client_secret.
## Contribute!
Me (rubytastic) and the orginal author Diego don't actively use this code at the moment, anyone interested in maintaining and contributing to this codebase please write me up in a personal message ( rubytastic )
I try to merge pull requests in every once and a while but this code would benefit from someone actively use and contribute to it.
## Gem build updates
There is now a new gem build out which should address many issues people had when posting on the issue tracker. Please update to the latest GEM version if you have problems before posting new issues.
## Usage
Add OmniContacts as a dependency:
```ruby
gem "omnicontacts"
```
As for OmniAuth, there is a Builder facilitating the usage of multiple contacts importers. In the case of a Rails application, the following code could be placed at `config/initializers/omnicontacts.rb`:
```ruby
require "omnicontacts"
Rails.application.middleware.use OmniContacts::Builder do
importer :gmail, "client_id", "client_secret", {:redirect_path => "/oauth2callback", :ssl_ca_file => "/etc/ssl/certs/curl-ca-bundle.crt"}
importer :yahoo, "consumer_id", "consumer_secret", {:callback_path => "/callback"}
importer :linkedin, "consumer_id", "consumer_secret", {:redirect_path => "/oauth2callback", :state => '<long_unique_string_value>'}
importer :hotmail, "client_id", "client_secret"
importer :outlook, "app_id", "app_secret"
importer :facebook, "client_id", "client_secret"
end
```
Every importer expects `client_id` and `client_secret` as mandatory, while `:redirect_path` and `:ssl_ca_file` are optional (except linkedin - `state` arg mandatory).
Since Yahoo implements the version 1.0 of the OAuth protocol, naming is slightly different. Instead of `:redirect_path` you should use `:callback_path` as key in the hash providing the optional parameters.
While `:ssl_ca_file` is optional, it is highly recommended to set it on production environments for obvious security reasons.
On the other hand it makes things much easier to leave the default value for `:redirect_path` and `:callback path`, the reason of which will be clear after reading the following section.
## Register your application
* For Gmail : [Google API Console](https://code.google.com/apis/console/)
* For Yahoo : [Yahoo Developer Network](https://developer.yahoo.com/social/contacts/)
* For Hotmail : [Microsoft Developer Network](https://account.live.com/developers/applications/index)
* For Outlook : [Microsoft Application Registration Portal](https://apps.dev.microsoft.com/)
* For Facebook : [Facebook Developers](https://developers.facebook.com/apps)
* For Linkedin : [Linkedin Developer Network](https://www.linkedin.com/secure/developer)
##### Note:
Please go through [MSDN](http://msdn.microsoft.com/en-us/library/cc287659.aspx) if above Hotmail link will not work.
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).
## Integrating with your Application
To use the Gem you first need to redirect your users to `/contacts/:importer`, where `:importer` can be facebook, gmail, yahoo or hotmail.
No changes to `config/routes.rb` are needed for this step since OmniContacts will be listening on that path and redirect the user to the email provider's website in order to authorize your app to access his contact list.
Once that is done the user will be redirected back to your application, to the path specified in `:redirect_path` (or `:callback_path` for yahoo).
If nothing is specified the default value is `/contacts/:importer/callback` (e.g. `/contacts/yahoo/callback`). This makes things simpler and you can just add the following line to `config/routes.rb`:
```ruby
match "/contacts/:importer/callback" => "your_controller#callback"
```
The list of contacts can be accessed via the `omnicontacts.contacts` key in the environment hash and it consists of a simple array of hashes.
The following table shows which fields are supported by which provider:
<table>
<tr>
<th>Provider</th>
<th>:email</th>
<th>:id</th>
<th>:profile_picture</th>
<th>:name</th>
<th>:first_name</th>
<th>:last_name</th>
<th>:address_1</th>
<th>:address_2</th>
<th>:city</th>
<th>:region</th>
<th>:postcode</th>
<th>:country</th>
<th>:phone_number</th>
<th>:birthday</th>
<th>:gender</th>
<th>:relation</th>
</tr>
<tr>
<td>Gmail</td>
<td>X</td>
<td>X</td>
<td></td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td>Facebook</td>
<td></td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td>Yahoo</td>
<td>X</td>
<td>X</td>
<td></td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td></td>
<td>X</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Hotmail</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>X</td>
<td></td>
</tr>
<tr>
<td>Outlook</td>
<td>X</td>
<td>X</td>
<td></td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Linkedin</td>
<td></td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<tr>
</table>
Obviously it may happen that some fields are blank even if supported by the provider in the case that the contact did not provide any information about them.
The information for the logged in user can also be accessed via 'omnicontacts.user' key in the environment hash. It consists of a simple hash which includes the same fields as above.
The following snippet shows how to simply print name and email of each contact, and also the the name of logged in user:
```ruby
def contacts_callback
@contacts = request.env['omnicontacts.contacts']
@user = request.env['omnicontacts.user']
puts "List of contacts of #{@user[:name]} obtained from #{params[:importer]}:"
@contacts.each do |contact|
puts "Contact found: name => #{contact[:name]}, email => #{contact[:email]}"
end
end
```
If the user does not authorize your application to access his/her contacts list, or any other inconvenience occurs, he/she is redirected to `/contacts/failure`. The query string will contain a parameter named `error_message` which specifies why the list of contacts could not be retrieved. `error_message` can have one of the following values: `not_authorized`, `timeout` and `internal_error`.
## Tips and tricks
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.
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.
To configure the max number of contacts to download from Gmail, just add a max results parameter in your initializer:
```ruby
importer :gmail, "xxx", "yyy", :max_results => 1000
```
Yahoo requires you to configure the Permissions your application requires. Make sure to go the Yahoo website and to select Read permission for Contacts.
Hotmail presents a "peculiar" feature. Their API returns a Contact object which does not contain an e-mail field!
However, if the contact has either name, family name or both set to null, than there is a field called name which does contain the e-mail address.
This means that it may happen that an Hotmail contact does not contain the email field.
## Integration Testing
You can enable test mode like this:
```ruby
OmniContacts.integration_test.enabled = true
```
In this way all requests to `/omnicontacts/provider` will be redirected automatically to `/omnicontacts/provider/callback`.
The `mock` method allows to configure per-provider the result to return:
```ruby
OmniContacts.integration_test.mock(:provider_name, :email => "user@example.com")
```
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`
You can also pass a user to fill `omnicontacts.user` (optional)
```ruby
OmniContacts.integration_test.mock(:provider_name, {:email => "contact@example.com"}, {:email => "user@example.com"})
```
Follows a full example of an integration test:
```ruby
OmniContacts.integration_test.enabled = true
OmniContacts.integration_test.mock(:gmail, :email => "user@example.com")
visit '/contacts/gmail'
page.should have_content("user@example.com")
```
## Working on localhost
Since Hotmail and Facebook do not allow the usage of `localhost` as redirect path for the authorization step, a workaround is to use `ngrok`.
This is really useful when you need someone, the contacts provider in this case, to access your locally running application using a unique url.
Install ngrok, download from:
https://ngrok.com/
https://github.com/inconshreveable/ngrok
Unzip the file
```bash
unzip /place/this/is/ngrok.zip
```
Start your application
```bash
$ rails server
=> Booting WEBrick
=> Rails 4.0.4 application starting in development on http://0.0.0.0:3000
```
In a new terminal window, start the tunnel and pass the port where your application is running:
```bash
./ngrok 3000
```
Check the output to see something like
```bash
ngrok (Ctrl+C to quit)
Tunnel Status online
Version 1.6/1.5
Forwarding http://274101c1e.ngrok.com -> 127.0.0.1:3000
Forwarding https://274101c1e.ngrok.com -> 127.0.0.1:3000
Web Interface 127.0.0.1:4040
# Conn 0
Avg Conn Time 0.00ms
```
This window will show all network transaction that your locally hosted application is processing.
Ngrok will process all of the requests and responses on your localhost. Visit:
```bash
http://123456789.ngrok.com # replace 123456789 with your instance
```
## Example application
Thanks to @sonianand11, you can find a full example of a Rails application using OmniContacts at: https://github.com/sonianand11/omnicontacts_example
## Thanks
As already mentioned above, a special thanks goes to @sonianand11 for implementing an example app.
Thanks also to @asmatameem for her huge contribution. She indeed added support for Facebook and for many fields which were missing before.
## License
Copyright (c) 2012-2013 Diego81
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
================================================
FILE: Rakefile
================================================
require 'bundler'
require 'rspec/core/rake_task'
Bundler::GemHelper.install_tasks
RSpec::Core::RakeTask.new(:spec)
task :default => :spec
task :test => :spec
================================================
FILE: lib/omnicontacts/authorization/oauth1.rb
================================================
require "omnicontacts/http_utils"
require "base64"
# This module represent a OAuth 1.0 Client.
#
# Classes including the module must implement
# the following methods:
# * auth_host -> the host of the authorization server
# * auth_token_path -> the path to query to obtain a request token
# * consumer_key -> the registered consumer key of the client
# * consumer_secret -> the registered consumer secret of the client
# * callback -> the callback to include during the redirection step
# * auth_path -> the path on the authorization server to redirect the user to
# * access_token_path -> the path to query in order to obtain the access token
module OmniContacts
module Authorization
module OAuth1
include HTTPUtils
OAUTH_VERSION = "1.0"
# Obtain an authorization token from the server.
# The token is returned in an array along with the relative authorization token secret.
def fetch_authorization_token
request_token_response = https_post(auth_host, auth_token_path, request_token_req_params)
values_from_query_string(request_token_response, ["oauth_token", "oauth_token_secret"])
end
private
def request_token_req_params
{
:oauth_consumer_key => consumer_key,
:oauth_nonce => encode(random_string),
:oauth_signature_method => "PLAINTEXT",
:oauth_signature => encode(consumer_secret + "&"),
:oauth_timestamp => timestamp,
:oauth_version => OAUTH_VERSION,
:oauth_callback => callback
}
end
def random_string
(0...50).map { ('a'..'z').to_a[rand(26)] }.join
end
def timestamp
Time.now.to_i.to_s
end
def values_from_query_string query_string, keys_to_extract
map = query_string_to_map(query_string)
keys_to_extract.collect do |key|
if map.has_key?(key)
map[key]
else
raise "No value found for #{key} in #{query_string}"
end
end
end
public
# Returns the url the user has to be redirected to do in order grant permission to the client application.
def authorization_url auth_token
"https://" + auth_host + auth_path + "?oauth_token=" + auth_token
end
# Fetches the access token from the authorization server.
# The method expects the authorization token, the authorization token secret and the authorization verifier.
# The result comprises the access token, the access token secret and a list of additional fields extracted from the server's response.
# The list of additional fields to extract is specified as last parameter
def fetch_access_token auth_token, auth_token_secret, auth_verifier, additional_fields_to_extract = []
access_token_resp = https_post(auth_host, access_token_path, access_token_req_params(auth_token, auth_token_secret, auth_verifier))
values_from_query_string(access_token_resp, (["oauth_token", "oauth_token_secret"] + additional_fields_to_extract))
end
private
def access_token_req_params auth_token, auth_token_secret, auth_verifier
{
:oauth_consumer_key => consumer_key,
:oauth_nonce => encode(random_string),
:oauth_signature_method => "PLAINTEXT",
:oauth_signature => encode(consumer_secret + "&" + auth_token_secret),
:oauth_version => OAUTH_VERSION,
:oauth_timestamp => timestamp,
:oauth_token => auth_token,
:oauth_verifier => auth_verifier
}
end
public
# Calculates a signature using HMAC-SHA1 according to the OAuth 1.0 specifications.
#
# The base string is given is a RFC 3986 encoded concatenation of:
# * Uppercase HTTP method
# * An '&'
# * A url without any parameters
# * An '&'
# * All parameters to use in the request encoded themselves and sorted by key.
#
# The signature key is given by the concatenation of:
# * RFC 3986 encoded consumer secret
# * An '&'
# * RFC 3986 encoded token secret
def oauth_signature method, url, params, secret
encoded_method = encode(method.upcase)
encoded_url = encode(url)
# params must be in alphabetical order
encoded_params = encode(to_query_string(params.sort { |x, y| x.to_s <=> y.to_s }))
base_string = encoded_method + '&' + encoded_url + '&' + encoded_params
key = encode(consumer_secret) + '&' + secret
hmac_sha1 = OpenSSL::HMAC.digest('sha1', key, base_string)
# base64 encode results must be stripped
encode(Base64.encode64(hmac_sha1).strip)
end
end
end
end
================================================
FILE: lib/omnicontacts/authorization/oauth2.rb
================================================
require "omnicontacts/http_utils"
require "json"
# This module represents an OAuth 2.0 client.
#
# Classes including the module must implement
# the following methods:
# * auth_host -> the host of the authorization server
# * authorize_path -> the path on the authorization server the redirect the use to
# * client_id -> the registered client id of the client
# * client_secret -> the registered client secret of the client
# * redirect_path -> the path the authorization server has to redirect the user back after authorization
# * auth_token_path -> the path to query once the user has granted permission to the application
# * scope -> the scope necessary to acquire the contacts list.
module OmniContacts
module Authorization
module OAuth2
include HTTPUtils
# Calculates the URL the user has to be redirected to in order to authorize
# the application to access his contacts list.
def authorization_url
"https://" + auth_host + authorize_path + "?" + authorize_url_params
end
private
def authorize_url_params
to_query_string({
:client_id => client_id,
:scope => encode(scope),
:response_type => "code",
:access_type => "online",
:approval_prompt => "auto",
:redirect_uri => encode(redirect_uri)
})
end
public
# Fetches the access token from the authorization server using the given authorization code.
def fetch_access_token code
access_token_from_response https_post(auth_host, auth_token_path, token_req_params(code))
end
private
def token_req_params code
{
:client_id => client_id,
:client_secret => client_secret,
:code => code,
:redirect_uri => encode(redirect_uri),
:grant_type => "authorization_code"
}
end
def access_token_from_response response
if auth_host == "graph.facebook.com"
response = query_string_to_map(response).to_json
end
json = JSON.parse(response)
raise json["error"] if json["error"]
[json["access_token"], json["token_type"], json["refresh_token"]]
end
public
# Refreshes the access token using the provided refresh_token.
def refresh_access_token refresh_token
access_token_from_response https_post(auth_host, auth_token_path, refresh_token_req_params(refresh_token))
end
private
def refresh_token_req_params refresh_token
{
:client_id => client_id,
:client_secret => client_secret,
:refresh_token => refresh_token,
:grant_type => "refresh_token"
}
end
end
end
end
================================================
FILE: lib/omnicontacts/builder.rb
================================================
require "omnicontacts"
module OmniContacts
class Builder < Rack::Builder
def initialize(app, &block)
if rack14?
super
else
@app = app
super(&block)
end
end
def rack14?
v = Rack.release.split('.')
v[0].to_i >= 1 || v[1].to_i >= 4
end
def importer importer, *args
middleware = OmniContacts::Importer.const_get(importer.to_s.capitalize)
use middleware, *args
rescue NameError
raise LoadError, "Could not find importer #{importer}."
end
def call env
@ins << @app unless rack14? || @ins.include?(@app)
to_app.call(env)
end
end
end
================================================
FILE: lib/omnicontacts/http_utils.rb
================================================
require "net/http"
require "net/https"
require "cgi"
require "openssl"
# This module contains a set of utility methods related to the HTTP protocol.
module OmniContacts
module HTTPUtils
SSL_PORT = 443
module_function
def query_string_to_map query_string
query_string.split('&').reduce({}) do |memo, key_value|
(key, value) = key_value.split('=')
memo[key]= value
memo
end
end
def to_query_string map
map.collect do |key, value|
key.to_s + "=" + value.to_s
end.join("&")
end
# Encodes the given input according to RFC 3986
def encode to_encode
CGI.escape(to_encode)
end
# Calculates the url of the host from a Rack environment.
# The result is in the form scheme://host:port
# If port is 80 the result is scheme://host
# According to Rack specification the HTTP_HOST variable is preferred over SERVER_NAME.
def host_url_from_rack_env env
port = ((env["SERVER_PORT"] == 80) && "") || ":#{env['SERVER_PORT']}"
host = (env["HTTP_HOST"]) || (env["SERVER_NAME"] + port)
"#{scheme(env)}://#{host}"
end
def scheme env
if env['HTTPS'] == 'on'
'https'
elsif env['HTTP_X_FORWARDED_SSL'] == 'on'
'https'
elsif env['HTTP_X_FORWARDED_PROTO']
env['HTTP_X_FORWARDED_PROTO'].split(',').first
else
env["rack.url_scheme"]
end
end
# Classes including the module must respond to the ssl_ca_file message in order to use the following methods.
# The response will be the path to the CA file to use when making https requests.
# If the result of ssl_ca_file is nil no file is used. In this case a warn message is logged.
private
# Executes an HTTP GET request.
# It raises a RuntimeError if the response code is not equal to 200
def http_get host, path, params
connection = Net::HTTP.new(host)
process_http_response connection.request_get(path + "?" + to_query_string(params))
end
# Executes an HTTP POST request over SSL
# It raises a RuntimeError if the response code is not equal to 200
def https_post host, path, params
https_connection host do |connection|
connection.request_post(path, to_query_string(params))
end
end
# Executes an HTTP GET request over SSL
# It raises a RuntimeError if the response code is not equal to 200
def https_get host, path, params, headers = nil
https_connection host do |connection|
connection.request_get(path + "?" + to_query_string(params), headers)
end
end
def https_connection (host)
connection = Net::HTTP.new(host, SSL_PORT)
connection.use_ssl = true
if ssl_ca_file
connection.ca_file = ssl_ca_file
else
logger << "No SSL ca file provided. It is highly reccomended to use one in production envinronments" if respond_to?(:logger) && logger
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
process_http_response(yield(connection))
end
def process_http_response response
raise response.body if response.code != "200"
response.body
end
end
end
================================================
FILE: lib/omnicontacts/importer/facebook.rb
================================================
require "omnicontacts/parse_utils"
require "omnicontacts/middleware/oauth2"
require "json"
module OmniContacts
module Importer
class Facebook < Middleware::OAuth2
include ParseUtils
attr_reader :auth_host, :authorize_path, :auth_token_path, :scope
def initialize *args
super *args
@auth_host = 'graph.facebook.com'
@authorize_path = '/oauth/authorize'
@scope = 'email,user_relationships,user_birthday,user_friends'
@auth_token_path = '/oauth/access_token'
@contacts_host = 'graph.facebook.com'
@friends_path = '/v2.5/me/friends'
@family_path = '/v2.5/me/family'
@self_path = '/v2.5/me'
end
def fetch_contacts_using_access_token access_token, access_token_secret
self_response = fetch_current_user access_token
user = current_user self_response
set_current_user user
spouse_id = extract_spouse_id self_response
spouse_response = nil
if spouse_id
spouse_path = "/#{spouse_id}"
spouse_response = https_get(@contacts_host, spouse_path, {:access_token => access_token, :fields => 'first_name,last_name,name,id,gender,birthday,picture'})
end
family_response = https_get(@contacts_host, @family_path, {:access_token => access_token, :fields => 'first_name,last_name,name,id,gender,birthday,picture,relationship'})
friends_response = https_get(@contacts_host, @friends_path, {:access_token => access_token, :fields => 'first_name,last_name,name,id,gender,birthday,picture'})
contacts_from_response(spouse_response, family_response, friends_response)
end
def fetch_current_user access_token
self_response = https_get(@contacts_host, @self_path, {:access_token => access_token, :fields => 'first_name,last_name,name,id,gender,birthday,picture,relationship_status,significant_other,email'})
self_response = JSON.parse(self_response) if self_response
self_response
end
private
def extract_spouse_id response
return nil if response.nil?
id = nil
if response['significant_other'] && response['relationship_status'] == 'Married'
id = response['significant_other']['id']
end
id
end
def contacts_from_response(spouse_response, family_response, friends_response)
contacts = []
family_ids = Set.new
if spouse_response
spouse_contact = create_contact_element(JSON.parse(spouse_response))
spouse_contact[:relation] = 'spouse'
contacts << spouse_contact
family_ids.add(spouse_contact[:id])
end
if family_response
family_response = JSON.parse(family_response)
family_response['data'].each do |family_contact|
contacts << create_contact_element(family_contact)
family_ids.add(family_contact['id'])
end
end
if friends_response
friends_response = JSON.parse(friends_response)
friends_response['data'].each do |friends_contact|
contacts << create_contact_element(friends_contact) unless family_ids.include?(friends_contact['id'])
end
end
contacts
end
def create_contact_element contact_info
# creating nil fields to keep the fields consistent across other networks
contact = {:id => nil, :first_name => nil, :last_name => nil, :name => nil, :email => nil, :gender => nil, :birthday => nil, :profile_picture=> nil, :relation => nil}
contact[:id] = contact_info['id']
contact[:first_name] = normalize_name(contact_info['first_name'])
contact[:last_name] = normalize_name(contact_info['last_name'])
contact[:name] = contact_info['name']
contact[:email] = contact_info['email']
contact[:gender] = contact_info['gender']
contact[:birthday] = birthday(contact_info['birthday'])
contact[:profile_picture] = image_url(contact_info['id'])
contact[:relation] = contact_info['relationship']
contact
end
def image_url fb_id
return "https://graph.facebook.com/" + fb_id + "/picture" if fb_id
end
def escape_windows_format value
value.gsub(/[\r\s]/, '')
end
def birthday dob
return nil if dob.nil?
birthday = dob.split('/')
return birthday_format(birthday[0],birthday[1],birthday[2])
end
def current_user me
return nil if me.nil?
user = {:id => me['id'], :email => me['email'],
:name => me['name'], :first_name => normalize_name(me['first_name']),
:last_name => normalize_name(me['last_name']), :birthday => birthday(me['birthday']),
:gender => me['gender'], :profile_picture => image_url(me['id'])
}
user
end
end
end
end
================================================
FILE: lib/omnicontacts/importer/gmail.rb
================================================
require "omnicontacts/parse_utils"
require "omnicontacts/middleware/oauth2"
module OmniContacts
module Importer
class Gmail < Middleware::OAuth2
include ParseUtils
attr_reader :auth_host, :authorize_path, :auth_token_path, :scope
def initialize *args
super *args
@auth_host = "accounts.google.com"
@authorize_path = "/o/oauth2/auth"
@auth_token_path = "/o/oauth2/token"
@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"
@contacts_host = "www.google.com"
@contacts_path = "/m8/feeds/contacts/default/full"
@max_results = (args[3] && args[3][:max_results]) || 100
@self_host = "www.googleapis.com"
@profile_path = "/oauth2/v3/userinfo"
end
def fetch_contacts_using_access_token access_token, token_type
fetch_current_user(access_token, token_type)
contacts_response = https_get(@contacts_host, @contacts_path, contacts_req_params, contacts_req_headers(access_token, token_type))
contacts_from_response(contacts_response, access_token)
end
def fetch_current_user access_token, token_type
self_response = https_get(@self_host, @profile_path, contacts_req_params, contacts_req_headers(access_token, token_type))
user = current_user(self_response, access_token, token_type)
set_current_user user
end
private
def contacts_req_params
{'max-results' => @max_results.to_s, 'alt' => 'json'}
end
def contacts_req_headers token, token_type
{"GData-Version" => "3.0", "Authorization" => "#{token_type} #{token}"}
end
def contacts_from_response(response_as_json, access_token)
response = JSON.parse(response_as_json)
return [] if response['feed'].nil? || response['feed']['entry'].nil?
contacts = []
return contacts if response.nil?
response['feed']['entry'].each do |entry|
# creating nil fields to keep the fields consistent across other networks
contact = { :id => nil,
:first_name => nil,
:last_name => nil,
:name => nil,
:emails => nil,
:gender => nil,
:birthday => nil,
:profile_picture=> nil,
:relation => nil,
:addresses => nil,
:phone_numbers => nil,
:dates => nil,
:company => nil,
:position => nil
}
contact[:id] = entry['id']['$t'] if entry['id']
if entry['gd$name']
gd_name = entry['gd$name']
contact[:first_name] = normalize_name(entry['gd$name']['gd$givenName']['$t']) if gd_name['gd$givenName']
contact[:last_name] = normalize_name(entry['gd$name']['gd$familyName']['$t']) if gd_name['gd$familyName']
contact[:name] = normalize_name(entry['gd$name']['gd$fullName']['$t']) if gd_name['gd$fullName']
contact[:name] = full_name(contact[:first_name],contact[:last_name]) if contact[:name].nil?
end
contact[:emails] = []
entry['gd$email'].each do |email|
if email['rel']
split_index = email['rel'].index('#')
contact[:emails] << {:name => email['rel'][split_index + 1, email['rel'].length - 1], :email => email['address']}
elsif email['label']
contact[:emails] << {:name => email['label'], :email => email['address']}
end
end if entry['gd$email']
# Support older versions of the gem by keeping singular entries around
contact[:email] = contact[:emails][0][:email] if contact[:emails][0]
contact[:first_name], contact[:last_name], contact[:name] = email_to_name(contact[:name]) if !contact[:name].nil? && contact[:name].include?('@')
contact[:first_name], contact[:last_name], contact[:name] = email_to_name(contact[:emails][0][:email]) if (contact[:name].nil? && contact[:emails][0] && contact[:emails][0][:email])
#format - year-month-date
contact[:birthday] = birthday(entry['gContact$birthday']['when']) if entry['gContact$birthday']
# value is either "male" or "female"
contact[:gender] = entry['gContact$gender']['value'] if entry['gContact$gender']
if entry['gContact$relation']
if entry['gContact$relation'].is_a?(Hash)
contact[:relation] = entry['gContact$relation']['rel']
elsif entry['gContact$relation'].is_a?(Array)
contact[:relation] = entry['gContact$relation'].first['rel']
end
end
contact[:addresses] = []
entry['gd$structuredPostalAddress'].each do |address|
if address['rel']
split_index = address['rel'].index('#')
new_address = {:name => address['rel'][split_index + 1, address['rel'].length - 1]}
elsif address['label']
new_address = {:name => address['label']}
end
new_address[:address_1] = address['gd$street']['$t'] if address['gd$street']
new_address[:address_1] = address['gd$formattedAddress']['$t'] if new_address[:address_1].nil? && address['gd$formattedAddress']
if !new_address[:address_1].nil? && new_address[:address_1].index("\n")
parts = new_address[:address_1].split("\n")
new_address[:address_1] = parts.first
# this may contain city/state/zip if user jammed it all into one string.... :-(
new_address[:address_2] = parts[1..-1].join(', ')
end
new_address[:city] = address['gd$city']['$t'] if address['gd$city']
new_address[:region] = address['gd$region']['$t'] if address['gd$region'] # like state or province
new_address[:country] = address['gd$country']['code'] if address['gd$country']
new_address[:postcode] = address['gd$postcode']['$t'] if address['gd$postcode']
contact[:addresses] << new_address
end if entry['gd$structuredPostalAddress']
# Support older versions of the gem by keeping singular entries around
if contact[:addresses][0]
contact[:address_1] = contact[:addresses][0][:address_1]
contact[:address_2] = contact[:addresses][0][:address_2]
contact[:city] = contact[:addresses][0][:city]
contact[:region] = contact[:addresses][0][:region]
contact[:country] = contact[:addresses][0][:country]
contact[:postcode] = contact[:addresses][0][:postcode]
end
contact[:phone_numbers] = []
entry['gd$phoneNumber'].each do |phone_number|
if phone_number['rel']
split_index = phone_number['rel'].index('#')
contact[:phone_numbers] << {:name => phone_number['rel'][split_index + 1, phone_number['rel'].length - 1], :number => phone_number['$t']}
elsif phone_number['label']
contact[:phone_numbers] << {:name => phone_number['label'], :number => phone_number['$t']}
end
end if entry['gd$phoneNumber']
# Support older versions of the gem by keeping singular entries around
contact[:phone_number] = contact[:phone_numbers][0][:number] if contact[:phone_numbers][0]
if entry["link"] && entry["link"].is_a?(Array)
entry["link"].each do |link|
if link["type"] == 'image/*' && link["gd$etag"]
contact[:profile_picture] = link["href"] + "?&access_token=" + access_token
break
end
end
end
if entry['gContact$event']
contact[:dates] = []
entry['gContact$event'].each do |event|
if event['rel']
contact[:dates] << {:name => event['rel'], :date => birthday(event['gd$when']['startTime'])}
elsif event['label']
contact[:dates] << {:name => event['label'], :date => birthday(event['gd$when']['startTime'])}
end
end
end
if entry['gd$organization']
contact[:company] = entry['gd$organization'][0]['gd$orgName']['$t'] if entry['gd$organization'][0]['gd$orgName']
contact[:position] = entry['gd$organization'][0]['gd$orgTitle']['$t'] if entry['gd$organization'][0]['gd$orgTitle']
end
contacts << contact if contact[:name]
end
contacts.uniq! {|c| c[:email] || c[:profile_picture] || c[:name]}
contacts
end
def current_user me, access_token, token_type
return nil if me.nil?
me = JSON.parse(me)
user = {:id => me['id'], :email => me['email'], :name => me['name'], :first_name => me['given_name'],
:last_name => me['family_name'], :gender => me['gender'], :birthday => birthday(me['birthday']), :profile_picture => me["picture"],
:access_token => access_token, :token_type => token_type
}
user
end
def birthday dob
return nil if dob.nil?
birthday = dob.split('-')
return birthday_format(birthday[2], birthday[3], nil) if birthday.size == 4
return birthday_format(birthday[1], birthday[2], birthday[0]) if birthday.size == 3
end
def contact_id(profile_url)
id = (profile_url.present?) ? File.basename(profile_url) : nil
id
end
end
end
end
================================================
FILE: lib/omnicontacts/importer/hotmail.rb
================================================
require "omnicontacts/middleware/oauth2"
require "omnicontacts/parse_utils"
require "json"
module OmniContacts
module Importer
class Hotmail < Middleware::OAuth2
include ParseUtils
attr_reader :auth_host, :authorize_path, :auth_token_path, :scope
def initialize app, client_id, client_secret, options ={}
super app, client_id, client_secret, options
@auth_host = "login.live.com"
@authorize_path = "/oauth20_authorize.srf"
@scope = options[:permissions] || "wl.signin, wl.basic, wl.birthday , wl.emails ,wl.contacts_birthday , wl.contacts_photos, wl.contacts_emails"
@auth_token_path = "/oauth20_token.srf"
@contacts_host = "apis.live.net"
@contacts_path = "/v5.0/me/contacts"
@self_path = "/v5.0/me"
end
def fetch_contacts_using_access_token access_token, access_token_secret
fetch_current_user(access_token)
contacts_response = https_get(@contacts_host, @contacts_path, :access_token => access_token)
contacts_from_response contacts_response
end
def fetch_current_user access_token
self_response = https_get(@contacts_host, @self_path, :access_token => access_token)
user = current_user self_response
set_current_user user
end
private
def contacts_from_response response_as_json
response = JSON.parse(response_as_json)
contacts = []
response['data'].each do |entry|
# creating nil fields to keep the fields consistent across other networks
contact = {:id => nil, :first_name => nil, :last_name => nil, :name => nil, :email => nil, :gender => nil, :birthday => nil, :profile_picture=> nil, :relation => nil, :email_hashes => []}
contact[:id] = entry['user_id'] ? entry['user_id'] : entry['id']
contact[:email] = parse_email(entry['emails']) if valid_email? parse_email(entry['emails'])
contact[:first_name] = normalize_name(entry['first_name'])
contact[:last_name] = normalize_name(entry['last_name'])
contact[:name] = normalize_name(entry['name'])
contact[:birthday] = birthday_format(entry['birth_month'], entry['birth_day'], entry['birth_year'])
contact[:gender] = entry['gender']
contact[:profile_picture] = image_url(entry['user_id'])
contact[:email_hashes] = entry['email_hashes']
contacts << contact if contact[:name] || contact[:first_name]
end
contacts
end
def parse_email(emails)
return nil if emails.nil?
emails['account'] || emails['preferred'] || emails['personal'] || emails['business'] || emails['other']
end
def current_user me
return nil if me.nil?
me = JSON.parse(me)
email = parse_email(me['emails'])
user = {:id => me['id'], :email => email, :name => me['name'], :first_name => me['first_name'],
:last_name => me['last_name'], :gender => me['gender'], :profile_picture => image_url(me['id']),
:birthday => birthday_format(me['birth_month'], me['birth_day'], me['birth_year'])
}
user
end
def image_url hotmail_id
return 'https://apis.live.net/v5.0/' + hotmail_id + '/picture' if hotmail_id
end
def escape_windows_format value
value.gsub(/[\r\s]/, '')
end
def valid_email? value
/\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/.match(value)
end
end
end
end
================================================
FILE: lib/omnicontacts/importer/linkedin.rb
================================================
require "omnicontacts/parse_utils"
require "omnicontacts/middleware/oauth2"
module OmniContacts
module Importer
class Linkedin < Middleware::OAuth2
include ParseUtils
attr_reader :auth_host, :authorize_path, :auth_token_path, :scope, :state
def initialize *args
super *args
@auth_host = "www.linkedin.com"
@authorize_path = "/uas/oauth2/authorization"
@auth_token_path = "/uas/oauth2/accessToken"
@scope = (args[3] && args[3][:scope]) || "r_network"
@contacts_host = "api.linkedin.com"
@contacts_path = "/v1/people/~/connections:(id,first-name,last-name,picture-url)"
@self_host = "www.linkedin.com"
@profile_path = "/oauth2/v1/userinfo"
@state = (args[3] && args[3][:state])
end
def fetch_contacts_using_access_token access_token, token_type
token_type = "Bearer" if token_type.nil?
contacts_response = https_get(@contacts_host, @contacts_path, contacts_req_params, contacts_req_headers(access_token, token_type))
contacts_from_response contacts_response
end
private
def contacts_req_params
{'format' => 'json'}
end
def contacts_req_headers token, token_type
{"Authorization" => "#{token_type} #{token}"}
end
def contacts_from_response response_as_json
response = JSON.parse(response_as_json)
return [] if response['values'].nil?
contacts = []
return contacts if response.nil?
response['values'].map do |entry|
{
id: entry['id'],
first_name: normalize_name(entry['firstName']),
last_name: normalize_name(entry['lastName']),
name: full_name(entry['firstName'],entry['lastName']),
profile_picture: entry['pictureUrl']
}
end
end
def authorize_url_params
# merge state param required by LinkedIn
_params = super
_params += "&" + to_query_string(state: @state)
end
end
end
end
================================================
FILE: lib/omnicontacts/importer/outlook.rb
================================================
require "omnicontacts/middleware/oauth2"
require "omnicontacts/parse_utils"
require "json"
# API Docs: https://msdn.microsoft.com/en-us/office/office365/api/api-catalog#Outlookcontacts
module OmniContacts
module Importer
class Outlook < Middleware::OAuth2
include ParseUtils
attr_reader :auth_host, :authorize_path, :auth_token_path, :scope
def initialize app, client_id, client_secret, options ={}
super app, client_id, client_secret, options
@auth_host = "login.microsoftonline.com"
@authorize_path = "/common/oauth2/v2.0/authorize"
@scope = options[:permissions] || "https://outlook.office.com/contacts.read"
@auth_token_path = "/common/oauth2/v2.0/token"
@contacts_host = "outlook.office.com"
@contacts_path = "/api/v2.0/me/contacts"
@self_path = "/api/v2.0/me"
end
def fetch_contacts_using_access_token access_token, token_type
fetch_current_user(access_token, token_type)
contacts_response = https_get(@contacts_host, @contacts_path, {}, contacts_req_headers(access_token, token_type))
contacts_from_response contacts_response
end
def fetch_current_user access_token, token_type
self_response = https_get(@contacts_host, @self_path, {}, contacts_req_headers(access_token, token_type))
user = current_user self_response
set_current_user user
end
private
def contacts_req_headers token, token_type
{ "Authorization" => "#{token_type} #{token}" }
end
def current_user me
return nil if me.nil?
me = JSON.parse(me)
name_splitted = me["DisplayName"].split(" ")
first_name = name_splitted.first
last_name = name_splitted.last if name_splitted.size > 1
user = empty_contact
user[:id] = me["Id"]
user[:email] = me["EmailAddress"]
user[:name] = me["DisplayName"]
user[:first_name] = normalize_name(first_name)
user[:last_name] = normalize_name(last_name)
user
end
def contacts_from_response response_as_json
response = JSON.parse(response_as_json)
contacts = []
response["value"].each do |entry|
contact = empty_contact
# Full fields reference:
# https://msdn.microsoft.com/office/office365/api/complex-types-for-mail-contacts-calendar#RESTAPIResourcesContact
contact[:id] = entry["Id"]
contact[:first_name] = entry["GivenName"]
contact[:last_name] = entry["Surname"]
contact[:name] = entry["DisplayName"]
contact[:email] = parse_email(entry["EmailAddresses"])
contact[:birthday] = birthday(entry["Birthday"])
address = [entry["HomeAddress"], entry["BusinessAddress"], entry["OtherAddress"]].reject(&:empty?).first
if address
contact[:address_1] = address["Street"]
contact[:city] = address["City"]
contact[:region] = address["State"]
contact[:postcode] = address["PostalCode"]
contact[:country] = address["CountryOrRegion"]
end
contacts << contact if contact[:name] || contact[:first_name]
end
contacts
end
def empty_contact
{ :id => nil, :first_name => nil, :last_name => nil, :name => nil, :email => nil,
:gender => nil, :birthday => nil, :profile_picture => nil, :address_1 => nil,
:address_2 => nil, :city => nil, :region => nil, :postcode => nil, :relation => nil }
end
def parse_email emails
return nil if emails.nil?
emails.map! { |email| email["Address"] }
emails.select! { |email| valid_email? email }
emails.first
end
def birthday dob
return nil if dob.nil?
birthday = dob[0..9].split("-")
birthday[0] = nil if birthday[0].to_i < 1900 # if year is not set it returns 1604
return birthday_format(birthday[1], birthday[2], birthday[0])
end
def valid_email? value
/\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/.match(value)
end
end
end
end
================================================
FILE: lib/omnicontacts/importer/yahoo.rb
================================================
require "omnicontacts/parse_utils"
require "omnicontacts/middleware/oauth1"
require "json"
module OmniContacts
module Importer
class Yahoo < Middleware::OAuth1
include ParseUtils
attr_reader :auth_host, :auth_token_path, :auth_path, :access_token_path
def initialize *args
super *args
@auth_host = 'api.login.yahoo.com'
@auth_token_path = '/oauth/v2/get_request_token'
@auth_path = '/oauth/v2/request_auth'
@access_token_path = '/oauth/v2/get_token'
@contacts_host = 'social.yahooapis.com'
end
def fetch_contacts_from_token_and_verifier auth_token, auth_token_secret, auth_verifier
(access_token, access_token_secret, guid) = fetch_access_token(auth_token, auth_token_secret, auth_verifier, ['xoauth_yahoo_guid'])
fetch_current_user(access_token, access_token_secret, guid)
contacts_path = "/v1/user/#{guid}/contacts"
contacts_response = https_get(@contacts_host, contacts_path, contacts_req_params(access_token, access_token_secret, contacts_path))
contacts_from_response contacts_response
end
def fetch_current_user access_token, access_token_secret, guid
self_path = "/v1/user/#{guid}/profile"
self_response = https_get(@contacts_host, self_path, contacts_req_params(access_token, access_token_secret, self_path))
user = current_user self_response
set_current_user user
end
private
def contacts_req_params access_token, access_token_secret, contacts_path
params = {
:format => 'json',
:oauth_consumer_key => consumer_key,
:oauth_nonce => encode(random_string),
:oauth_signature_method => 'HMAC-SHA1',
:oauth_timestamp => timestamp,
:oauth_token => access_token,
:oauth_version => OmniContacts::Authorization::OAuth1::OAUTH_VERSION
}
contacts_url = "https://#{@contacts_host}#{contacts_path}"
params['oauth_signature'] = oauth_signature('GET', contacts_url, params, access_token_secret)
params
end
def contacts_from_response response_as_json
response = JSON.parse(response_as_json)
contacts = []
return contacts unless response['contacts']['contact']
response['contacts']['contact'].each do |entry|
# creating nil fields to keep the fields consistent across other networks
contact = { :id => nil,
:first_name => nil,
:last_name => nil,
:name => nil,
:email => nil,
:gender => nil,
:birthday => nil,
:profile_picture=> nil,
:address_1 => nil,
:address_2 => nil,
:city => nil,
:region => nil,
:postcode => nil,
:relation => nil }
yahoo_id = nil
contact[:id] = entry['id'].to_s
entry['fields'].each do |field|
case field['type']
when 'name'
contact[:first_name] = normalize_name(field['value']['givenName'])
contact[:last_name] = normalize_name(field['value']['familyName'])
contact[:name] = full_name(contact[:first_name],contact[:last_name])
when 'email'
contact[:email] = field['value'] if field['type'] == 'email'
when 'yahooid'
yahoo_id = field['value']
when 'address'
value = field['value']
contact[:address_1] = street = value['street']
if street.index("\n")
parts = street.split("\n")
contact[:address_1] = parts.first
contact[:address_2] = parts[1..-1].join(', ')
end
contact[:city] = value['city']
contact[:region] = value['stateOrProvince']
contact[:postcode] = value['postalCode']
when 'birthday'
contact[:birthday] = birthday_format(field['value']['month'], field['value']['day'],field['value']['year'])
end
contact[:first_name], contact[:last_name], contact[:name] = email_to_name(contact[:email]) if contact[:name].nil? && contact[:email]
# contact[:first_name], contact[:last_name], contact[:name] = email_to_name(yahoo_id) if (yahoo_id && contact[:name].nil? && contact[:email].nil?)
if yahoo_id
contact[:profile_picture] = image_url(yahoo_id)
else
contact[:profile_picture] = image_url_from_email(contact[:email])
end
end
contacts << contact if contact[:name]
end
contacts.uniq! {|c| c[:email] || c[:profile_picture] || c[:name]}
contacts
end
def image_url yahoo_id
return 'https://img.msg.yahoo.com/avatar.php?yids=' + yahoo_id if yahoo_id
end
def parse_email(emails)
return nil if emails.nil?
email = nil
if emails.is_a?(Hash)
if emails.has_key?("primary")
email = emails['handle']
end
elsif emails.is_a?(Array)
emails.each do |e|
if e.has_key?('primary') && e['primary']
email = e['handle']
break
end
end
end
email
end
def birthday dob
return nil if dob.nil?
birthday = dob.split('/')
return birthday_format(birthday[0], birthday[1], birthday[3]) if birthday.size == 3
return birthday_format(birthday[0], birthday[1], nil) if birthday.size == 2
end
def gender g
return "female" if g == "F"
return "male" if g == "M"
end
def my_image img
return nil if img.nil?
return img['imageUrl']
end
def current_user me
return nil if me.nil?
me = JSON.parse(me)
me = me['profile']
email = parse_email(me['emails'])
user = {:id => me["guid"], :email => email, :name => full_name(me['givenName'],me['familyName']), :first_name => normalize_name(me['givenName']),
:last_name => normalize_name(me['familyName']), :gender => gender(me['gender']), :birthday => birthday(me['birthdate']),
:profile_picture => my_image(me['image'])
}
user
end
#def profile_image_url(guid, access_token, access_token_secret)
# image_path = "/v1/user/#{guid}/profile/image/48x48"
# response = https_get(@contacts_host, image_path, contacts_req_params(access_token, access_token_secret, image_path))
# image_data = JSON.parse(response)
# return image_data['image']['imageUrl'] if image_data['image']
# return nil
#end
end
end
end
================================================
FILE: lib/omnicontacts/importer.rb
================================================
module OmniContacts
module Importer
autoload :Gmail, "omnicontacts/importer/gmail"
autoload :Yahoo, "omnicontacts/importer/yahoo"
autoload :Hotmail, "omnicontacts/importer/hotmail"
autoload :Outlook, "omnicontacts/importer/outlook"
autoload :Facebook, "omnicontacts/importer/facebook"
autoload :Linkedin, "omnicontacts/importer/linkedin"
end
end
================================================
FILE: lib/omnicontacts/integration_test.rb
================================================
require 'singleton'
class IntegrationTest
include Singleton
attr_accessor :enabled
def initialize
enabled = false
clear_mocks
end
def clear_mocks
@user_mocks = {}
@contact_mocks = {}
end
def mock provider, contacts, user = {}
@contact_mocks[provider.to_sym] = contacts
@user_mocks[provider.to_sym] = user
end
def mock_authorization_from_user provider
[302, {"Content-Type" => "application/x-www-form-urlencoded", "location" => provider.redirect_path}, []]
end
def mock_fetch_contacts provider
result = @contact_mocks[provider.class_name.to_sym] || []
if result.is_a? Array
result
elsif result.is_a? Hash
[result]
else
raise result.to_s
end
end
def mock_fetch_user provider
@user_mocks[provider.class_name.to_sym] || {}
end
end
================================================
FILE: lib/omnicontacts/middleware/base_oauth.rb
================================================
# This class contains the common behavior for middlewares
# implementing either versions of OAuth.
#
# Extending classes are required to implement
# the following methods:
# * request_authorization_from_user
# * fetch_contatcs
module OmniContacts
module Middleware
class BaseOAuth
attr_reader :ssl_ca_file
def initialize app, options
@app = app
@listening_path = MOUNT_PATH + class_name
@ssl_ca_file = options[:ssl_ca_file]
end
def class_name
self.class.name.split('::').last.downcase
end
# Rack callback. It handles three cases:
# * user visit middleware entry point.
# In this case request_authorization_from_user is called
# * user is redirected back to the application
# from the authorization site. In this case the list
# of contacts is fetched and stored in the variables
# omnicontacts.contacts within the Rack env variable.
# Once that is done the next middleware component is called.
# * user visits any other resource. In this case the request
# is simply forwarded to the next middleware component.
def call env
@env = env
if env["PATH_INFO"] =~ /^#{@listening_path}\/?$/
session['omnicontacts.params'] = Rack::Request.new(env).params
handle_initial_request
elsif env["PATH_INFO"] =~ /^#{redirect_path}/
env['omnicontacts.params'] = session.delete('omnicontacts.params')
handle_callback
else
@app.call(env)
end
end
private
def test_mode?
IntegrationTest.instance.enabled
end
def handle_initial_request
execute_and_rescue_exceptions do
if test_mode?
IntegrationTest.instance.mock_authorization_from_user(self)
else
request_authorization_from_user
end
end
end
def handle_callback
execute_and_rescue_exceptions do
@env["omnicontacts.contacts"] = if test_mode?
IntegrationTest.instance.mock_fetch_contacts(self)
else
fetch_contacts
end
set_current_user IntegrationTest.instance.mock_fetch_user(self) if test_mode?
@app.call(@env)
end
end
def set_current_user user
@env["omnicontacts.user"] = user
end
# This method rescues executes a block of code and
# rescue all exceptions. In case of an exception the
# user is redirected to the failure endpoint.
def execute_and_rescue_exceptions
yield
rescue AuthorizationError => e
handle_error :not_authorized, e
rescue ::Timeout::Error, ::Errno::ETIMEDOUT => e
handle_error :timeout, e
rescue ::RuntimeError => e
handle_error :internal_error, e
end
def handle_error error_type, exception
logger.puts("Error #{error_type} while processing #{@env["PATH_INFO"]}: #{exception.message}") if logger
failure_url = "#{ MOUNT_PATH }failure?error_message=#{error_type}&importer=#{class_name}"
params_url = append_request_params(failure_url)
target_url = append_state_query(params_url)
[302, {"Content-Type" => "text/html", "location" => target_url}, []]
end
def session
raise "You must provide a session to use OmniContacts" unless @env["rack.session"]
@env["rack.session"]
end
def logger
@env["rack.errors"] if @env
end
def base_prop_name
"omnicontacts." + class_name
end
def append_request_params(target_url)
return target_url unless @env['omnicontacts.params']
params = Rack::Utils.build_query(@env['omnicontacts.params'])
unless params.nil? or params.empty?
target_url = target_url + (target_url.include?("?")?"&":"?") + params
end
return target_url
end
def append_state_query(target_url)
state = Rack::Utils.parse_query(@env['QUERY_STRING'])['state']
unless state.nil?
target_url = target_url + (target_url.include?("?")?"&":"?") + 'state=' + state
end
return target_url
end
end
end
end
================================================
FILE: lib/omnicontacts/middleware/oauth1.rb
================================================
require "omnicontacts/authorization/oauth1"
require "omnicontacts/middleware/base_oauth"
# This class is an OAuth 1.0 Rack middleware.
#
# Extending classes are required to
# implement the following methods:
# * fetch_token_from_token_and_verifier -> this method has to
# fetch the list of contacts from the authorization server.
module OmniContacts
module Middleware
class OAuth1 < BaseOAuth
include Authorization::OAuth1
attr_reader :consumer_key, :consumer_secret, :callback_path
def initialize app, consumer_key, consumer_secret, options = {}
super app, options
@consumer_key = consumer_key
@consumer_secret = consumer_secret
@callback_path = options[:callback_path] || "#{ MOUNT_PATH }#{class_name}/callback"
@token_prop_name = "#{base_prop_name}.oauth_token"
end
def callback
host_url_from_rack_env(@env) + callback_path
end
alias :redirect_path :callback_path
# Obtains an authorization token from the server,
# stores it and the session and redirect the user
# to the authorization website.
def request_authorization_from_user
(auth_token, auth_token_secret) = fetch_authorization_token
session[@token_prop_name] = auth_token
session[token_secret_prop_name(auth_token)] = auth_token_secret
redirect_to_authorization_site(auth_token)
end
def token_secret_prop_name oauth_token
"#{base_prop_name}.#{oauth_token}.oauth_token_secret"
end
def redirect_to_authorization_site auth_token
authorization_url = authorization_url(auth_token)
target_url = append_state_query(authorization_url)
[302, {"Content-Type" => "application/x-www-form-urlencoded", "location" => target_url}, []]
end
# Parses the authorization token from the query string and
# obtain the relative secret from the session.
# Finally it calls fetch_contacts_from_token_and_verifier.
# If token is found in the query string an AuhorizationError
# is raised.
def fetch_contacts
params = query_string_to_map(@env["QUERY_STRING"])
oauth_token = params["oauth_token"]
oauth_verifier = params["oauth_verifier"]
oauth_token_secret = session[token_secret_prop_name(oauth_token)]
if oauth_token && oauth_verifier && oauth_token_secret
fetch_contacts_from_token_and_verifier(oauth_token, oauth_token_secret, oauth_verifier)
else
raise AuthorizationError.new("User did not grant access to contacts list")
end
end
end
end
end
================================================
FILE: lib/omnicontacts/middleware/oauth2.rb
================================================
require "omnicontacts/authorization/oauth2"
require "omnicontacts/middleware/base_oauth"
# This class is a OAuth 2 Rack middleware.
#
# Extending class are required to implement
# the following methods:
# * fetch_contacts_using_access_token -> it
# fetches the list of contacts from the authorization
# server.
module OmniContacts
module Middleware
class OAuth2 < BaseOAuth
include Authorization::OAuth2
attr_reader :client_id, :client_secret, :redirect_path
def initialize app, client_id, client_secret, options ={}
super app, options
@client_id = client_id
@client_secret = client_secret
@redirect_path = options[:redirect_path] || "#{ MOUNT_PATH }#{class_name}/callback"
@ssl_ca_file = options[:ssl_ca_file]
end
def request_authorization_from_user
target_url = append_state_query(authorization_url)
[302, {"Content-Type" => "application/x-www-form-urlencoded", "location" => target_url}, []]
end
def redirect_uri
host_url_from_rack_env(@env) + redirect_path
end
# It extract the authorization code from the query string.
# It uses it to obtain an access token.
# If the authorization code has a refresh token associated
# with it in the session, it uses the obtain an access token.
# It fetches the list of contacts and stores the refresh token
# associated with the access token in the session.
# Finally it returns the list of contacts.
# If no authorization code is found in the query string an
# AuthoriazationError is raised.
def fetch_contacts
code = query_string_to_map(@env["QUERY_STRING"])["code"]
if code
refresh_token = session[refresh_token_prop_name(code)]
(access_token, token_type, refresh_token) = if refresh_token
refresh_access_token(refresh_token)
else
fetch_access_token(code)
end
contacts = fetch_contacts_using_access_token(access_token, token_type)
session[refresh_token_prop_name(code)] = refresh_token if refresh_token
contacts
else
raise AuthorizationError.new("User did not grant access to contacts list")
end
end
def refresh_token_prop_name code
"#{base_prop_name}.#{code}.refresh_token"
end
end
end
end
================================================
FILE: lib/omnicontacts/parse_utils.rb
================================================
module OmniContacts
module ParseUtils
# return has of birthday day, month and year
def birthday_format month, day, year
return {:day => day.to_i, :month => month.to_i, :year => year.to_i}if year && month && day
return {:day => day.to_i, :month => month.to_i, :year => nil} if !year && month && day
return nil if (!year && !month) || (!year && !day)
end
# normalize the name
def normalize_name name
return nil if name.nil?
name.chomp!
name.squeeze!(' ')
name.strip!
return name
end
# create a full name given the individual first and last name
def full_name first_name, last_name
return "#{first_name} #{last_name}" if first_name && last_name
return "#{first_name}" if first_name && !last_name
return "#{last_name}" if !first_name && last_name
return nil
end
# create a username/name from a given email
def email_to_name username_or_email
username_or_email = username_or_email.split('@').first if username_or_email.include?('@')
if group = (/(?<first>[a-z|A-Z]+)[\.|_](?<last>[a-z|A-Z]+)/).match(username_or_email)
first_name = normalize_name(group[:first])
last_name = normalize_name(group[:last])
return first_name, last_name, "#{first_name} #{last_name}"
end
username = normalize_name(username_or_email)
return username, nil, username
end
# create an image_url from a gmail or yahoo email id.
def image_url_from_email email
return nil if email.nil? || !email.include?('@')
username, domain = *(email.split('@'))
return nil if username.nil? or domain.nil?
gmail_base_url = "https://profiles.google.com/s2/photos/profile/"
yahoo_base_url = "https://img.msg.yahoo.com/avatar.php?yids="
if domain.include?('gmail')
image_url = gmail_base_url + username
elsif domain.include?('yahoo')
image_url = yahoo_base_url + username
end
image_url
end
end
end
================================================
FILE: lib/omnicontacts.rb
================================================
module OmniContacts
VERSION = "0.3.10"
MOUNT_PATH = "/contacts/"
autoload :Builder, "omnicontacts/builder"
autoload :Importer, "omnicontacts/importer"
autoload :IntegrationTest, "omnicontacts/integration_test"
class AuthorizationError < RuntimeError
end
def self.integration_test
IntegrationTest.instance
end
end
================================================
FILE: omnicontacts.gemspec
================================================
# encoding: utf-8
require File.expand_path('../lib/omnicontacts', __FILE__)
Gem::Specification.new do |gem|
gem.name = 'omnicontacts'
gem.description = %q{A generalized Rack middleware for importing contacts from major email providers.}
gem.authors = ['Diego Castorina', 'Jordan Lance', 'Asma Tameem', 'Randy Villanueva']
gem.email = ['diegocastorina@gmail.com', 'voorruby@gmail.com']
gem.add_runtime_dependency 'rack'
gem.add_runtime_dependency 'json'
gem.add_development_dependency 'simplecov'
gem.add_development_dependency 'rake'
gem.add_development_dependency 'rack-test'
gem.add_development_dependency 'rspec'
gem.version = OmniContacts::VERSION
gem.files = `git ls-files`.split("\n")
gem.homepage = 'http://github.com/Diego81/omnicontacts'
gem.require_paths = ['lib']
gem.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if gem.respond_to? :required_rubygems_version=
gem.summary = gem.description
gem.test_files = `git ls-files -- {spec}/*`.split("\n")
end
================================================
FILE: spec/omnicontacts/authorization/oauth1_spec.rb
================================================
require "spec_helper"
require "omnicontacts/authorization/oauth1"
describe OmniContacts::Authorization::OAuth1 do
before(:all) do
OAuth1TestClass= Struct.new(:consumer_key, :consumer_secret, :auth_host, :auth_token_path, :auth_path, :access_token_path, :callback)
class OAuth1TestClass
include OmniContacts::Authorization::OAuth1
end
end
let(:test_target) do
OAuth1TestClass.new("consumer_key", "secret1", "auth_host", "auth_token_path", "auth_path", "access_token_path", "callback")
end
describe "fetch_authorization_token" do
it "should request the token providing all mandatory parameters" do
test_target.should_receive(:https_post) do |host, path, params|
host.should eq(test_target.auth_host)
path.should eq(test_target.auth_token_path)
params[:oauth_consumer_key].should eq(test_target.consumer_key)
params[:oauth_nonce].should_not be_nil
params[:oauth_signature_method].should eq("PLAINTEXT")
params[:oauth_signature].should eq(test_target.consumer_secret + "%26")
params[:oauth_timestamp].should_not be_nil
params[:oauth_version].should eq("1.0")
params[:oauth_callback].should eq(test_target.callback)
"oauth_token=token&oauth_token_secret=token_secret"
end
test_target.fetch_authorization_token
end
it "should successfully parse the result" do
test_target.should_receive(:https_post).and_return("oauth_token=token&oauth_token_secret=token_secret")
test_target.fetch_authorization_token.should eq(["token", "token_secret"])
end
it "should raise an error if request is invalid" do
test_target.should_receive(:https_post).and_return("invalid_request")
expect { test_target.fetch_authorization_token }.to raise_error
end
end
describe "authorization_url" do
subject { test_target.authorization_url("token") }
it { should eq("https://#{test_target.auth_host}#{test_target.auth_path}?oauth_token=token") }
end
describe "fetch_access_token" do
it "should request the access token using all required parameters" do
auth_token = "token"
auth_token_secret = "token_secret"
auth_verifier = "verifier"
test_target.should_receive(:https_post) do |host, path, params|
host.should eq(test_target.auth_host)
path.should eq(test_target.access_token_path)
params[:oauth_consumer_key].should eq(test_target.consumer_key)
params[:oauth_nonce].should_not be_nil
params[:oauth_signature_method].should eq("PLAINTEXT")
params[:oauth_version].should eq("1.0")
params[:oauth_signature].should eq("#{test_target.consumer_secret}%26#{auth_token_secret}")
params[:oauth_token].should eq(auth_token)
params[:oauth_verifier].should eq(auth_verifier)
"oauth_token=access_token&oauth_token_secret=access_token_secret&other_param=other_value"
end
test_target.fetch_access_token auth_token, auth_token_secret, auth_verifier, ["other_param"]
end
it "should successfully extract access_token and the other fields" do
test_target.should_receive(:https_post).and_return("oauth_token=access_token&oauth_token_secret=access_token_secret&other_param=other_value")
test_target.fetch_access_token("token", "token_scret", "verified", ["other_param"]).should eq(["access_token", "access_token_secret", "other_value"])
end
end
describe "oauth_signature" do
subject { test_target.oauth_signature("GET", "https://social.yahooapis.com/v1/user", {:name => "diego", :surname => "castorina"}, "secret2") }
it { should eq("xfumZoyVYUbHXSAafdha3HZUqQg%3D") }
end
end
================================================
FILE: spec/omnicontacts/authorization/oauth2_spec.rb
================================================
require "spec_helper"
require "omnicontacts/authorization/oauth2"
describe OmniContacts::Authorization::OAuth2 do
before(:all) do
OAuth2TestClass= Struct.new(:auth_host, :authorize_path, :client_id, :client_secret, :scope, :redirect_uri, :auth_token_path)
class OAuth2TestClass
include OmniContacts::Authorization::OAuth2
end
end
let(:test_target) do
OAuth2TestClass.new("auth_host", "authorize_path", "client_id", "client_secret", "scope", "redirect_uri", "auth_token_path")
end
describe "authorization_url" do
subject { test_target.authorization_url }
it { should include("https://#{test_target.auth_host}#{test_target.authorize_path}") }
it { should include("client_id=#{test_target.client_id}") }
it { should include("scope=#{test_target.scope}") }
it { should include("redirect_uri=#{test_target.redirect_uri}") }
it { should include("access_type=online") }
it { should include("response_type=code") }
end
let(:access_token_response) { %[{"access_token": "access_token", "token_type":"token_type", "refresh_token":"refresh_token"}] }
describe "fetch_access_token" do
it "should provide all mandatory parameters in a https post request" do
code = "code"
test_target.should_receive(:https_post) do |host, path, params|
host.should eq(test_target.auth_host)
path.should eq(test_target.auth_token_path)
params[:code].should eq(code)
params[:client_id].should eq(test_target.client_id)
params[:client_secret].should eq(test_target.client_secret)
params[:redirect_uri].should eq(test_target.redirect_uri)
params[:grant_type].should eq("authorization_code")
access_token_response
end
test_target.fetch_access_token code
end
it "should successfully parse the token from the JSON response" do
test_target.should_receive(:https_post).and_return(access_token_response)
(access_token, token_type, refresh_token) = test_target.fetch_access_token "code"
access_token.should eq("access_token")
token_type.should eq("token_type")
refresh_token.should eq("refresh_token")
end
it "should raise if the http request fails" do
test_target.should_receive(:https_post).and_raise("Invalid code")
expect { test_target.fetch_access_token("code") }.to raise_error
end
it "should raise an error if the JSON response contains an error field" do
test_target.should_receive(:https_post).and_return(%[{"error": "error_message"}])
expect { test_target.fetch_access_token("code") }.to raise_error
end
end
describe "refresh_access_token" do
it "should provide all mandatory fields in a https post request" do
refresh_token = "refresh_token"
test_target.should_receive(:https_post) do |host, path, params|
host.should eq(test_target.auth_host)
path.should eq(test_target.auth_token_path)
params[:client_id].should eq(test_target.client_id)
params[:client_secret].should eq(test_target.client_secret)
params[:refresh_token].should eq(refresh_token)
params[:grant_type].should eq("refresh_token")
access_token_response
end
test_target.refresh_access_token refresh_token
end
it "should successfully parse the token from the JSON response" do
test_target.should_receive(:https_post).and_return(access_token_response)
(access_token, token_type, refresh_token) = test_target.refresh_access_token "refresh_token"
access_token.should eq("access_token")
token_type.should eq("token_type")
refresh_token.should eq("refresh_token")
end
end
end
================================================
FILE: spec/omnicontacts/http_utils_spec.rb
================================================
require "spec_helper"
require "omnicontacts/http_utils"
describe OmniContacts::HTTPUtils do
describe "to_query_string" do
it "should create a query string from a map" do
result = OmniContacts::HTTPUtils.to_query_string(:name => "john", :surname => "doe")
if result.match(/^name/)
result.should eq("name=john&surname=doe")
else
result.should eq("surname=doe&name=john")
end
end
it "should work for integer values in the map" do
result = OmniContacts::HTTPUtils.to_query_string(:client_id => 1234, :secret => "1234HJL8")
result.should eq("client_id=1234&secret=1234HJL8")
end
end
describe "encode" do
it "should encode the space" do
OmniContacts::HTTPUtils.encode("name=\"john\"").should eq("name%3D%22john%22")
end
end
describe "query_string_to_map" do
it "should split a query string into a map" do
query_string = "name=john&surname=doe"
result = OmniContacts::HTTPUtils.query_string_to_map(query_string)
result["name"].should eq("john")
result["surname"].should eq("doe")
end
end
describe "host_url_from_rack_env" do
it "should calculate the host url using the HTTP_HOST variable" do
env = {"rack.url_scheme" => "http", "HTTP_HOST" => "localhost:8080", "SERVER_NAME" => "localhost", "SERVER_PORT" => 8080}
OmniContacts::HTTPUtils.host_url_from_rack_env(env).should eq("http://localhost:8080")
end
it "should calculate the host url using SERVER_NAME and SERVER_PORT variables" do
env = {"rack.url_scheme" => "http", "SERVER_NAME" => "localhost", "SERVER_PORT" => 8080}
OmniContacts::HTTPUtils.host_url_from_rack_env(env).should eq("http://localhost:8080")
end
end
describe "https_post" do
before(:each) do
@connection = double
Net::HTTP.should_receive(:new).and_return(@connection)
@connection.should_receive(:use_ssl=).with(true)
@test_target = Object.new
@test_target.extend OmniContacts::HTTPUtils
@response = double
end
it "should execute a request with success" do
@test_target.should_receive(:ssl_ca_file).and_return(nil)
@connection.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
@connection.should_receive(:request_post).and_return(@response)
@response.should_receive(:code).and_return("200")
@response.should_receive(:body).and_return("some content")
@test_target.send(:https_post, "host", "path", {})
end
it "should raise an exception with response code != 200" do
@test_target.should_receive(:ssl_ca_file).and_return(nil)
@connection.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
@connection.should_receive(:request_get).and_return(@response)
@response.should_receive(:code).and_return("500")
@response.should_receive(:body).and_return("some error message")
expect { @test_target.send(:https_get, "host", "path", {}) }.to raise_error
end
end
end
================================================
FILE: spec/omnicontacts/importer/facebook_spec.rb
================================================
require "spec_helper"
require "omnicontacts/importer/facebook"
describe OmniContacts::Importer::Facebook do
let(:facebook) { OmniContacts::Importer::Facebook.new({}, "client_id", "client_secret") }
let(:self_response) {
'{
"first_name":"Chris",
"last_name":"Johnson",
"name":"Chris Johnson",
"id":"543216789",
"gender":"male",
"birthday":"06/21/1982",
"significant_other":{"id": "243435322"},
"relationship_status": "Married",
"picture":{"data":{"url":"http://profile.ak.fbcdn.net/hprofile-ak-snc6/186364_543216789_2089044200_q.jpg","is_silhouette":false}},
"email": "chrisjohnson@gmail.com"
}'
}
let(:spouse_response) {
'{
"first_name":"Mary",
"last_name":"Johnson",
"name":"Mary Johnson",
"id":"243435322",
"gender":"female",
"birthday":"01/21",
"picture":{"data":{"url":"http://profile.ak.fbcdn.net/hprofile-ak-snc6/186364_243435322_2089044200_q.jpg","is_silhouette":false}}
}'
}
let(:contacts_as_json) {
'{"data":[
{
"first_name":"John",
"last_name":"Smith",
"name":"John Smith",
"id":"608061886",
"gender":"male",
"birthday":"06/21",
"relationship":"cousin",
"picture":{"data":{"url":"http://profile.ak.fbcdn.net/hprofile-ak-snc6/186364_608061886_2089044200_q.jpg","is_silhouette":false}}
}
]
}' }
describe "fetch_contacts_using_access_token" do
let(:token) { "token" }
let(:token_type) { "token_type" }
before(:each) do
facebook.instance_variable_set(:@env, {})
end
it "should request the contacts by providing the token in the url" do
facebook.should_receive(:https_get) do |host, self_path, params, headers|
params[:access_token].should eq(token)
params[:fields].should eq('first_name,last_name,name,id,gender,birthday,picture,relationship_status,significant_other,email')
self_response
end
facebook.should_receive(:https_get) do |host, spouse_path, params, headers|
params[:access_token].should eq(token)
params[:fields].should eq('first_name,last_name,name,id,gender,birthday,picture')
spouse_response
end
facebook.should_receive(:https_get) do |host, path, params, headers|
params[:access_token].should eq(token)
params[:fields].should eq('first_name,last_name,name,id,gender,birthday,picture,relationship')
contacts_as_json
end.exactly(1).times
facebook.should_receive(:https_get) do |host, path, params, headers|
params[:access_token].should eq(token)
params[:fields].should eq('first_name,last_name,name,id,gender,birthday,picture')
contacts_as_json
end.exactly(1).times
facebook.fetch_contacts_using_access_token token, token_type
end
it "should correctly parse id, name,email,gender, birthday, profile picture and relation" do
1.times { facebook.should_receive(:https_get).and_return(self_response) }
1.times { facebook.should_receive(:https_get) }
2.times { facebook.should_receive(:https_get).and_return(contacts_as_json) }
result = facebook.fetch_contacts_using_access_token token, token_type
result.size.should be(1)
result.first[:id].should eq('608061886')
result.first[:first_name].should eq('John')
result.first[:last_name].should eq('Smith')
result.first[:name].should eq('John Smith')
result.first[:email].should be_nil
result.first[:gender].should eq('male')
result.first[:birthday].should eq({:day=>21, :month=>06, :year=>nil})
result.first[:profile_picture].should eq('https://graph.facebook.com/608061886/picture')
result.first[:relation].should eq('cousin')
end
it "should correctly parse and set logged in user information" do
1.times { facebook.should_receive(:https_get).and_return(self_response) }
1.times { facebook.should_receive(:https_get) }
2.times { facebook.should_receive(:https_get).and_return(contacts_as_json) }
facebook.fetch_contacts_using_access_token token, token_type
user = facebook.instance_variable_get(:@env)["omnicontacts.user"]
user.should_not be_nil
user[:id].should eq("543216789")
user[:first_name].should eq("Chris")
user[:last_name].should eq("Johnson")
user[:name].should eq("Chris Johnson")
user[:email].should eq("chrisjohnson@gmail.com")
user[:gender].should eq("male")
user[:birthday].should eq({:day=>21, :month=>06, :year=>1982})
user[:profile_picture].should eq("https://graph.facebook.com/543216789/picture")
end
end
end
================================================
FILE: spec/omnicontacts/importer/gmail_spec.rb
================================================
require "spec_helper"
require "omnicontacts/importer/gmail"
describe OmniContacts::Importer::Gmail do
let(:gmail) { OmniContacts::Importer::Gmail.new({}, "client_id", "client_secret") }
let(:gmail_with_scope_args) {
OmniContacts::Importer::Gmail.new(
{},
"client_id",
"client_secret",
{
scope: %w(
https://www.googleapis.com/auth/contacts.readonly
https://www.googleapis.com/auth/userinfo#email
https://www.googleapis.com/auth/userinfo.profile
).join(" ")
}
)
}
let(:self_response) {
'{
"id":"16482944006464829443",
"email":"chrisjohnson@gmail.com",
"name":"Chris Johnson",
"given_name":"Chris",
"family_name":"Johnson",
"picture":"https://lh3.googleusercontent.com/-b8aFbTBM/AAAAAAI/IWA/vsek/photo.jpg",
"gender":"male",
"birthday":"1982-06-21"
}'
}
let(:contacts_as_json) {
'{"version":"1.0","encoding":"UTF-8",
"feed":{
"xmlns":"http://www.w3.org/2005/Atom",
"xmlns$openSearch":"http://a9.com/-/spec/opensearch/1.1/",
"xmlns$gContact":"http://schemas.google.com/contact/2008",
"xmlns$batch":"http://schemas.google.com/gdata/batch",
"xmlns$gd":"http://schemas.google.com/g/2005",
"gd$etag":"W/\"C0YHRno7fSt7I2A9WhBSQ0Q.\"",
"id":{"$t":"logged_in_user@gmail.com"},
"updated":{"$t":"2013-02-20T20:12:17.405Z"},
"category":[{
"scheme":"http://schemas.google.com/g/2005#kind",
"term":"http://schemas.google.com/contact/2008#contact"
}],
"title":{"$t":"Users\'s Contacts"},
"link":[
{"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/6b41d030b05abc","gd$etag":"\"VSxuN0cISit7I2A1UVUSdy12KHwgBFkE333.\""},
{"rel":"alternate","type":"text/html","href":"http://www.google.com/"},
{"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"},
{"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"},
{"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"},
{"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"},
{"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"}
],
"author":[{"name":{"$t":"Edward"},"email":{"$t":"logged_in_user@gmail.com"}}],
"generator":{"version":"1.0","uri":"http://www.google.com/m8/feeds","$t":"Contacts"},
"openSearch$totalResults":{"$t":"1007"},
"openSearch$startIndex":{"$t":"1"},
"openSearch$itemsPerPage":{"$t":"1"},
"entry":[
{
"gd$etag":"\"R3oyfDVSLyt7I2A9WhBTSEULRA0.\"",
"id":{"$t":"http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1"},
"updated":{"$t":"2013-02-14T22:36:36.494Z"},
"app$edited":{"xmlns$app":"http://www.w3.org/2007/app","$t":"2013-02-14T22:36:36.494Z"},
"category":[{"scheme":"http://schemas.google.com/g/2005#kind","term":"http://schemas.google.com/contact/2008#contact"}],
"title":{"$t":"Edward Bennet"},
"link":[
{"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/6b41d030b05abc", "gd$etag":"\"VSxuN0cISit7I2A1UVUSdy12KHwgBFkE333.\""},
{"rel":"self","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"},
{"rel":"edit","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"}
],
"gd$name":{
"gd$fullName":{"$t":"Edward Bennet"},
"gd$givenName":{"$t":"Edward"},
"gd$familyName":{"$t":"Bennet"}
},
"gd$organization":[{"rel":"http://schemas.google.com/g/2005#other","gd$orgName":{"$t":"Google"},"gd$orgTitle":{"$t":"Master Developer"}}],
"gContact$birthday":{"when":"1954-07-02"},
"gContact$relation":{"rel":"father"},
"gContact$gender":{"value":"male"},
"gContact$event":[{"rel":"anniversary","gd$when":{"startTime":"1983-04-21"}},{"label":"New Job","gd$when":{"startTime":"2014-12-01"}}],
"gd$email":[{"rel":"http://schemas.google.com/g/2005#other","address":"bennet@gmail.com","primary":"true"}],
"gContact$groupMembershipInfo":[{"deleted":"false","href":"http://www.google.com/m8/feeds/groups/logged_in_user%40gmail.com/base/6"}],
"gd$structuredPostalAddress":[{"rel":"http://schemas.google.com/g/2005#home","gd$formattedAddress":{"$t":"1313 Trashview Court\nApt. 13\nNowheresville, OK 66666"},"gd$street":{"$t":"1313 Trashview Court\nApt. 13"},"gd$postcode":{"$t":"66666"},"gd$country":{"code":"VA","$t":"Valoran"},"gd$city":{"$t":"Nowheresville"},"gd$region":{"$t":"OK"}}],
"gd$phoneNumber":[{"rel":"http://schemas.google.com/g/2005#mobile","uri":"tel:+34-653-15-76-88","$t":"653157688"}]
},
{
"gd$etag":"\"R3oyfDVSLyt7I2A9WhBTSEULRA0.\"",
"id":{"$t":"http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1"},
"updated":{"$t":"2013-02-15T22:36:36.494Z"},
"app$edited":{"xmlns$app":"http://www.w3.org/2007/app","$t":"2013-02-15T22:36:36.494Z"},
"category":[{"scheme":"http://schemas.google.com/g/2005#kind","term":"http://schemas.google.com/contact/2008#contact"}],
"title":{"$t":"Emilia Fox"},
"link":[
{"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"},
{"rel":"self","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"},
{"rel":"edit","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"}
],
"gd$name":{
"gd$fullName":{"$t":"Emilia Fox"},
"gd$givenName":{"$t":"Emilia"},
"gd$familyName":{"$t":"Fox"}
},
"gContact$birthday":{"when":"1974-02-10"},
"gContact$relation":[{"rel":"spouse"}],
"gContact$gender":{"value":"female"},
"gd$email":[{"rel":"http://schemas.google.com/g/2005#other","address":"emilia.fox@gmail.com","primary":"true"}],
"gContact$groupMembershipInfo":[{"deleted":"false","href":"http://www.google.com/m8/feeds/groups/logged_in_user%40gmail.com/base/6"}]
}]
}
}'
}
describe "fetch_contacts_using_access_token" do
let(:token) { "token" }
let(:token_type) { "token_type" }
before(:each) do
gmail.instance_variable_set(:@env, {})
gmail_with_scope_args.instance_variable_set(:@env, {})
end
it "should request the contacts by specifying version and code in the http headers" do
gmail.should_receive(:https_get) do |host, path, params, headers|
headers["GData-Version"].should eq("3.0")
headers["Authorization"].should eq("#{token_type} #{token}")
self_response
end
gmail.should_receive(:https_get) do |host, path, params, headers|
headers["GData-Version"].should eq("3.0")
headers["Authorization"].should eq("#{token_type} #{token}")
contacts_as_json
end
gmail.fetch_contacts_using_access_token token, token_type
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"
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"
end
it "should correctly parse id, name, email, gender, birthday, profile picture and relation for 1st contact" do
gmail.should_receive(:https_get)
gmail.should_receive(:https_get).and_return(contacts_as_json)
result = gmail.fetch_contacts_using_access_token token, token_type
result.size.should be(2)
result.first[:id].should eq('http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1')
result.first[:first_name].should eq('Edward')
result.first[:last_name].should eq('Bennet')
result.first[:name].should eq("Edward Bennet")
result.first[:email].should eq("bennet@gmail.com")
result.first[:gender].should eq("male")
result.first[:birthday].should eq({ :day => 02, :month => 07, :year => 1954 })
result.first[:relation].should eq('father')
result.first[:profile_picture].should eq("https://www.google.com/m8/feeds/photos/media/logged_in_user%40gmail.com/6b41d030b05abc?&access_token=token")
result.first[:dates][0][:name].should eq("anniversary")
end
it "should correctly parse id, name, email, gender, birthday, profile picture, snailmail address, phone and relation for 2nd contact" do
gmail.should_receive(:https_get)
gmail.should_receive(:https_get).and_return(contacts_as_json)
result = gmail.fetch_contacts_using_access_token token, token_type
result.size.should be(2)
result.last[:id].should eq('http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1')
result.last[:first_name].should eq('Emilia')
result.last[:last_name].should eq('Fox')
result.last[:name].should eq("Emilia Fox")
result.last[:email].should eq("emilia.fox@gmail.com")
result.last[:gender].should eq("female")
result.last[:birthday].should eq({ :day => 10, :month => 02, :year => 1974 })
result.last[:profile_picture].should be_nil
result.last[:relation].should eq('spouse')
result.first[:address_1].should eq('1313 Trashview Court')
result.first[:address_2].should eq('Apt. 13')
result.first[:city].should eq('Nowheresville')
result.first[:region].should eq('OK')
result.first[:country].should eq('VA')
result.first[:postcode].should eq('66666')
result.first[:phone_number].should eq('653157688')
end
it "should correctly parse and set logged in user information" do
gmail.should_receive(:https_get).and_return(self_response)
gmail.should_receive(:https_get).and_return(contacts_as_json)
gmail.fetch_contacts_using_access_token token, token_type
user = gmail.instance_variable_get(:@env)["omnicontacts.user"]
user.should_not be_nil
user[:id].should eq("16482944006464829443")
user[:first_name].should eq("Chris")
user[:last_name].should eq("Johnson")
user[:name].should eq("Chris Johnson")
user[:email].should eq("chrisjohnson@gmail.com")
user[:gender].should eq("male")
user[:birthday].should eq({ :day => 21, :month => 06, :year => 1982 })
user[:profile_picture].should eq("https://lh3.googleusercontent.com/-b8aFbTBM/AAAAAAI/IWA/vsek/photo.jpg")
end
context "when address_1 is nil" do
let(:contacts_as_json) {
'{"version":"1.0","encoding":"UTF-8",
"feed":{
"xmlns":"http://www.w3.org/2005/Atom",
"xmlns$openSearch":"http://a9.com/-/spec/opensearch/1.1/",
"xmlns$gContact":"http://schemas.google.com/contact/2008",
"xmlns$batch":"http://schemas.google.com/gdata/batch",
"xmlns$gd":"http://schemas.google.com/g/2005",
"gd$etag":"W/\"C0YHRno7fSt7I2A9WhBSQ0Q.\"",
"id":{"$t":"logged_in_user@gmail.com"},
"updated":{"$t":"2013-02-20T20:12:17.405Z"},
"category":[{
"scheme":"http://schemas.google.com/g/2005#kind",
"term":"http://schemas.google.com/contact/2008#contact"
}],
"title":{"$t":"Users\'s Contacts"},
"link":[
{"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/6b41d030b05abc","gd$etag":"\"VSxuN0cISit7I2A1UVUSdy12KHwgBFkE333.\""},
{"rel":"alternate","type":"text/html","href":"http://www.google.com/"},
{"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"},
{"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"},
{"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"},
{"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"},
{"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"}
],
"author":[{"name":{"$t":"Edward"},"email":{"$t":"logged_in_user@gmail.com"}}],
"generator":{"version":"1.0","uri":"http://www.google.com/m8/feeds","$t":"Contacts"},
"openSearch$totalResults":{"$t":"1007"},
"openSearch$startIndex":{"$t":"1"},
"openSearch$itemsPerPage":{"$t":"1"},
"entry":[
{
"gd$etag":"\"R3oyfDVSLyt7I2A9WhBTSEULRA0.\"",
"id":{"$t":"http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1"},
"updated":{"$t":"2013-02-14T22:36:36.494Z"},
"app$edited":{"xmlns$app":"http://www.w3.org/2007/app","$t":"2013-02-14T22:36:36.494Z"},
"category":[{"scheme":"http://schemas.google.com/g/2005#kind","term":"http://schemas.google.com/contact/2008#contact"}],
"title":{"$t":"Edward Bennet"},
"link":[
{"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/6b41d030b05abc", "gd$etag":"\"VSxuN0cISit7I2A1UVUSdy12KHwgBFkE333.\""},
{"rel":"self","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"},
{"rel":"edit","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"}
],
"gd$name":{
"gd$fullName":{"$t":"Edward Bennet"},
"gd$givenName":{"$t":"Edward"},
"gd$familyName":{"$t":"Bennet"}
},
"gd$organization":[{"rel":"http://schemas.google.com/g/2005#other","gd$orgName":{"$t":"Google"},"gd$orgTitle":{"$t":"Master Developer"}}],
"gContact$birthday":{"when":"1954-07-02"},
"gContact$relation":{"rel":"father"},
"gContact$gender":{"value":"male"},
"gContact$event":[{"rel":"anniversary","gd$when":{"startTime":"1983-04-21"}},{"label":"New Job","gd$when":{"startTime":"2014-12-01"}}],
"gd$email":[{"rel":"http://schemas.google.com/g/2005#other","address":"bennet@gmail.com","primary":"true"}],
"gContact$groupMembershipInfo":[{"deleted":"false","href":"http://www.google.com/m8/feeds/groups/logged_in_user%40gmail.com/base/6"}],
"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"}}],
"gd$phoneNumber":[{"rel":"http://schemas.google.com/g/2005#mobile","uri":"tel:+34-653-15-76-88","$t":"653157688"}]
},
{
"gd$etag":"\"R3oyfDVSLyt7I2A9WhBTSEULRA0.\"",
"id":{"$t":"http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1"},
"updated":{"$t":"2013-02-15T22:36:36.494Z"},
"app$edited":{"xmlns$app":"http://www.w3.org/2007/app","$t":"2013-02-15T22:36:36.494Z"},
"category":[{"scheme":"http://schemas.google.com/g/2005#kind","term":"http://schemas.google.com/contact/2008#contact"}],
"title":{"$t":"Emilia Fox"},
"link":[
{"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"},
{"rel":"self","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"},
{"rel":"edit","type":"application/atom+xml","href":"https://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/full/1"}
],
"gd$name":{
"gd$fullName":{"$t":"Emilia Fox"},
"gd$givenName":{"$t":"Emilia"},
"gd$familyName":{"$t":"Fox"}
},
"gContact$birthday":{"when":"1974-02-10"},
"gContact$relation":[{"rel":"spouse"}],
"gContact$gender":{"value":"female"},
"gd$email":[{"rel":"http://schemas.google.com/g/2005#other","address":"emilia.fox@gmail.com","primary":"true"}],
"gContact$groupMembershipInfo":[{"deleted":"false","href":"http://www.google.com/m8/feeds/groups/logged_in_user%40gmail.com/base/6"}]
}]
}
}'
}
it "should correctly parse id, name, email, gender, birthday, profile picture, snailmail address, phone and relation for 2nd contact" do
gmail.should_receive(:https_get)
gmail.should_receive(:https_get).and_return(contacts_as_json)
result = gmail.fetch_contacts_using_access_token token, token_type
result.size.should be(2)
result.last[:id].should eq('http://www.google.com/m8/feeds/contacts/logged_in_user%40gmail.com/base/1')
result.last[:first_name].should eq('Emilia')
result.last[:last_name].should eq('Fox')
result.last[:name].should eq("Emilia Fox")
result.last[:email].should eq("emilia.fox@gmail.com")
result.last[:gender].should eq("female")
result.last[:birthday].should eq({ :day => 10, :month => 02, :year => 1974 })
result.last[:profile_picture].should be_nil
result.last[:relation].should eq('spouse')
result.first[:address_1].should eq(nil)
result.first[:address_2].should eq(nil)
result.first[:city].should eq('Nowheresville')
result.first[:region].should eq('OK')
result.first[:country].should eq('VA')
result.first[:postcode].should eq('66666')
result.first[:phone_number].should eq('653157688')
end
end
end
end
================================================
FILE: spec/omnicontacts/importer/hotmail_spec.rb
================================================
require "spec_helper"
require "omnicontacts/importer/hotmail"
describe OmniContacts::Importer::Hotmail do
let(:permissions) { "perm1, perm2" }
let(:hotmail) { OmniContacts::Importer::Hotmail.new({}, "client_id", "client_secret", {:permissions => permissions}) }
let(:self_response) {
'{
"id": "4502de12390223d0",
"name": "Chris Johnson",
"first_name": "Chris",
"last_name": "Johnson",
"birth_day": 21,
"birth_month": 6,
"birth_year": 1982,
"gender": null,
"emails": {"preferred":"chrisjohnson@gmail.com", "account":"chrisjohn@gmail.com", "personal":null, "business":null}
}'
}
let(:contacts_as_json) {
'{
"data": [
{
"id": "contact.7fac34bb000000000000000000000000",
"first_name": "John",
"last_name": "Smith",
"name": "John Smith",
"gender": null,
"user_id": "123456",
"is_friend": false,
"is_favorite": false,
"birth_day": 5,
"birth_month": 6,
"birth_year":1952,
"email_hashes":["1234567890"]
}
]}'
}
describe "fetch_contacts_using_access_token" do
let(:token) { "token" }
let(:token_type) { "token_type" }
before(:each) do
hotmail.instance_variable_set(:@env, {"HTTP_HOST" => "http://example.com"})
end
it "should request the contacts by providing the token in the url" do
hotmail.should_receive(:https_get) do |host, path, params, headers|
params[:access_token].should eq(token)
self_response
end
hotmail.should_receive(:https_get) do |host, path, params, headers|
params[:access_token].should eq(token)
contacts_as_json
end
hotmail.fetch_contacts_using_access_token token, token_type
end
it "should set requested permissions in the authorization url" do
hotmail.authorization_url.should match(/scope=#{Regexp.quote(CGI.escape(permissions))}/)
end
it "should correctly parse id, name, email, gender, birthday, profile picture, relation and email hashes" do
hotmail.should_receive(:https_get).and_return(self_response)
hotmail.should_receive(:https_get).and_return(contacts_as_json)
result = hotmail.fetch_contacts_using_access_token token, token_type
result.size.should be(1)
result.first[:id].should eq('123456')
result.first[:first_name].should eq("John")
result.first[:last_name].should eq('Smith')
result.first[:name].should eq("John Smith")
result.first[:email].should be_nil
result.first[:gender].should be_nil
result.first[:birthday].should eq({:day=>5, :month=>6, :year=>1952})
result.first[:profile_picture].should eq('https://apis.live.net/v5.0/123456/picture')
result.first[:relation].should be_nil
result.first[:email_hashes].should eq(["1234567890"])
end
it "should correctly parse and set logged in user information" do
hotmail.should_receive(:https_get).and_return(self_response)
hotmail.should_receive(:https_get).and_return(contacts_as_json)
hotmail.fetch_contacts_using_access_token token, token_type
user = hotmail.instance_variable_get(:@env)["omnicontacts.user"]
user.should_not be_nil
user[:id].should eq('4502de12390223d0')
user[:first_name].should eq('Chris')
user[:last_name].should eq('Johnson')
user[:name].should eq('Chris Johnson')
user[:gender].should be_nil
user[:birthday].should eq({:day=>21, :month=>06, :year=>1982})
user[:email].should eq('chrisjohn@gmail.com')
user[:profile_picture].should eq('https://apis.live.net/v5.0/4502de12390223d0/picture')
end
end
end
================================================
FILE: spec/omnicontacts/importer/linkedin_spec.rb
================================================
require "spec_helper"
require "omnicontacts/importer/linkedin"
describe OmniContacts::Importer::Linkedin do
let(:linkedin) { OmniContacts::Importer::Linkedin.new({}, "client_id", "client_secret", state: "ipsaeumeaque") }
let(:contacts_as_json) do
"{
\n \"_total\": 2,
\n \"values\": [
\n {
\n \"firstName\": \"Adolf\",
\n \"id\": \"k71S5q6MKe\",
\n \"lastName\": \"Witting\",
\n \"pictureUrl\": \"https://media.licdn.com/mpr/mprx/0_mLnj-7szw130pFRLB8Op7-p1Sxoyv53U3B47Scp1Sxoyv53U3B47Scp1Sxoyv53U3B47Sc\"\n
},
\n {
\n \"firstName\": \"Emmet\",
\n \"id\": \"ms5r3lI3J2\",
\n \"lastName\": \"Little\",
\n \"pictureUrl\": \"https://media.licdn.com/mpr/mprx/0_iH9m158zCdISt1X6iH9m158zCdISt1X6iH9m158zCdISt1X6iH9m158zCdISt1X6iH9m158zCdISt1X6\"\n
}
]\n
}"
end
describe "fetch_contacts_using_access_token" do
let(:token) { "token" }
let(:token_type) { nil }
before(:each) do
linkedin.instance_variable_set(:@env, {})
end
it "should request the contacts by specifying code in the http headers" do
linkedin.should_receive(:https_get) do |host, path, params, headers|
headers["Authorization"].should eq("Bearer #{token}")
contacts_as_json
end
linkedin.fetch_contacts_using_access_token token, token_type
end
it "should correctly parse id, name, and profile picture for 1st contact" do
linkedin.should_receive(:https_get).and_return(contacts_as_json)
result = linkedin.fetch_contacts_using_access_token token, token_type
result.size.should be(2)
result.first[:id].should eq('k71S5q6MKe')
result.first[:first_name].should eq('Adolf')
result.first[:last_name].should eq('Witting')
result.first[:name].should eq("Adolf Witting")
result.first[:profile_picture].should eq("https://media.licdn.com/mpr/mprx/0_mLnj-7szw130pFRLB8Op7-p1Sxoyv53U3B47Scp1Sxoyv53U3B47Scp1Sxoyv53U3B47Sc")
end
it "should correctly parse id, name, and profile picture for 2nd contact" do
linkedin.should_receive(:https_get).and_return(contacts_as_json)
result = linkedin.fetch_contacts_using_access_token token, token_type
result.size.should be(2)
result.last[:id].should eq('ms5r3lI3J2')
result.last[:first_name].should eq('Emmet')
result.last[:last_name].should eq('Little')
result.last[:name].should eq("Emmet Little")
result.last[:profile_picture].should eq("https://media.licdn.com/mpr/mprx/0_iH9m158zCdISt1X6iH9m158zCdISt1X6iH9m158zCdISt1X6iH9m158zCdISt1X6iH9m158zCdISt1X6")
end
end
end
================================================
FILE: spec/omnicontacts/importer/outlook_spec.rb
================================================
require "spec_helper"
require "omnicontacts/importer/outlook"
describe OmniContacts::Importer::Outlook do
let(:permissions) { "Contacts.Read" }
let(:outlook) { OmniContacts::Importer::Outlook.new({}, "app_id", "app_secret", {:permissions => permissions}) }
let(:self_response) {
'{
"@odata.context": "https://outlook.office.com/api/v2.0/$metadata#Me",
"@odata.id": "https://outlook.office.com/api/v2.0/Users(\'00034001-df52-d3d5-0000-000000000000@84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa\')",
"Id": "00034001-df52-d3d5-0000-000000000000@84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa",
"EmailAddress": "test.user@outlook.com",
"DisplayName": "Test User",
"Alias": "puid-00034001DF52D3D5",
"MailboxGuid": "00034001-df52-d3d5-0000-000000000000"
}'
}
let(:contacts_as_json) {
'{
"@odata.context": "https://outlook.office.com/api/v2.0/$metadata#Me/Contacts",
"value": [{
"@odata.id": "https://outlook.office.com/api/v2.0/Users(\'00034001-df52-d3d5-0000-000000000000@84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa\')/Contacts(\'AQMkADAwATM0MDAAMS1kZjUyLWQzZDUtMDACLTAwCgBGAAADQ1hAWLJpwk6DZYyOhnclvgcAxCL3G7jnpkiRUVmiNrhjJgAAAgEOAAAAxCL3G7jnpkiRUVmiNrhjJgAAAixsAAAA\')",
"@odata.etag": "W/\"EQAAABYAAADEIvcbuOemSJFRWaI2uGMmAAAAlo4t\"",
"Id": "AQMkADAwATM0MDAAMS1kZjUyLWQzZDUtMDACLTAwCgBGAAADQ1hAWLJpwk6DZYyOhnclvgcAxCL3G7jnpkiRUVmiNrhjJgAAAgEOAAAAxCL3G7jnpkiRUVmiNrhjJgAAAixsAAAA",
"CreatedDateTime": "2016-04-13T21:25:24Z",
"LastModifiedDateTime": "2016-04-14T19:36:55Z",
"ChangeKey": "EQAAABYAAADEIvcbuOemSJFRWaI2uGMmAAAAlo4t",
"Categories": [],
"ParentFolderId": "AQMkADAwATM0MDAAMS1kZjUyLWQzZDUtMDACLTAwCgAuAAADQ1hAWLJpwk6DZYyOhnclvgEAxCL3G7jnpkiRUVmiNrhjJgAAAgEOAAAA",
"Birthday": "1604-08-14T00:00:00Z",
"FileAs": "Contact, First",
"DisplayName": "First Contact",
"GivenName": "First",
"Initials": null,
"MiddleName": null,
"NickName": null,
"Surname": "Contact",
"Title": null,
"YomiGivenName": null,
"YomiSurname": null,
"YomiCompanyName": null,
"Generation": null,
"EmailAddresses": [{
"Name": "contact.first@email.com",
"Address": "contact.first@email.com"
}, {
"Name": "contact.second@email.com",
"Address": "contact.second@email.com"
}],
"ImAddresses": [],
"JobTitle": null,
"CompanyName": null,
"Department": null,
"OfficeLocation": null,
"Profession": null,
"BusinessHomePage": null,
"AssistantName": null,
"Manager": null,
"HomePhones": [],
"MobilePhone1": null,
"BusinessPhones": [],
"HomeAddress": {
"Street": "address1",
"City": "city",
"State": "state",
"CountryOrRegion": "US",
"PostalCode": "89111"
},
"BusinessAddress": {},
"OtherAddress": {},
"SpouseName": null,
"PersonalNotes": null,
"Children": []
}]
}'
}
describe "fetch_contacts_using_access_token" do
let(:access_token) { "access_token" }
let(:token_type) { "token_type" }
before(:each) do
outlook.instance_variable_set(:@env, {"HTTP_HOST" => "http://example.com"})
end
it "should request the contacts by providing the authorization header with token_type and access_token" do
outlook.should_receive(:https_get) do |host, path, params, headers|
params.should eq({})
headers["Authorization"].should eq("token_type access_token")
self_response
end
outlook.should_receive(:https_get) do |host, path, params, headers|
params.should eq({})
headers["Authorization"].should eq("token_type access_token")
contacts_as_json
end
outlook.fetch_contacts_using_access_token access_token, token_type
end
it "should set requested permissions in the authorization url" do
outlook.authorization_url.should match(/scope=#{Regexp.quote(CGI.escape(permissions))}/)
end
it "should correctly parse id, name and email" do
outlook.should_receive(:https_get).and_return(self_response)
outlook.should_receive(:https_get).and_return(contacts_as_json)
result = outlook.fetch_contacts_using_access_token access_token, token_type
result.size.should be(1)
result.first[:id].should eq('AQMkADAwATM0MDAAMS1kZjUyLWQzZDUtMDACLTAwCgBGAAADQ1hAWLJpwk6DZYyOhnclvgcAxCL3G7jnpkiRUVmiNrhjJgAAAgEOAAAAxCL3G7jnpkiRUVmiNrhjJgAAAixsAAAA')
result.first[:first_name].should eq("First")
result.first[:last_name].should eq("Contact")
result.first[:name].should eq("First Contact")
result.first[:email].should eq("contact.first@email.com")
result.first[:birthday].should eq({ :day => 14, :month => 8, :year => nil })
result.first[:address_1].should eq("address1")
result.first[:address_2].should be_nil
result.first[:city].should eq("city")
result.first[:region].should eq("state")
result.first[:postcode].should eq("89111")
result.first[:country].should eq("US")
result.first[:gender].should be_nil
result.first[:profile_picture].should be_nil
result.first[:relation].should be_nil
end
it "should correctly parse and set logged in user information" do
outlook.should_receive(:https_get).and_return(self_response)
outlook.should_receive(:https_get).and_return(contacts_as_json)
outlook.fetch_contacts_using_access_token access_token, token_type
user = outlook.instance_variable_get(:@env)["omnicontacts.user"]
user.should_not be_nil
user[:id].should eq('00034001-df52-d3d5-0000-000000000000@84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa')
user[:first_name].should eq("Test")
user[:last_name].should eq("User")
user[:name].should eq("Test User")
user[:email].should eq("test.user@outlook.com")
user[:gender].should be_nil
user[:birthday].should be_nil
end
end
end
================================================
FILE: spec/omnicontacts/importer/yahoo_spec.rb
================================================
require "spec_helper"
require "omnicontacts/importer/yahoo"
describe OmniContacts::Importer::Yahoo do
describe "fetch_contacts_from_token_and_verifier" do
let(:self_response) {
'{"profile":{
"guid":"PCLASP523T3E2R5TFMHDW9KWQQ",
"birthdate": "06/21",
"emails":[{"handle":"chrisjohnson@gmail.com", "id":10, "primary":true, "type":"HOME"}, {"handle":"xyz@xyz.com", "id":11, "type":"HOME"}],
"familyName": "Johnson",
"gender":"M",
"givenName":"Chris",
"image":{"imageUrl":"https://avatars.zenfs.com/users/23T3E2R5TFMHDW-AFE-I7lUpIsGQ==.large.png"}
}
}'
}
let(:contacts_as_json) {
'{
"contacts": {
"start":1,
"count":1,
"contact":[
{
"id":10,
"fields":[
{"id":819, "type":"email", "value":"johnny@yahoo.com"},
{"id":806,"type":"name","value":{"givenName":"John","middleName":"","familyName":"Smith"},"editedBy":"OWNER","categories":[]},
{"id":33555343,"type":"guid","value":"7ET6MYV2UQ6VR6CBSNMCLFJIVI"},
{"id":946,"type":"birthday","value":{"day":"22","month":"2","year":"1952"},"editedBy":"OWNER","categories":[]},
{"id":21, "type":"address", "value":{"street":"1313 Trashview Court\nApt. 13", "city":"Nowheresville", "stateOrProvince":"OK", "postalCode":"66666", "country":"", "countryCode":""}, "editedBy":"OWNER", "flags":["HOME"], "categories":[]}
]
}
]
}
}' }
let(:yahoo) { OmniContacts::Importer::Yahoo.new({}, "consumer_key", "consumer_secret") }
before(:each) do
yahoo.instance_variable_set(:@env, {})
end
it "should request the contacts by specifying all required parameters" do
yahoo.should_receive(:fetch_access_token).and_return(["access_token", "access_token_secret", "guid"])
yahoo.should_receive(:https_get) do |host, path, params|
params[:format].should eq("json")
params[:oauth_consumer_key].should eq("consumer_key")
params[:oauth_nonce].should_not be_nil
params[:oauth_signature_method].should eq("HMAC-SHA1")
params[:oauth_timestamp].should_not be_nil
params[:oauth_token].should eq("access_token")
params[:oauth_version].should eq("1.0")
self_response
end
yahoo.should_receive(:https_get) do |host, path, params|
params[:format].should eq("json")
params[:oauth_consumer_key].should eq("consumer_key")
params[:oauth_nonce].should_not be_nil
params[:oauth_signature_method].should eq("HMAC-SHA1")
params[:oauth_timestamp].should_not be_nil
params[:oauth_token].should eq("access_token")
params[:oauth_version].should eq("1.0")
contacts_as_json
end
yahoo.fetch_contacts_from_token_and_verifier "auth_token", "auth_token_secret", "oauth_verifier"
end
it "should correctly parse id, name,email,gender, birthday, snailmail address, image source and relation for contact and logged in user" do
yahoo.should_receive(:fetch_access_token).and_return(["access_token", "access_token_secret", "guid"])
yahoo.should_receive(:https_get).and_return(self_response)
yahoo.should_receive(:https_get).and_return(contacts_as_json)
result = yahoo.fetch_contacts_from_token_and_verifier "auth_token", "auth_token_secret", "oauth_verifier"
result.size.should be(1)
result.first[:id].should eq('10')
result.first[:first_name].should eq('John')
result.first[:last_name].should eq('Smith')
result.first[:name].should eq("John Smith")
result.first[:email].should eq("johnny@yahoo.com")
result.first[:gender].should be_nil
result.first[:birthday].should eq({:day=>22, :month=>2, :year=>1952})
result.first[:address_1].should eq('1313 Trashview Court')
result.first[:address_2].should eq('Apt. 13')
result.first[:city].should eq('Nowheresville')
result.first[:region].should eq('OK')
result.first[:postcode].should eq('66666')
result.first[:relation].should be_nil
end
it "should return an empty list of contacts" do
empty_contacts_list = '{"contacts": {"start":0, "count":0}}'
yahoo.should_receive(:fetch_access_token).and_return(["access_token", "access_token_secret", "guid"])
yahoo.should_receive(:https_get).and_return(self_response)
yahoo.should_receive(:https_get).and_return(empty_contacts_list)
result = yahoo.fetch_contacts_from_token_and_verifier "auth_token", "auth_token_secret", "oauth_verifier"
result.should be_empty
end
it "should correctly parse and set logged in user information" do
yahoo.should_receive(:fetch_access_token).and_return(["access_token", "access_token_secret", "guid"])
yahoo.should_receive(:https_get).and_return(self_response)
yahoo.should_receive(:https_get).and_return(contacts_as_json)
yahoo.fetch_contacts_from_token_and_verifier "auth_token", "auth_token_secret", "oauth_verifier"
user = yahoo.instance_variable_get(:@env)["omnicontacts.user"]
user.should_not be_nil
user[:id].should eq('PCLASP523T3E2R5TFMHDW9KWQQ')
user[:first_name].should eq('Chris')
user[:last_name].should eq('Johnson')
user[:name].should eq('Chris Johnson')
user[:gender].should eq('male')
user[:birthday].should eq({:day=>21, :month=>06, :year=>nil})
user[:email].should eq('chrisjohnson@gmail.com')
user[:profile_picture].should eq('https://avatars.zenfs.com/users/23T3E2R5TFMHDW-AFE-I7lUpIsGQ==.large.png')
end
end
end
================================================
FILE: spec/omnicontacts/integration_test_spec.rb
================================================
require "spec_helper"
require "omnicontacts/integration_test"
describe IntegrationTest do
context "mock_initial_request" do
it "should redirect to the provider's redirect_path" do
provider = mock
redirect_path = "/redirect_path"
provider.stub(:redirect_path => redirect_path)
IntegrationTest.instance.mock_authorization_from_user(provider)[1]["location"].should eq(redirect_path)
end
end
context "mock_callback" do
before(:each) {
@env = {}
@provider = self.mock
@provider.stub(:class_name => "test")
IntegrationTest.instance.clear_mocks
}
it "should return an empty contacts list" do
IntegrationTest.instance.mock_fetch_contacts(@provider).should be_empty
end
it "should return a configured list of contacts " do
contacts = [:name => 'John Doe', :email => 'john@doe.com']
IntegrationTest.instance.mock('test', contacts)
result = IntegrationTest.instance.mock_fetch_contacts(@provider)
result.size.should be(1)
result.first[:email].should eq(contacts.first[:email])
result.first[:name].should eq(contacts.first[:name])
end
it "should return a single element list of contacts " do
contact = {:name => 'John Doe', :email => 'john@doe.com'}
IntegrationTest.instance.mock('test', contact)
result = IntegrationTest.instance.mock_fetch_contacts(@provider)
result.size.should be(1)
result.first[:email].should eq(contact[:email])
result.first[:name].should eq(contact[:name])
end
it "should return a user" do
contact = {:name => 'John Doe', :email => 'john@doe.com'}
user = {:name => 'Mary Smith', :email => 'mary@smith.com'}
IntegrationTest.instance.mock('test', contact, user)
result = IntegrationTest.instance.mock_fetch_user(@provider)
result[:email].should eq(user[:email])
result[:name].should eq(user[:name])
end
it "should throw an exception" do
IntegrationTest.instance.mock('test', :some_error)
expect {IntegrationTest.instance.mock_fetch_contacts(@provider)}.to raise_error
end
end
end
================================================
FILE: spec/omnicontacts/middleware/base_oauth_spec.rb
================================================
require "spec_helper"
require "omnicontacts"
require "omnicontacts/middleware/base_oauth"
describe OmniContacts::Middleware::BaseOAuth do
before(:all) do
class TestProvider < OmniContacts::Middleware::BaseOAuth
def initialize app, consumer_key, consumer_secret, options = {}
super app, options
end
def redirect_path
"#{ MOUNT_PATH }testprovider/callback"
end
def self.mock_session
@mock_session ||= {}
end
def session
TestProvider.mock_session
end
end
OmniContacts.integration_test.enabled = true
end
let(:app) {
Rack::Builder.new do |b|
b.use TestProvider, "consumer_id", "consumer_secret"
b.run lambda { |env| [200, {"Content-Type" => "text/html"}, ["Hello World"]] }
end.to_app
}
it "should return a preconfigured list of contacts" do
OmniContacts.integration_test.mock(:testprovider, :email => "user@example.com")
get "#{ MOUNT_PATH }testprovider"
get "#{ MOUNT_PATH }testprovider/callback"
last_request.env["omnicontacts.contacts"].first[:email].should eq("user@example.com")
end
it "should redirect to failure url" do
OmniContacts.integration_test.mock(:testprovider, "some_error" )
get "#{ MOUNT_PATH }testprovider"
get "#{MOUNT_PATH }testprovider/callback"
last_response.should be_redirect
last_response.headers["location"].should eq("#{ MOUNT_PATH }failure?error_message=internal_error&importer=testprovider")
end
it "should pass through state query params to the failure url" do
OmniContacts.integration_test.mock(:testprovider, "some_error" )
get "#{MOUNT_PATH }testprovider/callback?state=/parent/resource/id"
last_response.headers["location"].should eq("#{ MOUNT_PATH }failure?error_message=internal_error&importer=testprovider&state=/parent/resource/id")
end
it "should store request params in session" do
OmniContacts.integration_test.mock(:testprovider, :email => "user@example.com")
get "#{ MOUNT_PATH }testprovider?foo=bar"
app.session['omnicontacts.params'].should eq({'foo' => 'bar'})
end
it "should pass the params from session to callback environment " do
OmniContacts.integration_test.mock(:testprovider, :email => "user@example.com")
app.session.merge!({'omnicontacts.params' => {'foo' => 'bar'}})
get "#{MOUNT_PATH }testprovider/callback?state=/parent/resource/id"
last_request.env["omnicontacts.params"].should eq({'foo' => 'bar'})
end
it "should pass the params from session on failure" do
OmniContacts.integration_test.mock(:testprovider, "some_error" )
get "#{ MOUNT_PATH }testprovider"
app.session.merge!({'omnicontacts.params' => {'foo' => 'bar'}})
get "#{MOUNT_PATH }testprovider/callback"
last_response.should be_redirect
last_response.headers["location"].should be_include("foo=bar")
end
after(:all) do
OmniContacts.integration_test.enabled = false
OmniContacts.integration_test.clear_mocks
end
end
================================================
FILE: spec/omnicontacts/middleware/oauth1_spec.rb
================================================
require "spec_helper"
require "omnicontacts/middleware/oauth1"
describe OmniContacts::Middleware::OAuth1 do
before(:all) do
class OAuth1Middleware < OmniContacts::Middleware::OAuth1
def self.mock_auth_token_resp
@mock_auth_token_resp ||= Object.new
end
def fetch_authorization_token
OAuth1Middleware.mock_auth_token_resp.body
end
def authorization_url auth_token
"http://www.example.com"
end
def fetch_contacts_from_token_and_verifier oauth_token, ouath_token_secret, oauth_verifier
[{:name => "John Doe", :email => "john@example.com"}]
end
def self.mock_session
@mock_session ||= {}
end
def session
OAuth1Middleware.mock_session
end
end
end
let(:app) {
Rack::Builder.new do |b|
b.use OAuth1Middleware, "consumer_id", "consumer_secret"
b.run lambda { |env| [200, {"Content-Type" => "text/html"}, ["Hello World"]] }
end.to_app
}
context "visiting the listening path" do
it "should save the authorization token and redirect to the authorization url" do
OAuth1Middleware.mock_auth_token_resp.should_receive(:body).and_return(["auth_token", "auth_token_secret"])
get "#{ MOUNT_PATH }oauth1middleware"
last_response.should be_redirect
last_response.headers['location'].should eq("http://www.example.com")
end
it "should pass through state query params visiting the listening path" do
OAuth1Middleware.mock_auth_token_resp.should_receive(:body).and_return(["auth_token", "auth_token_secret"])
get "#{ MOUNT_PATH }oauth1middleware?state=/parent/resource/id"
last_response.headers['location'].should eq("http://www.example.com?state=/parent/resource/id")
end
it "should redirect to failure url if fetching the request token does not succeed" do
OAuth1Middleware.mock_auth_token_resp.should_receive(:body).and_raise("Request failed")
get "contacts/oauth1middleware"
last_response.should be_redirect
last_response.headers["location"].should eq("#{ MOUNT_PATH }failure?error_message=internal_error&importer=oauth1middleware")
end
end
context "visiting the callback url after authorization" do
it "should return the list of contacts" do
OAuth1Middleware.mock_session.should_receive(:[]).and_return("oauth_token_secret")
get "#{ MOUNT_PATH }oauth1middleware/callback?oauth_token=token&oauth_verifier=verifier"
last_response.should be_ok
last_request.env["omnicontacts.contacts"].size.should be(1)
end
it "should redirect to failure url if oauth_token_secret is not found in the session" do
OAuth1Middleware.mock_session.should_receive(:[]).and_return(nil)
get "#{ MOUNT_PATH }oauth1middleware/callback?oauth_token=token&oauth_verifier=verifier"
last_response.should be_redirect
last_response.headers["location"].should eq("#{ MOUNT_PATH }failure?error_message=not_authorized&importer=oauth1middleware")
end
end
end
================================================
FILE: spec/omnicontacts/middleware/oauth2_spec.rb
================================================
require "spec_helper"
require "omnicontacts/middleware/oauth2"
describe OmniContacts::Middleware::OAuth2 do
before(:all) do
class OAuth2Middleware < OmniContacts::Middleware::OAuth2
def authorization_url
"http://www.example.com"
end
def redirect_path
"/redirect_path"
end
def self.mock_session
@mock_session ||= {}
end
def session
OAuth2Middleware.mock_session
end
def fetch_access_token code
["access_token", "token_type", "token_refresh"]
end
def fetch_contacts_using_access_token token, token_type
[{:name => "John Doe", :email => "john@example.com"}]
end
end
end
let(:app) {
Rack::Builder.new do |b|
b.use OAuth2Middleware, "client_id", "client_secret"
b.run lambda { |env| [200, {"Content-Type" => "text/html"}, ["Hello World"]] }
end.to_app
}
context "visiting the listening path" do
it "should redirect to authorization site when visiting the listening path" do
get "#{ MOUNT_PATH }oauth2middleware"
last_response.should be_redirect
last_response.headers['location'].should eq("http://www.example.com")
end
it "should pass through state query params visiting the listening path" do
get "#{ MOUNT_PATH }oauth2middleware?state=/parent/resource/id"
last_response.headers['location'].should eq("http://www.example.com?state=/parent/resource/id")
end
end
context "visiting the callback url after authorization" do
it "should fetch the contacts" do
get '/redirect_path?code=ABC'
last_response.should be_ok
last_request.env["omnicontacts.contacts"].size.should be(1)
end
it "should redirect to failure page because user did not allow access to contacts list" do
get '/redirect_path?error=not_authorized'
last_response.should be_redirect
last_response.headers["location"].should eq("#{ MOUNT_PATH }failure?error_message=not_authorized&importer=oauth2middleware")
end
end
end
================================================
FILE: spec/omnicontacts/parse_utils_spec.rb
================================================
require "spec_helper"
require "omnicontacts/parse_utils"
include OmniContacts::ParseUtils
describe OmniContacts::ParseUtils do
describe "normalize_name" do
it "should remove trailing spaces" do
result = normalize_name("John ")
result.should eq("John")
end
it "should preserve capitalization" do
result = normalize_name("John McDonald")
result.should eq("John McDonald")
end
end
describe "full_name" do
it "should preserve capitalization" do
result = full_name("John", "McDonald")
result.should eq("John McDonald")
end
it "returns only first name if no last name present" do
result = full_name("John", nil)
result.should eq("John")
end
it "returns only last name if no first name present" do
result = full_name(nil, "McDonald")
result.should eq("McDonald")
end
end
describe "birthday_format" do
it "returns nil if (!year && !month) || (!year && !day)" do
result = birthday_format(nil, Date.today, nil)
result.should eq(nil)
result = birthday_format(Date.today.month, nil, nil)
result.should eq(nil)
end
end
describe "email_to_name" do
it "create a probable name from email" do
username_or_email = "foo.bar@test.com"
result = email_to_name(username_or_email)
result.should eq ['foo','bar',"foo bar"]
end
end
end
================================================
FILE: spec/spec_helper.rb
================================================
require "simplecov"
SimpleCov.start do
add_filter "spec/"
end
require "rspec"
require "rack/test"
RSpec.configure do |config|
config.include Rack::Test::Methods
end
MOUNT_PATH = "/contacts/"
gitextract_m1xfdr8k/
├── .gitignore
├── Gemfile
├── README.md
├── Rakefile
├── lib/
│ ├── omnicontacts/
│ │ ├── authorization/
│ │ │ ├── oauth1.rb
│ │ │ └── oauth2.rb
│ │ ├── builder.rb
│ │ ├── http_utils.rb
│ │ ├── importer/
│ │ │ ├── facebook.rb
│ │ │ ├── gmail.rb
│ │ │ ├── hotmail.rb
│ │ │ ├── linkedin.rb
│ │ │ ├── outlook.rb
│ │ │ └── yahoo.rb
│ │ ├── importer.rb
│ │ ├── integration_test.rb
│ │ ├── middleware/
│ │ │ ├── base_oauth.rb
│ │ │ ├── oauth1.rb
│ │ │ └── oauth2.rb
│ │ └── parse_utils.rb
│ └── omnicontacts.rb
├── omnicontacts.gemspec
└── spec/
├── omnicontacts/
│ ├── authorization/
│ │ ├── oauth1_spec.rb
│ │ └── oauth2_spec.rb
│ ├── http_utils_spec.rb
│ ├── importer/
│ │ ├── facebook_spec.rb
│ │ ├── gmail_spec.rb
│ │ ├── hotmail_spec.rb
│ │ ├── linkedin_spec.rb
│ │ ├── outlook_spec.rb
│ │ └── yahoo_spec.rb
│ ├── integration_test_spec.rb
│ ├── middleware/
│ │ ├── base_oauth_spec.rb
│ │ ├── oauth1_spec.rb
│ │ └── oauth2_spec.rb
│ └── parse_utils_spec.rb
└── spec_helper.rb
SYMBOL INDEX (187 symbols across 22 files)
FILE: lib/omnicontacts.rb
type OmniContacts (line 1) | module OmniContacts
class AuthorizationError (line 11) | class AuthorizationError < RuntimeError
function integration_test (line 15) | def self.integration_test
FILE: lib/omnicontacts/authorization/oauth1.rb
type OmniContacts (line 15) | module OmniContacts
type Authorization (line 16) | module Authorization
type OAuth1 (line 17) | module OAuth1
function fetch_authorization_token (line 24) | def fetch_authorization_token
function request_token_req_params (line 31) | def request_token_req_params
function random_string (line 43) | def random_string
function timestamp (line 47) | def timestamp
function values_from_query_string (line 51) | def values_from_query_string query_string, keys_to_extract
function authorization_url (line 65) | def authorization_url auth_token
function fetch_access_token (line 73) | def fetch_access_token auth_token, auth_token_secret, auth_verifie...
function access_token_req_params (line 80) | def access_token_req_params auth_token, auth_token_secret, auth_ve...
function oauth_signature (line 108) | def oauth_signature method, url, params, secret
FILE: lib/omnicontacts/authorization/oauth2.rb
type OmniContacts (line 15) | module OmniContacts
type Authorization (line 16) | module Authorization
type OAuth2 (line 17) | module OAuth2
function authorization_url (line 22) | def authorization_url
function authorize_url_params (line 28) | def authorize_url_params
function fetch_access_token (line 42) | def fetch_access_token code
function token_req_params (line 48) | def token_req_params code
function access_token_from_response (line 58) | def access_token_from_response response
function refresh_access_token (line 70) | def refresh_access_token refresh_token
function refresh_token_req_params (line 76) | def refresh_token_req_params refresh_token
FILE: lib/omnicontacts/builder.rb
type OmniContacts (line 3) | module OmniContacts
class Builder (line 4) | class Builder < Rack::Builder
method initialize (line 5) | def initialize(app, &block)
method rack14? (line 14) | def rack14?
method importer (line 19) | def importer importer, *args
method call (line 26) | def call env
FILE: lib/omnicontacts/http_utils.rb
type OmniContacts (line 7) | module OmniContacts
type HTTPUtils (line 8) | module HTTPUtils
function query_string_to_map (line 14) | def query_string_to_map query_string
function to_query_string (line 22) | def to_query_string map
function encode (line 29) | def encode to_encode
function host_url_from_rack_env (line 37) | def host_url_from_rack_env env
function scheme (line 43) | def scheme env
function http_get (line 62) | def http_get host, path, params
function https_post (line 69) | def https_post host, path, params
function https_get (line 77) | def https_get host, path, params, headers = nil
function https_connection (line 83) | def https_connection (host)
function process_http_response (line 95) | def process_http_response response
FILE: lib/omnicontacts/importer.rb
type OmniContacts (line 1) | module OmniContacts
type Importer (line 2) | module Importer
FILE: lib/omnicontacts/importer/facebook.rb
type OmniContacts (line 5) | module OmniContacts
type Importer (line 6) | module Importer
class Facebook (line 7) | class Facebook < Middleware::OAuth2
method initialize (line 12) | def initialize *args
method fetch_contacts_using_access_token (line 24) | def fetch_contacts_using_access_token access_token, access_token_s...
method fetch_current_user (line 39) | def fetch_current_user access_token
method extract_spouse_id (line 47) | def extract_spouse_id response
method contacts_from_response (line 56) | def contacts_from_response(spouse_response, family_response, frien...
method create_contact_element (line 81) | def create_contact_element contact_info
method image_url (line 96) | def image_url fb_id
method escape_windows_format (line 100) | def escape_windows_format value
method birthday (line 104) | def birthday dob
method current_user (line 110) | def current_user me
FILE: lib/omnicontacts/importer/gmail.rb
type OmniContacts (line 4) | module OmniContacts
type Importer (line 5) | module Importer
class Gmail (line 6) | class Gmail < Middleware::OAuth2
method initialize (line 11) | def initialize *args
method fetch_contacts_using_access_token (line 24) | def fetch_contacts_using_access_token access_token, token_type
method fetch_current_user (line 30) | def fetch_current_user access_token, token_type
method contacts_req_params (line 38) | def contacts_req_params
method contacts_req_headers (line 42) | def contacts_req_headers token, token_type
method contacts_from_response (line 46) | def contacts_from_response(response_as_json, access_token)
method current_user (line 185) | def current_user me, access_token, token_type
method birthday (line 195) | def birthday dob
method contact_id (line 202) | def contact_id(profile_url)
FILE: lib/omnicontacts/importer/hotmail.rb
type OmniContacts (line 5) | module OmniContacts
type Importer (line 6) | module Importer
class Hotmail (line 7) | class Hotmail < Middleware::OAuth2
method initialize (line 12) | def initialize app, client_id, client_secret, options ={}
method fetch_contacts_using_access_token (line 23) | def fetch_contacts_using_access_token access_token, access_token_s...
method fetch_current_user (line 29) | def fetch_current_user access_token
method contacts_from_response (line 37) | def contacts_from_response response_as_json
method parse_email (line 57) | def parse_email(emails)
method current_user (line 62) | def current_user me
method image_url (line 74) | def image_url hotmail_id
method escape_windows_format (line 78) | def escape_windows_format value
method valid_email? (line 82) | def valid_email? value
FILE: lib/omnicontacts/importer/linkedin.rb
type OmniContacts (line 4) | module OmniContacts
type Importer (line 5) | module Importer
class Linkedin (line 6) | class Linkedin < Middleware::OAuth2
method initialize (line 11) | def initialize *args
method fetch_contacts_using_access_token (line 24) | def fetch_contacts_using_access_token access_token, token_type
method contacts_req_params (line 32) | def contacts_req_params
method contacts_req_headers (line 36) | def contacts_req_headers token, token_type
method contacts_from_response (line 40) | def contacts_from_response response_as_json
method authorize_url_params (line 56) | def authorize_url_params
FILE: lib/omnicontacts/importer/outlook.rb
type OmniContacts (line 6) | module OmniContacts
type Importer (line 7) | module Importer
class Outlook (line 8) | class Outlook < Middleware::OAuth2
method initialize (line 13) | def initialize app, client_id, client_secret, options ={}
method fetch_contacts_using_access_token (line 24) | def fetch_contacts_using_access_token access_token, token_type
method fetch_current_user (line 30) | def fetch_current_user access_token, token_type
method contacts_req_headers (line 38) | def contacts_req_headers token, token_type
method current_user (line 42) | def current_user me
method contacts_from_response (line 59) | def contacts_from_response response_as_json
method empty_contact (line 87) | def empty_contact
method parse_email (line 93) | def parse_email emails
method birthday (line 100) | def birthday dob
method valid_email? (line 107) | def valid_email? value
FILE: lib/omnicontacts/importer/yahoo.rb
type OmniContacts (line 5) | module OmniContacts
type Importer (line 6) | module Importer
class Yahoo (line 7) | class Yahoo < Middleware::OAuth1
method initialize (line 12) | def initialize *args
method fetch_contacts_from_token_and_verifier (line 21) | def fetch_contacts_from_token_and_verifier auth_token, auth_token_...
method fetch_current_user (line 29) | def fetch_current_user access_token, access_token_secret, guid
method contacts_req_params (line 38) | def contacts_req_params access_token, access_token_secret, contact...
method contacts_from_response (line 53) | def contacts_from_response response_as_json
method image_url (line 114) | def image_url yahoo_id
method parse_email (line 118) | def parse_email(emails)
method birthday (line 136) | def birthday dob
method gender (line 144) | def gender g
method my_image (line 149) | def my_image img
method current_user (line 154) | def current_user me
FILE: lib/omnicontacts/integration_test.rb
class IntegrationTest (line 3) | class IntegrationTest
method initialize (line 8) | def initialize
method clear_mocks (line 13) | def clear_mocks
method mock (line 18) | def mock provider, contacts, user = {}
method mock_authorization_from_user (line 23) | def mock_authorization_from_user provider
method mock_fetch_contacts (line 27) | def mock_fetch_contacts provider
method mock_fetch_user (line 38) | def mock_fetch_user provider
FILE: lib/omnicontacts/middleware/base_oauth.rb
type OmniContacts (line 8) | module OmniContacts
type Middleware (line 9) | module Middleware
class BaseOAuth (line 10) | class BaseOAuth
method initialize (line 14) | def initialize app, options
method class_name (line 20) | def class_name
method call (line 34) | def call env
method test_mode? (line 49) | def test_mode?
method handle_initial_request (line 53) | def handle_initial_request
method handle_callback (line 63) | def handle_callback
method set_current_user (line 75) | def set_current_user user
method execute_and_rescue_exceptions (line 82) | def execute_and_rescue_exceptions
method handle_error (line 92) | def handle_error error_type, exception
method session (line 100) | def session
method logger (line 105) | def logger
method base_prop_name (line 109) | def base_prop_name
method append_request_params (line 113) | def append_request_params(target_url)
method append_state_query (line 122) | def append_state_query(target_url)
FILE: lib/omnicontacts/middleware/oauth1.rb
type OmniContacts (line 10) | module OmniContacts
type Middleware (line 11) | module Middleware
class OAuth1 (line 12) | class OAuth1 < BaseOAuth
method initialize (line 17) | def initialize app, consumer_key, consumer_secret, options = {}
method callback (line 25) | def callback
method request_authorization_from_user (line 34) | def request_authorization_from_user
method token_secret_prop_name (line 41) | def token_secret_prop_name oauth_token
method redirect_to_authorization_site (line 45) | def redirect_to_authorization_site auth_token
method fetch_contacts (line 56) | def fetch_contacts
FILE: lib/omnicontacts/middleware/oauth2.rb
type OmniContacts (line 11) | module OmniContacts
type Middleware (line 12) | module Middleware
class OAuth2 (line 13) | class OAuth2 < BaseOAuth
method initialize (line 18) | def initialize app, client_id, client_secret, options ={}
method request_authorization_from_user (line 26) | def request_authorization_from_user
method redirect_uri (line 31) | def redirect_uri
method fetch_contacts (line 44) | def fetch_contacts
method refresh_token_prop_name (line 61) | def refresh_token_prop_name code
FILE: lib/omnicontacts/parse_utils.rb
type OmniContacts (line 1) | module OmniContacts
type ParseUtils (line 2) | module ParseUtils
function birthday_format (line 5) | def birthday_format month, day, year
function normalize_name (line 12) | def normalize_name name
function full_name (line 21) | def full_name first_name, last_name
function email_to_name (line 29) | def email_to_name username_or_email
function image_url_from_email (line 41) | def image_url_from_email email
FILE: spec/omnicontacts/authorization/oauth1_spec.rb
class OAuth1TestClass (line 8) | class OAuth1TestClass
FILE: spec/omnicontacts/authorization/oauth2_spec.rb
class OAuth2TestClass (line 8) | class OAuth2TestClass
FILE: spec/omnicontacts/middleware/base_oauth_spec.rb
class TestProvider (line 8) | class TestProvider < OmniContacts::Middleware::BaseOAuth
method initialize (line 9) | def initialize app, consumer_key, consumer_secret, options = {}
method redirect_path (line 13) | def redirect_path
method mock_session (line 17) | def self.mock_session
method session (line 21) | def session
FILE: spec/omnicontacts/middleware/oauth1_spec.rb
class OAuth1Middleware (line 7) | class OAuth1Middleware < OmniContacts::Middleware::OAuth1
method mock_auth_token_resp (line 8) | def self.mock_auth_token_resp
method fetch_authorization_token (line 12) | def fetch_authorization_token
method authorization_url (line 16) | def authorization_url auth_token
method fetch_contacts_from_token_and_verifier (line 20) | def fetch_contacts_from_token_and_verifier oauth_token, ouath_token_se...
method mock_session (line 24) | def self.mock_session
method session (line 28) | def session
FILE: spec/omnicontacts/middleware/oauth2_spec.rb
class OAuth2Middleware (line 7) | class OAuth2Middleware < OmniContacts::Middleware::OAuth2
method authorization_url (line 8) | def authorization_url
method redirect_path (line 12) | def redirect_path
method mock_session (line 16) | def self.mock_session
method session (line 20) | def session
method fetch_access_token (line 24) | def fetch_access_token code
method fetch_contacts_using_access_token (line 28) | def fetch_contacts_using_access_token token, token_type
Condensed preview — 37 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (144K chars).
[
{
"path": ".gitignore",
"chars": 48,
"preview": "coverage\n*.iml\n.idea\n.rvmrc\n.DS_Store\nnbproject\n"
},
{
"path": "Gemfile",
"chars": 37,
"preview": "source 'http://rubygems.org'\n\ngemspec"
},
{
"path": "README.md",
"chars": 13137,
"preview": "# OmniContacts\n\nInspired by the popular OmniAuth, OmniContacts is a library that enables users of an application to impo"
},
{
"path": "Rakefile",
"chars": 159,
"preview": "require 'bundler'\nrequire 'rspec/core/rake_task'\n\nBundler::GemHelper.install_tasks\nRSpec::Core::RakeTask.new(:spec)\ntask"
},
{
"path": "lib/omnicontacts/authorization/oauth1.rb",
"chars": 4730,
"preview": "require \"omnicontacts/http_utils\"\nrequire \"base64\"\n\n# This module represent a OAuth 1.0 Client.\n#\n# Classes including th"
},
{
"path": "lib/omnicontacts/authorization/oauth2.rb",
"chars": 2753,
"preview": "require \"omnicontacts/http_utils\"\nrequire \"json\"\n\n# This module represents an OAuth 2.0 client.\n#\n# Classes including th"
},
{
"path": "lib/omnicontacts/builder.rb",
"chars": 654,
"preview": "require \"omnicontacts\"\n\nmodule OmniContacts\n class Builder < Rack::Builder\n def initialize(app, &block)\n if rac"
},
{
"path": "lib/omnicontacts/http_utils.rb",
"chars": 3199,
"preview": "require \"net/http\"\nrequire \"net/https\"\nrequire \"cgi\"\nrequire \"openssl\"\n\n# This module contains a set of utility methods "
},
{
"path": "lib/omnicontacts/importer/facebook.rb",
"chars": 4915,
"preview": "require \"omnicontacts/parse_utils\"\nrequire \"omnicontacts/middleware/oauth2\"\nrequire \"json\"\n\nmodule OmniContacts\n module"
},
{
"path": "lib/omnicontacts/importer/gmail.rb",
"chars": 9759,
"preview": "require \"omnicontacts/parse_utils\"\nrequire \"omnicontacts/middleware/oauth2\"\n\nmodule OmniContacts\n module Importer\n c"
},
{
"path": "lib/omnicontacts/importer/hotmail.rb",
"chars": 3518,
"preview": "require \"omnicontacts/middleware/oauth2\"\nrequire \"omnicontacts/parse_utils\"\nrequire \"json\"\n\nmodule OmniContacts\n module"
},
{
"path": "lib/omnicontacts/importer/linkedin.rb",
"chars": 2055,
"preview": "require \"omnicontacts/parse_utils\"\nrequire \"omnicontacts/middleware/oauth2\"\n\nmodule OmniContacts\n module Importer\n c"
},
{
"path": "lib/omnicontacts/importer/outlook.rb",
"chars": 4213,
"preview": "require \"omnicontacts/middleware/oauth2\"\nrequire \"omnicontacts/parse_utils\"\nrequire \"json\"\n\n# API Docs: https://msdn.mic"
},
{
"path": "lib/omnicontacts/importer/yahoo.rb",
"chars": 6913,
"preview": "require \"omnicontacts/parse_utils\"\nrequire \"omnicontacts/middleware/oauth1\"\nrequire \"json\"\n\nmodule OmniContacts\n module"
},
{
"path": "lib/omnicontacts/importer.rb",
"chars": 376,
"preview": "module OmniContacts\n module Importer\n\n autoload :Gmail, \"omnicontacts/importer/gmail\"\n autoload :Yahoo, \"omnicont"
},
{
"path": "lib/omnicontacts/integration_test.rb",
"chars": 833,
"preview": "require 'singleton'\n\nclass IntegrationTest\n include Singleton\n\n attr_accessor :enabled\n\n def initialize\n enabled ="
},
{
"path": "lib/omnicontacts/middleware/base_oauth.rb",
"chars": 4246,
"preview": "# This class contains the common behavior for middlewares\n# implementing either versions of OAuth.\n#\n# Extending classes"
},
{
"path": "lib/omnicontacts/middleware/oauth1.rb",
"chars": 2634,
"preview": "require \"omnicontacts/authorization/oauth1\"\nrequire \"omnicontacts/middleware/base_oauth\"\n\n# This class is an OAuth 1.0 R"
},
{
"path": "lib/omnicontacts/middleware/oauth2.rb",
"chars": 2569,
"preview": "require \"omnicontacts/authorization/oauth2\"\nrequire \"omnicontacts/middleware/base_oauth\"\n\n# This class is a OAuth 2 Rack"
},
{
"path": "lib/omnicontacts/parse_utils.rb",
"chars": 2012,
"preview": "module OmniContacts\n module ParseUtils\n\n # return has of birthday day, month and year\n def birthday_format month,"
},
{
"path": "lib/omnicontacts.rb",
"chars": 346,
"preview": "module OmniContacts\n \n VERSION = \"0.3.10\"\n\n MOUNT_PATH = \"/contacts/\"\n\n autoload :Builder, \"omnicontacts/builder\"\n "
},
{
"path": "omnicontacts.gemspec",
"chars": 1015,
"preview": "# encoding: utf-8\nrequire File.expand_path('../lib/omnicontacts', __FILE__)\n\nGem::Specification.new do |gem|\n gem.name "
},
{
"path": "spec/omnicontacts/authorization/oauth1_spec.rb",
"chars": 3686,
"preview": "require \"spec_helper\"\nrequire \"omnicontacts/authorization/oauth1\"\n\ndescribe OmniContacts::Authorization::OAuth1 do\n\n be"
},
{
"path": "spec/omnicontacts/authorization/oauth2_spec.rb",
"chars": 3690,
"preview": "require \"spec_helper\"\nrequire \"omnicontacts/authorization/oauth2\"\n\ndescribe OmniContacts::Authorization::OAuth2 do\n\n be"
},
{
"path": "spec/omnicontacts/http_utils_spec.rb",
"chars": 3006,
"preview": "require \"spec_helper\"\nrequire \"omnicontacts/http_utils\"\n\ndescribe OmniContacts::HTTPUtils do\n\n describe \"to_query_strin"
},
{
"path": "spec/omnicontacts/importer/facebook_spec.rb",
"chars": 4747,
"preview": "require \"spec_helper\"\nrequire \"omnicontacts/importer/facebook\"\n\ndescribe OmniContacts::Importer::Facebook do\n\n let(:fac"
},
{
"path": "spec/omnicontacts/importer/gmail_spec.rb",
"chars": 19631,
"preview": "require \"spec_helper\"\nrequire \"omnicontacts/importer/gmail\"\n\ndescribe OmniContacts::Importer::Gmail do\n let(:gmail) { O"
},
{
"path": "spec/omnicontacts/importer/hotmail_spec.rb",
"chars": 3712,
"preview": "require \"spec_helper\"\nrequire \"omnicontacts/importer/hotmail\"\n\ndescribe OmniContacts::Importer::Hotmail do\n\n let(:permi"
},
{
"path": "spec/omnicontacts/importer/linkedin_spec.rb",
"chars": 2741,
"preview": "require \"spec_helper\"\nrequire \"omnicontacts/importer/linkedin\"\n\ndescribe OmniContacts::Importer::Linkedin do\n\n let(:lin"
},
{
"path": "spec/omnicontacts/importer/outlook_spec.rb",
"chars": 6117,
"preview": "require \"spec_helper\"\nrequire \"omnicontacts/importer/outlook\"\n\ndescribe OmniContacts::Importer::Outlook do\n\n let(:permi"
},
{
"path": "spec/omnicontacts/importer/yahoo_spec.rb",
"chars": 5766,
"preview": "require \"spec_helper\"\nrequire \"omnicontacts/importer/yahoo\"\n\ndescribe OmniContacts::Importer::Yahoo do\n\n describe \"fetc"
},
{
"path": "spec/omnicontacts/integration_test_spec.rb",
"chars": 2137,
"preview": "require \"spec_helper\"\nrequire \"omnicontacts/integration_test\"\n\ndescribe IntegrationTest do\n\n context \"mock_initial_requ"
},
{
"path": "spec/omnicontacts/middleware/base_oauth_spec.rb",
"chars": 3018,
"preview": "require \"spec_helper\"\nrequire \"omnicontacts\"\nrequire \"omnicontacts/middleware/base_oauth\"\n\ndescribe OmniContacts::Middle"
},
{
"path": "spec/omnicontacts/middleware/oauth1_spec.rb",
"chars": 3033,
"preview": "require \"spec_helper\"\nrequire \"omnicontacts/middleware/oauth1\"\n\ndescribe OmniContacts::Middleware::OAuth1 do\n\n before(:"
},
{
"path": "spec/omnicontacts/middleware/oauth2_spec.rb",
"chars": 2048,
"preview": "require \"spec_helper\"\nrequire \"omnicontacts/middleware/oauth2\"\n\ndescribe OmniContacts::Middleware::OAuth2 do\n\n before(:"
},
{
"path": "spec/omnicontacts/parse_utils_spec.rb",
"chars": 1393,
"preview": "require \"spec_helper\"\nrequire \"omnicontacts/parse_utils\"\n\ninclude OmniContacts::ParseUtils\n\ndescribe OmniContacts::Parse"
},
{
"path": "spec/spec_helper.rb",
"chars": 197,
"preview": "require \"simplecov\"\nSimpleCov.start do\n add_filter \"spec/\"\nend\n\nrequire \"rspec\"\nrequire \"rack/test\"\nRSpec.configure do "
}
]
About this extraction
This page contains the full source code of the Diego81/omnicontacts GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 37 files (131.9 KB), approximately 34.9k tokens, and a symbol index with 187 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.