spyou_rpx_now 0.6.6

Sign up to get free protection for your applications and to get access to all the features.
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ #Needed to load rpx_now when used as Rails plugin
2
+ require 'rpx_now'
data/lib/rpx_now.rb ADDED
@@ -0,0 +1,149 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'json'
4
+
5
+ require 'rpx_now/api'
6
+ require 'rpx_now/contacts_collection'
7
+ require 'rpx_now/user_integration'
8
+ require 'rpx_now/user_proxy'
9
+
10
+ module RPXNow
11
+ extend self
12
+
13
+ attr_accessor :api_key
14
+ attr_accessor :api_version
15
+ self.api_version = 2
16
+
17
+ VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
18
+
19
+ # retrieve the users data
20
+ # - cleaned Hash
21
+ # - complete/unclean response when block was given user_data{|response| ...; return hash }
22
+ # - nil when token was invalid / data was not found
23
+ def user_data(token, options={})
24
+ begin
25
+ data = Api.call("auth_info", options.merge(:token => token))
26
+ if block_given? then yield(data) else parse_user_data(data) end
27
+ rescue ServerError
28
+ return nil if $!.to_s=~/Data not found/
29
+ raise
30
+ end
31
+ end
32
+
33
+ # set the users status
34
+ def set_status(identifier, status, options={})
35
+ options = options.merge(:identifier => identifier, :status => status)
36
+ data = Api.call("set_status", options)
37
+ rescue ServerError
38
+ return nil if $!.to_s=~/Data not found/
39
+ raise
40
+ end
41
+
42
+ # maps an identifier to an primary-key (e.g. user.id)
43
+ def map(identifier, primary_key, options={})
44
+ Api.call("map", options.merge(:identifier => identifier, :primaryKey => primary_key))
45
+ end
46
+
47
+ # un-maps an identifier to an primary-key (e.g. user.id)
48
+ def unmap(identifier, primary_key, options={})
49
+ Api.call("unmap", options.merge(:identifier => identifier, :primaryKey => primary_key))
50
+ end
51
+
52
+ # returns an array of identifiers which are mapped to one of your primary-keys (e.g. user.id)
53
+ def mappings(primary_key, options={})
54
+ Api.call("mappings", options.merge(:primaryKey => primary_key))['identifiers']
55
+ end
56
+
57
+ def all_mappings(options={})
58
+ Api.call("all_mappings", options)['mappings']
59
+ end
60
+
61
+ def contacts(identifier, options={})
62
+ data = Api.call("get_contacts", options.merge(:identifier => identifier))
63
+ RPXNow::ContactsCollection.new(data['response'])
64
+ end
65
+ alias get_contacts contacts
66
+
67
+ # iframe for rpx login
68
+ # options: :width, :height, :language, :flags
69
+ def embed_code(subdomain, url, options={})
70
+ options = {:width => '400', :height => '240'}.merge(options)
71
+ <<-EOF
72
+ <iframe src="#{Api.host(subdomain)}/openid/embed?#{embed_params(url, options)}"
73
+ scrolling="no" frameBorder="no" style="width:#{options[:width]}px;height:#{options[:height]}px;">
74
+ </iframe>
75
+ EOF
76
+ end
77
+
78
+ # popup window for rpx login
79
+ # options: :language / :flags / :unobtrusive
80
+ def popup_code(text, subdomain, url, options = {})
81
+ if options[:unobtrusive]
82
+ unobtrusive_popup_code(text, subdomain, url, options)
83
+ else
84
+ obtrusive_popup_code(text, subdomain, url, options)
85
+ end
86
+ end
87
+
88
+ # javascript for popup
89
+ # only needed in combination with popup_code(x,y,z, :unobtrusive => true)
90
+ def popup_source(subdomain, url, options={})
91
+ <<-EOF
92
+ <script src="#{Api.host}/openid/v#{extract_version(options)}/widget" type="text/javascript"></script>
93
+ <script type="text/javascript">
94
+ //<![CDATA[
95
+ RPXNOW.token_url = '#{url}';
96
+ RPXNOW.realm = '#{subdomain}';
97
+ RPXNOW.overlay = true;
98
+ #{ "RPXNOW.language_preference = '#{options[:language]}';" if options[:language] }
99
+ #{ "RPXNOW.default_provider = '#{options[:default_provider]}';" if options[:default_provider] }
100
+ #{ "RPXNOW.flags = '#{options[:flags]}';" if options[:flags] }
101
+ //]]>
102
+ </script>
103
+ EOF
104
+ end
105
+
106
+ def extract_version(options)
107
+ options[:api_version] || api_version
108
+ end
109
+
110
+ private
111
+
112
+ def self.embed_params(url, options)
113
+ {
114
+ :token_url => url,
115
+ :language_preference => options[:language],
116
+ :flags => options[:flags],
117
+ :default_provider => options[:default_provider]
118
+ }.map{|k,v| "#{k}=#{v}" if v}.compact.join('&')
119
+ end
120
+
121
+ def self.parse_user_data(response)
122
+ user_data = response['profile']
123
+ data = {}
124
+ data[:identifier] = user_data['identifier']
125
+ data[:email] = user_data['verifiedEmail'] || user_data['email']
126
+ data[:username] = user_data['preferredUsername'] || data[:email].to_s.sub(/@.*/,'')
127
+ data[:displayName] = user_data['displayName']
128
+ data[:preferredUsername] = user_data["preferredUsername"]
129
+ data[:gender] = user_data["gender"]
130
+ data[:birthday] = user_data["birthday"]
131
+ data[:photo] = user_data["photo"]
132
+ data[:name] = user_data['displayName'] || data[:username]
133
+ data[:id] = user_data['primaryKey'] unless user_data['primaryKey'].to_s.empty?
134
+ data
135
+ end
136
+
137
+ def unobtrusive_popup_code(text, subdomain, url, options={})
138
+ %Q(<a class="rpxnow" href="#{Api.host(subdomain)}/openid/v#{extract_version(options)}/signin?#{embed_params(url, options)}">#{text}</a>)
139
+ end
140
+
141
+ def obtrusive_popup_code(text, subdomain, url, options = {})
142
+ unobtrusive_popup_code(text, subdomain, url, options) +
143
+ popup_source(subdomain, url, options)
144
+ end
145
+
146
+ class ServerError < RuntimeError; end #backwards compatibility / catch all
147
+ class ApiError < ServerError; end
148
+ class ServiceUnavailableError < ServerError; end
149
+ end
@@ -0,0 +1,72 @@
1
+ module RPXNow
2
+ # low-level interaction with rpxnow.com api
3
+ # - send requests
4
+ # - parse response
5
+ # - handle server errors
6
+ class Api
7
+ HOST = 'rpxnow.com'
8
+ SSL_CERT = File.join(File.dirname(__FILE__), '..', '..', 'certs', 'ssl_cert.pem')
9
+
10
+ def self.call(method, data)
11
+ data = data.dup
12
+ version = RPXNow.extract_version(data)
13
+ data.delete(:api_version)
14
+
15
+ path = "/api/v#{version}/#{method}"
16
+ response = request(path, {:apiKey => RPXNow.api_key}.merge(data))
17
+ parse_response(response)
18
+ end
19
+
20
+ def self.host(subdomain=nil)
21
+ if subdomain
22
+ "https://#{subdomain}.#{Api::HOST}"
23
+ else
24
+ "https://#{Api::HOST}"
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def self.request(path, data)
31
+ client.request(request_object(path, data))
32
+ end
33
+
34
+ def self.request_object(path, data)
35
+ request = Net::HTTP::Post.new(path)
36
+ request.form_data = stringify_keys(data)
37
+ request
38
+ end
39
+
40
+ # symbol keys -> string keys
41
+ # because of ruby 1.9.x bug in Net::HTTP
42
+ # http://redmine.ruby-lang.org/issues/show/1351
43
+ def self.stringify_keys(hash)
44
+ hash.map{|k,v| [k.to_s,v]}
45
+ end
46
+
47
+ def self.client
48
+ client = Net::HTTP.new(HOST, 443)
49
+ client.use_ssl = true
50
+ client.ca_file = SSL_CERT
51
+ client.verify_mode = OpenSSL::SSL::VERIFY_PEER
52
+ client.verify_depth = 5
53
+ client
54
+ end
55
+
56
+ def self.parse_response(response)
57
+ if response.code.to_i >= 400
58
+ raise ServiceUnavailableError, "The RPX service is temporarily unavailable. (4XX)"
59
+ else
60
+ result = JSON.parse(response.body)
61
+ return result unless result['err']
62
+
63
+ code = result['err']['code']
64
+ if code == -1
65
+ raise ServiceUnavailableError, "The RPX service is temporarily unavailable."
66
+ else
67
+ raise ApiError, "Got error: #{result['err']['msg']} (code: #{code}), HTTP status: #{response.code}"
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,20 @@
1
+ module RPXNow
2
+ # Makes returned contacts feel like a array
3
+ class ContactsCollection < Array
4
+ def initialize(list)
5
+ @raw = list
6
+ @additional_info = list.reject{|k,v|k=='entry'}
7
+ list['entry'].each{|item| self << parse_data(item)}
8
+ end
9
+
10
+ def additional_info;@additional_info;end
11
+ def raw;@raw;end
12
+
13
+ private
14
+
15
+ def parse_data(entry)
16
+ entry['emails'] = entry['emails'].map{|email| email['value']}
17
+ entry
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ module RPXNow
2
+ module UserIntegration
3
+ def rpx
4
+ RPXNow::UserProxy.new(id)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ module RPXNow
2
+ class UserProxy
3
+ def initialize(id)
4
+ @id = id
5
+ end
6
+
7
+ def identifiers
8
+ RPXNow.mappings(@id)
9
+ end
10
+
11
+ def map(identifier)
12
+ RPXNow.map(identifier, @id)
13
+ end
14
+
15
+ def unmap(identifier)
16
+ RPXNow.unmap(identifier, @id)
17
+ end
18
+ end
19
+ end
data/rdoc/README.rdoc ADDED
@@ -0,0 +1 @@
1
+ documentation is at http://github.com/grosser/rpx_now
@@ -0,0 +1,58 @@
1
+ {
2
+ "response": {
3
+ "itemsPerPage": 5,
4
+ "totalResults": 5,
5
+ "entry": [
6
+ {
7
+ "displayName": "Bob Johnson",
8
+ "emails": [
9
+ {
10
+ "type": "other",
11
+ "value": "bob@example.com"
12
+ }
13
+ ]
14
+ },
15
+ {
16
+ "displayName": "Cindy Smith",
17
+ "emails": [
18
+ {
19
+ "type": "other",
20
+ "value": "cindy.smith@example.com"
21
+ }
22
+ ]
23
+ },
24
+ {
25
+ "displayName": "Fred Williams",
26
+ "emails": [
27
+ {
28
+ "type": "other",
29
+ "value": "fred.williams@example.com"
30
+ },
31
+ {
32
+ "type": "other",
33
+ "value": "fred@example.com"
34
+ }
35
+ ]
36
+ },
37
+ {
38
+ "emails": [
39
+ {
40
+ "type": "other",
41
+ "value": "j.lewis@example.com"
42
+ }
43
+ ]
44
+ },
45
+ {
46
+ "displayName": "Mary Jones",
47
+ "emails": [
48
+ {
49
+ "type": "other",
50
+ "value": "mary.jones@example.com"
51
+ }
52
+ ]
53
+ }
54
+ ],
55
+ "startIndex": 1
56
+ },
57
+ "stat": "ok"
58
+ }
@@ -0,0 +1,40 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe RPXNow do
4
+ describe :mapping_integration do
5
+ before do
6
+ @k1 = 'http://test.myopenid.com'
7
+ RPXNow.unmap(@k1, 1)
8
+ @k2 = 'http://test-2.myopenid.com'
9
+ RPXNow.unmap(@k2, 1)
10
+ end
11
+
12
+ it "has no mappings when nothing was mapped" do
13
+ RPXNow.mappings(1).should == []
14
+ end
15
+
16
+ it "unmaps mapped keys" do
17
+ RPXNow.map(@k2, 1)
18
+ RPXNow.unmap(@k2, 1)
19
+ RPXNow.mappings(1).should == []
20
+ end
21
+
22
+ it "maps keys to a primary key and then retrieves them" do
23
+ RPXNow.map(@k1, 1)
24
+ RPXNow.map(@k2, 1)
25
+ RPXNow.mappings(1).sort.should == [@k2,@k1]
26
+ end
27
+
28
+ it "does not add duplicate mappings" do
29
+ RPXNow.map(@k1, 1)
30
+ RPXNow.map(@k1, 1)
31
+ RPXNow.mappings(1).should == [@k1]
32
+ end
33
+
34
+ it "finds all mappings" do
35
+ RPXNow.map(@k1, 1)
36
+ RPXNow.map(@k2, 2)
37
+ RPXNow.all_mappings.sort.should == [["1", ["http://test.myopenid.com"]], ["2", ["http://test-2.myopenid.com"]]]
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe RPXNow::Api do
4
+ describe 'ssl cert' do
5
+ it "has an absolute path" do
6
+ RPXNow::Api::SSL_CERT[0..0].should == '/' #start with '/'
7
+ end
8
+
9
+ it "exists" do
10
+ File.read(RPXNow::Api::SSL_CERT).to_s.should_not be_empty
11
+ end
12
+ end
13
+
14
+ describe :parse_response do
15
+ it "parses json when status is ok" do
16
+ response = mock(:code=>'200', :body=>%Q({"stat":"ok","data":"xx"}))
17
+ RPXNow::Api.send(:parse_response, response)['data'].should == "xx"
18
+ end
19
+
20
+ it "raises when there is a communication error" do
21
+ response = stub(:code=>'200', :body=>%Q({"err":"wtf","stat":"ok"}))
22
+ lambda{
23
+ RPXNow::Api.send(:parse_response,response)
24
+ }.should raise_error(RPXNow::ApiError)
25
+ end
26
+
27
+ it "raises when service has downtime" do
28
+ response = stub(:code=>'200', :body=>%Q({"err":{"code":-1},"stat":"ok"}))
29
+ lambda{
30
+ RPXNow::Api.send(:parse_response,response)
31
+ }.should raise_error(RPXNow::ServiceUnavailableError)
32
+ end
33
+
34
+ it "raises when service is down" do
35
+ response = stub(:code=>'400',:body=>%Q({"stat":"err"}))
36
+ lambda{
37
+ RPXNow::Api.send(:parse_response,response)
38
+ }.should raise_error(RPXNow::ServiceUnavailableError)
39
+ end
40
+ end
41
+
42
+ describe :request_object do
43
+ it "converts symbols to string keys" do
44
+ mock = ''
45
+ mock.should_receive(:form_data=).with([['symbol', 'value']])
46
+ Net::HTTP::Post.should_receive(:new).and_return(mock)
47
+ RPXNow::Api.send(:request_object, 'something', :symbol=>'value')
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe RPXNow::ContactsCollection do
4
+ before do
5
+ data = JSON.parse(File.read('spec/fixtures/get_contacts_response.json'))['response']
6
+ @collection = RPXNow::ContactsCollection.new(data)
7
+ end
8
+
9
+ it "behaves like an array" do
10
+ @collection.size.should == 5
11
+ @collection[0] = "1"
12
+ @collection[0].should == "1"
13
+ end
14
+
15
+ it "parses entry to items" do
16
+ @collection[0]['displayName'].should == "Bob Johnson"
17
+ end
18
+
19
+ it "parses emails to list" do
20
+ @collection[0]['emails'].should == ["bob@example.com"]
21
+ end
22
+
23
+ it "parses emails to list with multiple emails" do
24
+ @collection[2]['emails'].should == ["fred.williams@example.com","fred@example.com"]
25
+ end
26
+
27
+ it "holds additional_info" do
28
+ @collection.additional_info['startIndex'].should == 1
29
+ @collection.additional_info['itemsPerPage'].should == 5
30
+ @collection.additional_info['totalResults'].should == 5
31
+ end
32
+ end