Empact-rpx_now 0.7.0

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,163 @@
1
+ require 'rpx_now/api'
2
+ require 'rpx_now/contacts_collection'
3
+ require 'cgi'
4
+
5
+ module RPXNow
6
+ extend self
7
+
8
+ attr_accessor :api_key
9
+ attr_accessor :api_version
10
+ attr_accessor :ssl
11
+ self.api_version = 2
12
+ self.ssl = true
13
+
14
+ VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
15
+
16
+ # retrieve the users data
17
+ # - cleaned Hash
18
+ # - complete/unclean response when block was given user_data{|response| ...; return hash }
19
+ # - nil when token was invalid / data was not found
20
+ def user_data(token, options={})
21
+ begin
22
+ data = Api.call("auth_info", options.merge(:token => token))
23
+ result = (block_given? ? yield(data) : parse_user_data(data, options))
24
+ result.respond_to?(:with_indifferent_access) ? result.with_indifferent_access : result
25
+ rescue ServerError
26
+ return nil if $!.to_s=~/Data not found/
27
+ raise
28
+ end
29
+ end
30
+
31
+ # set the users status
32
+ def set_status(identifier, status, options={})
33
+ options = options.merge(:identifier => identifier, :status => status)
34
+ data = Api.call("set_status", options)
35
+ rescue ServerError
36
+ return nil if $!.to_s=~/Data not found/
37
+ raise
38
+ end
39
+
40
+ # Post an activity update to the user's activity stream.
41
+ # See more: https://rpxnow.com/docs#api_activity
42
+ def activity(identifier, activity_options, options={})
43
+ options = options.merge(:identifier => identifier, :activity => activity_options.to_json)
44
+ Api.call("activity", options)
45
+ end
46
+
47
+ # maps an identifier to an primary-key (e.g. user.id)
48
+ def map(identifier, primary_key, options={})
49
+ Api.call("map", options.merge(:identifier => identifier, :primaryKey => primary_key))
50
+ end
51
+
52
+ # un-maps an identifier to an primary-key (e.g. user.id)
53
+ def unmap(identifier, primary_key, options={})
54
+ Api.call("unmap", options.merge(:identifier => identifier, :primaryKey => primary_key))
55
+ end
56
+
57
+ # returns an array of identifiers which are mapped to one of your primary-keys (e.g. user.id)
58
+ def mappings(primary_key, options={})
59
+ Api.call("mappings", options.merge(:primaryKey => primary_key))['identifiers']
60
+ end
61
+
62
+ def all_mappings(options={})
63
+ Api.call("all_mappings", options)['mappings']
64
+ end
65
+
66
+ def contacts(identifier, options={})
67
+ data = Api.call("get_contacts", options.merge(:identifier => identifier))
68
+ RPXNow::ContactsCollection.new(data['response'])
69
+ end
70
+ alias get_contacts contacts
71
+
72
+ # embedded rpx login (via iframe)
73
+ # options: :width, :height, :language, :flags, :api_version, :default_provider
74
+ def embed_code(subdomain, url, options={})
75
+ options = {:width => '400', :height => '240'}.merge(options)
76
+ <<-EOF
77
+ <iframe src="#{Api.host(subdomain)}/openid/embed?#{embed_params(url, options)}"
78
+ scrolling="no" frameBorder="no" style="width:#{options[:width]}px;height:#{options[:height]}px;">
79
+ </iframe>
80
+ EOF
81
+ end
82
+
83
+ # popup window for rpx login
84
+ # options: :language, :flags, :unobtrusive, :api_version, :default_provider
85
+ def popup_code(text, subdomain, url, options = {})
86
+ if options[:unobtrusive]
87
+ unobtrusive_popup_code(text, subdomain, url, options)
88
+ else
89
+ obtrusive_popup_code(text, subdomain, url, options)
90
+ end
91
+ end
92
+
93
+ # javascript for popup
94
+ # only needed in combination with popup_code(x,y,z, :unobtrusive => true)
95
+ def popup_source(subdomain, url, options={})
96
+ <<-EOF
97
+ <script src="#{Api.host}/openid/v#{extract_version(options)}/widget" type="text/javascript"></script>
98
+ <script type="text/javascript">
99
+ //<![CDATA[
100
+ RPXNOW.token_url = '#{url}';
101
+ RPXNOW.realm = '#{subdomain}';
102
+ RPXNOW.overlay = true;
103
+ RPXNOW.ssl = #{ssl};
104
+ #{ "RPXNOW.language_preference = '#{options[:language]}';" if options[:language] }
105
+ #{ "RPXNOW.default_provider = '#{options[:default_provider]}';" if options[:default_provider] }
106
+ #{ "RPXNOW.flags = '#{options[:flags]}';" if options[:flags] }
107
+ //]]>
108
+ </script>
109
+ EOF
110
+ end
111
+
112
+ # url for unobtrusive popup window
113
+ # options: :language, :flags, :api_version, :default_provider
114
+ def popup_url(subdomain, url, options={})
115
+ "#{Api.host(subdomain)}/openid/v#{extract_version(options)}/signin?#{embed_params(url, options)}"
116
+ end
117
+
118
+ def extract_version(options)
119
+ options[:api_version] || api_version
120
+ end
121
+
122
+ private
123
+
124
+ def self.embed_params(url, options)
125
+ {
126
+ :token_url => CGI::escape( url ),
127
+ :language_preference => options[:language],
128
+ :flags => options[:flags],
129
+ :default_provider => options[:default_provider]
130
+ }.map{|k,v| "#{k}=#{v}" if v}.compact.join('&amp;')
131
+ end
132
+
133
+ def self.parse_user_data(response, options)
134
+ user_data = response['profile']
135
+ data = {}
136
+ data[:identifier] = user_data['identifier']
137
+ data[:email] = user_data['verifiedEmail'] || user_data['email']
138
+ data[:username] = user_data['preferredUsername'] || data[:email].to_s.sub(/@.*/,'')
139
+ data[:name] = user_data['displayName'] || data[:username]
140
+ data[:id] = user_data['primaryKey'] unless user_data['primaryKey'].to_s.empty?
141
+ (options[:additional] || []).each do |key|
142
+ if key == :raw
143
+ data[key] = user_data
144
+ else
145
+ data[key] = user_data[key.to_s]
146
+ end
147
+ end
148
+ data
149
+ end
150
+
151
+ def unobtrusive_popup_code(text, subdomain, url, options={})
152
+ %Q(<a class="rpxnow" href="#{popup_url(subdomain, url, options)}">#{text}</a>)
153
+ end
154
+
155
+ def obtrusive_popup_code(text, subdomain, url, options = {})
156
+ unobtrusive_popup_code(text, subdomain, url, options) +
157
+ popup_source(subdomain, url, options)
158
+ end
159
+
160
+ class ServerError < RuntimeError; end #backwards compatibility / catch all
161
+ class ApiError < ServerError; end
162
+ class ServiceUnavailableError < ServerError; end
163
+ end
@@ -0,0 +1,81 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'json'
4
+
5
+ module RPXNow
6
+ # low-level interaction with rpxnow.com api
7
+ # - send requests
8
+ # - parse response
9
+ # - handle server errors
10
+ class Api
11
+ HOST = 'rpxnow.com'
12
+ SSL_CERT = File.join(File.dirname(__FILE__), '..', '..', 'certs', 'ssl_cert.pem')
13
+
14
+ def self.call(method, data)
15
+ data = data.dup
16
+ version = RPXNow.extract_version(data)
17
+ data.delete(:api_version)
18
+
19
+ path = "/api/v#{version}/#{method}"
20
+ response = request(path, {:apiKey => RPXNow.api_key}.merge(data))
21
+ parse_response(response)
22
+ end
23
+
24
+ def self.host(realm=nil)
25
+ protocol = RPXNow.ssl ? 'https' : 'http'
26
+ domain =
27
+ if realm.nil?
28
+ Api::HOST
29
+ elsif realm.include?('.')
30
+ realm
31
+ else
32
+ "#{realm}.#{Api::HOST}"
33
+ end
34
+ "#{protocol}://#{domain}"
35
+ end
36
+
37
+ private
38
+
39
+ def self.request(path, data)
40
+ client.request(request_object(path, data))
41
+ end
42
+
43
+ def self.request_object(path, data)
44
+ request = Net::HTTP::Post.new(path)
45
+ request.form_data = stringify_keys(data)
46
+ request
47
+ end
48
+
49
+ # symbol keys -> string keys
50
+ # because of ruby 1.9.x bug in Net::HTTP
51
+ # http://redmine.ruby-lang.org/issues/show/1351
52
+ def self.stringify_keys(hash)
53
+ hash.map{|k,v| [k.to_s,v]}
54
+ end
55
+
56
+ def self.client
57
+ client = Net::HTTP.new(HOST, 443)
58
+ client.use_ssl = true
59
+ client.ca_file = SSL_CERT
60
+ client.verify_mode = OpenSSL::SSL::VERIFY_PEER
61
+ client.verify_depth = 5
62
+ client
63
+ end
64
+
65
+ def self.parse_response(response)
66
+ if response.code.to_i >= 400
67
+ raise ServiceUnavailableError, "The RPX service is temporarily unavailable. (4XX)"
68
+ else
69
+ result = JSON.parse(response.body)
70
+ return result unless result['err']
71
+
72
+ code = result['err']['code']
73
+ if code == -1
74
+ raise ServiceUnavailableError, "The RPX service is temporarily unavailable."
75
+ else
76
+ raise ApiError, "Got error: #{result['err']['msg']} (code: #{code}), HTTP status: #{response.code}"
77
+ end
78
+ end
79
+ end
80
+ end
81
+ 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'] ? entry['emails'].map{|email| email['value']} : [])
17
+ entry
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ require 'rpx_now/user_proxy'
2
+
3
+ module RPXNow
4
+ module UserIntegration
5
+ def rpx
6
+ RPXNow::UserProxy.new(id)
7
+ end
8
+ end
9
+ 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
@@ -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,89 @@
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 == File.expand_path( RPXNow::Api::SSL_CERT )[0..0] # start with '/' on *nix, drive letter on win
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 :host do
15
+ after do
16
+ RPXNow.ssl = true
17
+ end
18
+
19
+ context "when the realm is a domain" do
20
+ it "returns the domain itself" do
21
+ RPXNow::Api.host("login.example.com").should == 'https://login.example.com'
22
+ end
23
+
24
+ it "honors the ssl setting" do
25
+ RPXNow.ssl = false
26
+ RPXNow::Api.host("login.example.com").should == 'http://login.example.com'
27
+ end
28
+ end
29
+
30
+ context "when the realm is a subdomain" do
31
+ it "returns the qualified rpx domain" do
32
+ RPXNow::Api.host("example").should == 'https://example.rpxnow.com'
33
+ end
34
+
35
+ it "honors the ssl setting" do
36
+ RPXNow.ssl = false
37
+ RPXNow::Api.host("example").should == 'http://example.rpxnow.com'
38
+ end
39
+ end
40
+
41
+ context "when no realm is provided" do
42
+ it "returns the rpx domain" do
43
+ RPXNow::Api.host.should == 'https://rpxnow.com'
44
+ end
45
+
46
+ it "honors the ssl setting" do
47
+ RPXNow.ssl = false
48
+ RPXNow::Api.host.should == 'http://rpxnow.com'
49
+ end
50
+ end
51
+ end
52
+
53
+ describe :parse_response do
54
+ it "parses json when status is ok" do
55
+ response = mock(:code=>'200', :body=>%Q({"stat":"ok","data":"xx"}))
56
+ RPXNow::Api.send(:parse_response, response)['data'].should == "xx"
57
+ end
58
+
59
+ it "raises when there is a communication error" do
60
+ response = stub(:code=>'200', :body=>%Q({"err":"wtf","stat":"ok"}))
61
+ lambda{
62
+ RPXNow::Api.send(:parse_response,response)
63
+ }.should raise_error(RPXNow::ApiError)
64
+ end
65
+
66
+ it "raises when service has downtime" do
67
+ response = stub(:code=>'200', :body=>%Q({"err":{"code":-1},"stat":"ok"}))
68
+ lambda{
69
+ RPXNow::Api.send(:parse_response,response)
70
+ }.should raise_error(RPXNow::ServiceUnavailableError)
71
+ end
72
+
73
+ it "raises when service is down" do
74
+ response = stub(:code=>'400',:body=>%Q({"stat":"err"}))
75
+ lambda{
76
+ RPXNow::Api.send(:parse_response,response)
77
+ }.should raise_error(RPXNow::ServiceUnavailableError)
78
+ end
79
+ end
80
+
81
+ describe :request_object do
82
+ it "converts symbols to string keys" do
83
+ mock = ''
84
+ mock.should_receive(:form_data=).with([['symbol', 'value']])
85
+ Net::HTTP::Post.should_receive(:new).and_return(mock)
86
+ RPXNow::Api.send(:request_object, 'something', :symbol=>'value')
87
+ end
88
+ end
89
+ end