spyou_rpx_now 0.6.6

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.
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