omnigroupcontacts 0.3.10 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +39 -0
  5. data/README.md +132 -0
  6. data/Rakefile +7 -0
  7. data/lib/omnigroupcontacts.rb +19 -0
  8. data/lib/omnigroupcontacts/authorization/oauth1.rb +122 -0
  9. data/lib/omnigroupcontacts/authorization/oauth2.rb +87 -0
  10. data/lib/omnigroupcontacts/builder.rb +30 -0
  11. data/lib/omnigroupcontacts/http_utils.rb +101 -0
  12. data/lib/omnigroupcontacts/importer.rb +5 -0
  13. data/lib/omnigroupcontacts/importer/gmailgroup.rb +238 -0
  14. data/lib/omnigroupcontacts/integration_test.rb +36 -0
  15. data/lib/omnigroupcontacts/middleware/base_oauth.rb +120 -0
  16. data/lib/omnigroupcontacts/middleware/oauth1.rb +70 -0
  17. data/lib/omnigroupcontacts/middleware/oauth2.rb +80 -0
  18. data/lib/omnigroupcontacts/parse_utils.rb +56 -0
  19. data/omnigroupcontacts-0.3.10.gem +0 -0
  20. data/omnigroupcontacts-0.3.8.gem +0 -0
  21. data/omnigroupcontacts-0.3.9.gem +0 -0
  22. data/omnigroupcontacts.gemspec +25 -0
  23. data/spec/omnicontacts/authorization/oauth1_spec.rb +82 -0
  24. data/spec/omnicontacts/authorization/oauth2_spec.rb +92 -0
  25. data/spec/omnicontacts/http_utils_spec.rb +79 -0
  26. data/spec/omnicontacts/importer/facebook_spec.rb +120 -0
  27. data/spec/omnicontacts/importer/gmail_spec.rb +194 -0
  28. data/spec/omnicontacts/importer/hotmail_spec.rb +106 -0
  29. data/spec/omnicontacts/importer/linkedin_spec.rb +67 -0
  30. data/spec/omnicontacts/importer/yahoo_spec.rb +124 -0
  31. data/spec/omnicontacts/integration_test_spec.rb +51 -0
  32. data/spec/omnicontacts/middleware/base_oauth_spec.rb +53 -0
  33. data/spec/omnicontacts/middleware/oauth1_spec.rb +78 -0
  34. data/spec/omnicontacts/middleware/oauth2_spec.rb +67 -0
  35. data/spec/omnicontacts/parse_utils_spec.rb +53 -0
  36. data/spec/spec_helper.rb +12 -0
  37. metadata +37 -2
