omnigroupcontacts 0.3.10 → 0.3.11

Sign up to get free protection for your applications and to get access to all the features.
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