@@ -0,0 +1,70 @@
1
+ require "omnigroupcontacts/authorization/oauth1"
2
+ require "omnigroupcontacts/middleware/base_oauth"
3
+
4
+ # This class is an OAuth 1.0 Rack middleware.
5
+ #
6
+ # Extending classes are required to
7
+ # implement the following methods:
8
+ # * fetch_token_from_token_and_verifier -> this method has to
9
+ # fetch the list of contacts from the authorization server.
10
+ module OmniGroupContacts
11
+ module Middleware
12
+ class OAuth1 < BaseOAuth
13
+ include Authorization::OAuth1
14
+
15
+ attr_reader :consumer_key, :consumer_secret, :callback_path
16
+
17
+ def initialize app, consumer_key, consumer_secret, options = {}
18
+ super app, options
19
+ @consumer_key = consumer_key
20
+ @consumer_secret = consumer_secret
21
+ @callback_path = options[:callback_path] || "#{ MOUNT_PATH }#{class_name}/callback"
22
+ @token_prop_name = "#{base_prop_name}.oauth_token"
23
+ end
24
+
25
+ def callback
26
+ host_url_from_rack_env(@env) + callback_path
27
+ end
28
+
29
+ alias :redirect_path :callback_path
30
+
31
+ # Obtains an authorization token from the server,
32
+ # stores it and the session and redirect the user
33
+ # to the authorization website.
34
+ def request_authorization_from_user
35
+ (auth_token, auth_token_secret) = fetch_authorization_token
36
+ session[@token_prop_name] = auth_token
37
+ session[token_secret_prop_name(auth_token)] = auth_token_secret
38
+ redirect_to_authorization_site(auth_token)
39
+ end
40
+
41
+ def token_secret_prop_name oauth_token
42
+ "#{base_prop_name}.#{oauth_token}.oauth_token_secret"
43
+ end
44
+
45
+ def redirect_to_authorization_site auth_token
46
+ authorization_url = authorization_url(auth_token)
47
+ target_url = append_state_query(authorization_url)
48
+ [302, {"Content-Type" => "application/x-www-form-urlencoded", "location" => target_url}, []]
49
+ end
50
+
51
+ # Parses the authorization token from the query string and
52
+ # obtain the relative secret from the session.
53
+ # Finally it calls fetch_contacts_from_token_and_verifier.
54
+ # If token is found in the query string an AuhorizationError
55
+ # is raised.
56
+ def fetch_contacts
57
+ params = query_string_to_map(@env["QUERY_STRING"])
58
+ oauth_token = params["oauth_token"]
59
+ oauth_verifier = params["oauth_verifier"]
60
+ oauth_token_secret = session[token_secret_prop_name(oauth_token)]
61
+ if oauth_token && oauth_verifier && oauth_token_secret
62
+ fetch_contacts_from_token_and_verifier(oauth_token, oauth_token_secret, oauth_verifier)
63
+ else
64
+ raise AuthorizationError.new("User did not grant access to contacts list")
65
+ end
66
+ end
67
+
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,80 @@
1
+ require "omnigroupcontacts/authorization/oauth2"
2
+ require "omnigroupcontacts/middleware/base_oauth"
3
+
4
+ # This class is a OAuth 2 Rack middleware.
5
+ #
6
+ # Extending class are required to implement
7
+ # the following methods:
8
+ # * fetch_contacts_using_access_token -> it
9
+ # fetches the list of contacts from the authorization
10
+ # server.
11
+ module OmniGroupContacts
12
+ module Middleware
13
+ class OAuth2 < BaseOAuth
14
+ include Authorization::OAuth2
15
+
16
+ attr_reader :client_id, :client_secret, :redirect_path
17
+
18
+ def initialize app, client_id, client_secret, options ={}
19
+ super app, options
20
+ @client_id = client_id
21
+ @client_secret = client_secret
22
+ @redirect_path = options[:redirect_path] || "#{ MOUNT_PATH }#{class_name}/callback"
23
+ @ssl_ca_file = options[:ssl_ca_file]
24
+ end
25
+
26
+ def request_authorization_from_user
27
+ target_url = append_state_query(authorization_url)
28
+ [302, {"Content-Type" => "application/x-www-form-urlencoded", "location" => target_url}, []]
29
+ end
30
+
31
+ def redirect_uri
32
+ host_url_from_rack_env(@env) + redirect_path
33
+ end
34
+
35
+ # It extract the authorization code from the query string.
36
+ # It uses it to obtain an access token.
37
+ # If the authorization code has a refresh token associated
38
+ # with it in the session, it uses the obtain an access token.
39
+ # It fetches the list of contacts and stores the refresh token
40
+ # associated with the access token in the session.
41
+ # Finally it returns the list of contacts.
42
+ # If no authorization code is found in the query string an
43
+ # AuthoriazationError is raised.
44
+ def fetch_groups
45
+ code = query_string_to_map(@env["QUERY_STRING"])["code"]
46
+ if code
47
+ refresh_token = session[refresh_token_prop_name(code)]
48
+ (access_token, token_type, refresh_token) = if refresh_token
49
+ refresh_access_token(refresh_token)
50
+ else
51
+ fetch_access_token(code)
52
+ end
53
+ groups = fetch_groups_using_access_token(access_token, token_type)
54
+ group_contacts = fetch_group_contacts(groups, access_token, token_type)
55
+ session[refresh_token_prop_name(code)] = refresh_token if refresh_token
56
+ group_contacts
57
+ else
58
+ raise AuthorizationError.new("User did not grant access to contacts list")
59
+ end
60
+ end
61
+
62
+ def fetch_group_contacts(groups, access_token, token_type)
63
+ group_contacts_all = {}
64
+
65
+ groups.each do |group|
66
+ Rails.logger.info "---------#{group.inspect}---------"
67
+ group_contacts = fetch_contacts_using_access_token(access_token, token_type, group[:id])
68
+ group_contacts_all[group[:title]] = group_contacts
69
+ end
70
+
71
+ group_contacts_all
72
+ end
73
+
74
+ def refresh_token_prop_name code
75
+ "#{base_prop_name}.#{code}.refresh_token"
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,56 @@
1
+ module OmniGroupContacts
2
+ module ParseUtils
3
+
4
+ # return has of birthday day, month and year
5
+ def birthday_format month, day, year
6
+ return {:day => day.to_i, :month => month.to_i, :year => year.to_i}if year && month && day
7
+ return {:day => day.to_i, :month => month.to_i, :year => nil} if !year && month && day
8
+ return nil if (!year && !month) || (!year && !day)
9
+ end
10
+
11
+ # normalize the name
12
+ def normalize_name name
13
+ return nil if name.nil?
14
+ name.chomp!
15
+ name.squeeze!(' ')
16
+ name.strip!
17
+ return name
18
+ end
19
+
20
+ # create a full name given the individual first and last name
21
+ def full_name first_name, last_name
22
+ return "#{first_name} #{last_name}" if first_name && last_name
23
+ return "#{first_name}" if first_name && !last_name
24
+ return "#{last_name}" if !first_name && last_name
25
+ return nil
26
+ end
27
+
28
+ # create a username/name from a given email
29
+ def email_to_name username_or_email
30
+ username_or_email = username_or_email.split('@').first if username_or_email.include?('@')
31
+ if group = (/(?<first>[a-z|A-Z]+)[\.|_](?<last>[a-z|A-Z]+)/).match(username_or_email)
32
+ first_name = normalize_name(group[:first])
33
+ last_name = normalize_name(group[:last])
34
+ return first_name, last_name, "#{first_name} #{last_name}"
35
+ end
36
+ username = normalize_name(username_or_email)
37
+ return username, nil, username
38
+ end
39
+
40
+ # create an image_url from a gmail or yahoo email id.
41
+ def image_url_from_email email
42
+ return nil if email.nil? || !email.include?('@')
43
+ username, domain = *(email.split('@'))
44
+ return nil if username.nil? or domain.nil?
45
+ gmail_base_url = "https://profiles.google.com/s2/photos/profile/"
46
+ yahoo_base_url = "https://img.msg.yahoo.com/avatar.php?yids="
47
+ if domain.include?('gmail')
48
+ image_url = gmail_base_url + username
49
+ elsif domain.include?('yahoo')
50
+ image_url = yahoo_base_url + username
51
+ end
52
+ image_url
53
+ end
54
+
55
+ end
56
+ end
Binary file
Binary file
Binary file
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ require File.expand_path('../lib/omnigroupcontacts', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = 'omnigroupcontacts'
6
+ gem.description = %q{A generalized Rack middleware for importing group contacts from gmail.}
7
+ gem.authors = ['Mitesh Jain']
8
+ gem.email = ['mitijain123@gmail.com']
9
+
10
+ gem.add_runtime_dependency 'rack'
11
+ gem.add_runtime_dependency 'json'
12
+
13
+ gem.add_development_dependency 'simplecov'
14
+ gem.add_development_dependency 'rake'
15
+ gem.add_development_dependency 'rack-test'
16
+ gem.add_development_dependency 'rspec'
17
+
18
+ gem.version = OmniGroupContacts::VERSION
19
+ gem.files = `git ls-files`.split("\n")
20
+ gem.homepage = 'http://github.com/mitijain123/omnicontacts'
21
+ gem.require_paths = ['lib']
22
+ gem.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') if gem.respond_to? :required_rubygems_version=
23
+ gem.summary = gem.description
24
+ gem.test_files = `git ls-files -- {spec}/*`.split("\n")
25
+ end
@@ -0,0 +1,82 @@
1
+ require "spec_helper"
2
+ require "omnigroupcontacts/authorization/oauth1"
3
+
4
+ describe OmniGroupContacts::Authorization::OAuth1 do
5
+
6
+ before(:all) do
7
+ OAuth1TestClass= Struct.new(:consumer_key, :consumer_secret, :auth_host, :auth_token_path, :auth_path, :access_token_path, :callback)
8
+ class OAuth1TestClass
9
+ include OmniGroupContacts::Authorization::OAuth1
10
+ end
11
+ end
12
+
13
+ let(:test_target) do
14
+ OAuth1TestClass.new("consumer_key", "secret1", "auth_host", "auth_token_path", "auth_path", "access_token_path", "callback")
15
+ end
16
+
17
+ describe "fetch_authorization_token" do
18
+
19
+ it "should request the token providing all mandatory parameters" do
20
+ test_target.should_receive(:https_post) do |host, path, params|
21
+ host.should eq(test_target.auth_host)
22
+ path.should eq(test_target.auth_token_path)
23
+ params[:oauth_consumer_key].should eq(test_target.consumer_key)
24
+ params[:oauth_nonce].should_not be_nil
25
+ params[:oauth_signature_method].should eq("PLAINTEXT")
26
+ params[:oauth_signature].should eq(test_target.consumer_secret + "%26")
27
+ params[:oauth_timestamp].should_not be_nil
28
+ params[:oauth_version].should eq("1.0")
29
+ params[:oauth_callback].should eq(test_target.callback)
30
+ "oauth_token=token&oauth_token_secret=token_secret"
31
+ end
32
+ test_target.fetch_authorization_token
33
+ end
34
+
35
+ it "should successfully parse the result" do
36
+ test_target.should_receive(:https_post).and_return("oauth_token=token&oauth_token_secret=token_secret")
37
+ test_target.fetch_authorization_token.should eq(["token", "token_secret"])
38
+ end
39
+
40
+ it "should raise an error if request is invalid" do
41
+ test_target.should_receive(:https_post).and_return("invalid_request")
42
+ expect { test_target.fetch_authorization_token }.to raise_error
43
+ end
44
+
45
+ end
46
+
47
+ describe "authorization_url" do
48
+ subject { test_target.authorization_url("token") }
49
+ it { should eq("https://#{test_target.auth_host}#{test_target.auth_path}?oauth_token=token") }
50
+ end
51
+
52
+ describe "fetch_access_token" do
53
+ it "should request the access token using all required parameters" do
54
+ auth_token = "token"
55
+ auth_token_secret = "token_secret"
56
+ auth_verifier = "verifier"
57
+ test_target.should_receive(:https_post) do |host, path, params|
58
+ host.should eq(test_target.auth_host)
59
+ path.should eq(test_target.access_token_path)
60
+ params[:oauth_consumer_key].should eq(test_target.consumer_key)
61
+ params[:oauth_nonce].should_not be_nil
62
+ params[:oauth_signature_method].should eq("PLAINTEXT")
63
+ params[:oauth_version].should eq("1.0")
64
+ params[:oauth_signature].should eq("#{test_target.consumer_secret}%26#{auth_token_secret}")
65
+ params[:oauth_token].should eq(auth_token)
66
+ params[:oauth_verifier].should eq(auth_verifier)
67
+ "oauth_token=access_token&oauth_token_secret=access_token_secret&other_param=other_value"
68
+ end
69
+ test_target.fetch_access_token auth_token, auth_token_secret, auth_verifier, ["other_param"]
70
+ end
71
+
72
+ it "should successfully extract access_token and the other fields" do
73
+ test_target.should_receive(:https_post).and_return("oauth_token=access_token&oauth_token_secret=access_token_secret&other_param=other_value")
74
+ test_target.fetch_access_token("token", "token_scret", "verified", ["other_param"]).should eq(["access_token", "access_token_secret", "other_value"])
75
+ end
76
+ end
77
+
78
+ describe "oauth_signature" do
79
+ subject { test_target.oauth_signature("GET", "https://social.yahooapis.com/v1/user", {:name => "diego", :surname => "castorina"}, "secret2") }
80
+ it { should eq("xfumZoyVYUbHXSAafdha3HZUqQg%3D") }
81
+ end
82
+ end
@@ -0,0 +1,92 @@
1
+ require "spec_helper"
2
+ require "omnigroupcontacts/authorization/oauth2"
3
+
4
+ describe OmniGroupContacts::Authorization::OAuth2 do
5
+
6
+ before(:all) do
7
+ OAuth2TestClass= Struct.new(:auth_host, :authorize_path, :client_id, :client_secret, :scope, :redirect_uri, :auth_token_path)
8
+ class OAuth2TestClass
9
+ include OmniGroupContacts::Authorization::OAuth2
10
+ end
11
+ end
12
+
13
+ let(:test_target) do
14
+ OAuth2TestClass.new("auth_host", "authorize_path", "client_id", "client_secret", "scope", "redirect_uri", "auth_token_path")
15
+ end
16
+
17
+ describe "authorization_url" do
18
+
19
+ subject { test_target.authorization_url }
20
+
21
+ it { should include("https://#{test_target.auth_host}#{test_target.authorize_path}") }
22
+ it { should include("client_id=#{test_target.client_id}") }
23
+ it { should include("scope=#{test_target.scope}") }
24
+ it { should include("redirect_uri=#{test_target.redirect_uri}") }
25
+ it { should include("access_type=online") }
26
+ it { should include("response_type=code") }
27
+ end
28
+
29
+ let(:access_token_response) { %[{"access_token": "access_token", "token_type":"token_type", "refresh_token":"refresh_token"}] }
30
+
31
+ describe "fetch_access_token" do
32
+
33
+ it "should provide all mandatory parameters in a https post request" do
34
+ code = "code"
35
+ test_target.should_receive(:https_post) do |host, path, params|
36
+ host.should eq(test_target.auth_host)
37
+ path.should eq(test_target.auth_token_path)
38
+ params[:code].should eq(code)
39
+ params[:client_id].should eq(test_target.client_id)
40
+ params[:client_secret].should eq(test_target.client_secret)
41
+ params[:redirect_uri].should eq(test_target.redirect_uri)
42
+ params[:grant_type].should eq("authorization_code")
43
+ access_token_response
44
+ end
45
+ test_target.fetch_access_token code
46
+ end
47
+
48
+ it "should successfully parse the token from the JSON response" do
49
+ test_target.should_receive(:https_post).and_return(access_token_response)
50
+ (access_token, token_type, refresh_token) = test_target.fetch_access_token "code"
51
+ access_token.should eq("access_token")
52
+ token_type.should eq("token_type")
53
+ refresh_token.should eq("refresh_token")
54
+ end
55
+
56
+ it "should raise if the http request fails" do
57
+ test_target.should_receive(:https_post).and_raise("Invalid code")
58
+ expect { test_target.fetch_access_token("code") }.to raise_error
59
+ end
60
+
61
+ it "should raise an error if the JSON response contains an error field" do
62
+ test_target.should_receive(:https_post).and_return(%[{"error": "error_message"}])
63
+ expect { test_target.fetch_access_token("code") }.to raise_error
64
+ end
65
+ end
66
+
67
+ describe "refresh_access_token" do
68
+ it "should provide all mandatory fields in a https post request" do
69
+ refresh_token = "refresh_token"
70
+ test_target.should_receive(:https_post) do |host, path, params|
71
+ host.should eq(test_target.auth_host)
72
+ path.should eq(test_target.auth_token_path)
73
+ params[:client_id].should eq(test_target.client_id)
74
+ params[:client_secret].should eq(test_target.client_secret)
75
+ params[:refresh_token].should eq(refresh_token)
76
+ params[:grant_type].should eq("refresh_token")
77
+ access_token_response
78
+ end
79
+ test_target.refresh_access_token refresh_token
80
+ end
81
+
82
+ it "should successfully parse the token from the JSON response" do
83
+ test_target.should_receive(:https_post).and_return(access_token_response)
84
+ (access_token, token_type, refresh_token) = test_target.refresh_access_token "refresh_token"
85
+ access_token.should eq("access_token")
86
+ token_type.should eq("token_type")
87
+ refresh_token.should eq("refresh_token")
88
+ end
89
+
90
+ end
91
+
92
+ end
@@ -0,0 +1,79 @@
1
+ require "spec_helper"
2
+ require "omnigroupcontacts/http_utils"
3
+
4
+ describe OmniGroupContacts::HTTPUtils do
5
+
6
+ describe "to_query_string" do
7
+ it "should create a query string from a map" do
8
+ result = OmniGroupContacts::HTTPUtils.to_query_string(:name => "john", :surname => "doe")
9
+ if result.match(/^name/)
10
+ result.should eq("name=john&surname=doe")
11
+ else
12
+ result.should eq("surname=doe&name=john")
13
+ end
14
+ end
15
+
16
+ it "should work for integer values in the map" do
17
+ result = OmniGroupContacts::HTTPUtils.to_query_string(:client_id => 1234, :secret => "1234HJL8")
18
+ result.should eq("client_id=1234&secret=1234HJL8")
19
+ end
20
+
21
+ end
22
+
23
+ describe "encode" do
24
+ it "should encode the space" do
25
+ OmniGroupContacts::HTTPUtils.encode("name=\"john\"").should eq("name%3D%22john%22")
26
+ end
27
+ end
28
+
29
+ describe "query_string_to_map" do
30
+ it "should split a query string into a map" do
31
+ query_string = "name=john&surname=doe"
32
+ result = OmniGroupContacts::HTTPUtils.query_string_to_map(query_string)
33
+ result["name"].should eq("john")
34
+ result["surname"].should eq("doe")
35
+ end
36
+ end
37
+
38
+ describe "host_url_from_rack_env" do
39
+ it "should calculate the host url using the HTTP_HOST variable" do
40
+ env = {"rack.url_scheme" => "http", "HTTP_HOST" => "localhost:8080", "SERVER_NAME" => "localhost", "SERVER_PORT" => 8080}
41
+ OmniGroupContacts::HTTPUtils.host_url_from_rack_env(env).should eq("http://localhost:8080")
42
+ end
43
+
44
+ it "should calculate the host url using SERVER_NAME and SERVER_PORT variables" do
45
+ env = {"rack.url_scheme" => "http", "SERVER_NAME" => "localhost", "SERVER_PORT" => 8080}
46
+ OmniGroupContacts::HTTPUtils.host_url_from_rack_env(env).should eq("http://localhost:8080")
47
+ end
48
+ end
49
+
50
+ describe "https_post" do
51
+
52
+ before(:each) do
53
+ @connection = double
54
+ Net::HTTP.should_receive(:new).and_return(@connection)
55
+ @connection.should_receive(:use_ssl=).with(true)
56
+ @test_target = Object.new
57
+ @test_target.extend OmniGroupContacts::HTTPUtils
58
+ @response = double
59
+ end
60
+
61
+ it "should execute a request with success" do
62
+ @test_target.should_receive(:ssl_ca_file).and_return(nil)
63
+ @connection.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
64
+ @connection.should_receive(:request_post).and_return(@response)
65
+ @response.should_receive(:code).and_return("200")
66
+ @response.should_receive(:body).and_return("some content")
67
+ @test_target.send(:https_post, "host", "path", {})
68
+ end
69
+
70
+ it "should raise an exception with response code != 200" do
71
+ @test_target.should_receive(:ssl_ca_file).and_return(nil)
72
+ @connection.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
73
+ @connection.should_receive(:request_get).and_return(@response)
74
+ @response.should_receive(:code).and_return("500")
75
+ @response.should_receive(:body).and_return("some error message")
76
+ expect { @test_target.send(:https_get, "host", "path", {}) }.to raise_error
77
+ end
78
+ end
79
+ end