slayer-rpx_now 0.6.24

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'
@@ -0,0 +1,75 @@
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
+ SSL_CERT = File.join(File.dirname(__FILE__), '..', '..', 'certs', 'ssl_cert.pem')
12
+
13
+ def self.call(method, data)
14
+ data = data.dup
15
+ version = RPXNow.extract_version(data)
16
+ data.delete(:api_version)
17
+
18
+ path = "/api/v#{version}/#{method}"
19
+ response = request(path, {:apiKey => RPXNow.api_key}.merge(data))
20
+ parse_response(response)
21
+ end
22
+
23
+ def self.host(subdomain=nil)
24
+ if subdomain
25
+ "https://#{subdomain}.#{RPXNow.domain}"
26
+ else
27
+ "https://#{RPXNow.domain}"
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def self.request(path, data)
34
+ client.request(request_object(path, data))
35
+ end
36
+
37
+ def self.request_object(path, data)
38
+ request = Net::HTTP::Post.new(path)
39
+ request.form_data = stringify_keys(data)
40
+ request
41
+ end
42
+
43
+ # symbol keys -> string keys
44
+ # because of ruby 1.9.x bug in Net::HTTP
45
+ # http://redmine.ruby-lang.org/issues/show/1351
46
+ def self.stringify_keys(hash)
47
+ hash.map{|k,v| [k.to_s,v]}
48
+ end
49
+
50
+ def self.client
51
+ client = Net::HTTP.new(RPXNow.domain, 443)
52
+ client.use_ssl = true
53
+ client.ca_file = SSL_CERT
54
+ client.verify_mode = OpenSSL::SSL::VERIFY_PEER
55
+ client.verify_depth = 5
56
+ client
57
+ end
58
+
59
+ def self.parse_response(response)
60
+ if response.code.to_i >= 400
61
+ raise ServiceUnavailableError, "The RPX service is temporarily unavailable. (4XX)"
62
+ else
63
+ result = JSON.parse(response.body)
64
+ return result unless result['err']
65
+
66
+ code = result['err']['code']
67
+ if code == -1
68
+ raise ServiceUnavailableError, "The RPX service is temporarily unavailable."
69
+ else
70
+ raise ApiError, "Got error: #{result['err']['msg']} (code: #{code}), HTTP status: #{response.code}"
71
+ end
72
+ end
73
+ end
74
+ end
75
+ 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
data/lib/rpx_now.rb ADDED
@@ -0,0 +1,201 @@
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 :domain
11
+
12
+ self.api_version = 2
13
+ self.domain = 'rpxnow.com'
14
+
15
+ VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
16
+
17
+ # retrieve the users data
18
+ # - cleaned Hash
19
+ # - complete/unclean response when block was given user_data{|response| ...; return hash }
20
+ # - nil when token was invalid / data was not found
21
+ def user_data(token, options={})
22
+ options = options.dup
23
+ return_raw = options.delete(:raw_response)
24
+
25
+ data = begin
26
+ auth_info(token, options)
27
+ rescue ServerError
28
+ return nil if $!.to_s=~/Data not found/
29
+ raise
30
+ end
31
+
32
+ result = (block_given? ? yield(data) : (return_raw ? data : parse_user_data(data, options)))
33
+ with_indifferent_access(result)
34
+ end
35
+
36
+ # same data as user_data, but without any kind of post-processing
37
+ def auth_info(token, options={})
38
+ data = Api.call("auth_info", options.merge(:token => token))
39
+ with_indifferent_access(data)
40
+ end
41
+
42
+ # same as for auth_info if Offline Profile Access is enabled,
43
+ # but can be called at any time and does not need a token / does not expire
44
+ def get_user_data(identifier, options={})
45
+ data = Api.call("get_user_data", options.merge(:identifier => identifier))
46
+ with_indifferent_access(data)
47
+ end
48
+
49
+ # set the users status
50
+ def set_status(identifier, status, options={})
51
+ options = options.merge(:identifier => identifier, :status => status)
52
+ Api.call("set_status", options)
53
+ rescue ServerError
54
+ return nil if $!.to_s=~/Data not found/
55
+ raise
56
+ end
57
+
58
+ # Post an activity update to the user's activity stream.
59
+ # See more: https://rpxnow.com/docs#api_activity
60
+ def activity(identifier, activity_options, options={})
61
+ options = options.merge(:identifier => identifier, :activity => activity_options.to_json)
62
+ Api.call("activity", options)
63
+ end
64
+
65
+ # maps an identifier to an primary-key (e.g. user.id)
66
+ def map(identifier, primary_key, options={})
67
+ Api.call("map", options.merge(:identifier => identifier, :primaryKey => primary_key))
68
+ end
69
+
70
+ # un-maps an identifier to an primary-key (e.g. user.id)
71
+ def unmap(identifier, primary_key, options={})
72
+ Api.call("unmap", options.merge(:identifier => identifier, :primaryKey => primary_key))
73
+ end
74
+
75
+ # returns an array of identifiers which are mapped to one of your primary-keys (e.g. user.id)
76
+ def mappings(primary_key, options={})
77
+ Api.call("mappings", options.merge(:primaryKey => primary_key))['identifiers']
78
+ end
79
+
80
+ def all_mappings(options={})
81
+ Api.call("all_mappings", options)['mappings']
82
+ end
83
+
84
+ def contacts(identifier, options={})
85
+ data = Api.call("get_contacts", options.merge(:identifier => identifier))
86
+ RPXNow::ContactsCollection.new(data['response'])
87
+ end
88
+ alias get_contacts contacts
89
+
90
+ # embedded rpx login (via iframe)
91
+ # options: :width, :height, :language, :flags, :api_version, :default_provider
92
+ def embed_code(subdomain, url, options={})
93
+ options = {:width => '400', :height => '240'}.merge(options)
94
+ <<-EOF
95
+ <iframe src="#{Api.host(subdomain)}/openid/embed?#{embed_params(url, options)}"
96
+ scrolling="no" frameBorder="no" style="width:#{options[:width]}px;height:#{options[:height]}px;" id="rpx_now_embed" allowtransparency="allowtransparency">
97
+ </iframe>
98
+ EOF
99
+ end
100
+
101
+ # popup window for rpx login
102
+ # options: :language, :flags, :unobtrusive, :api_version, :default_provider, :html
103
+ def popup_code(text, subdomain, url, options = {})
104
+ if options[:unobtrusive]
105
+ unobtrusive_popup_code(text, subdomain, url, options)
106
+ else
107
+ obtrusive_popup_code(text, subdomain, url, options)
108
+ end
109
+ end
110
+
111
+ # javascript for popup
112
+ # only needed in combination with popup_code(x,y,z, :unobtrusive => true)
113
+ def popup_source(subdomain, url, options={})
114
+ <<-EOF
115
+ <script src="#{Api.host}/openid/v#{extract_version(options)}/widget" type="text/javascript"></script>
116
+ <script type="text/javascript">
117
+ //<![CDATA[
118
+ $(function () {
119
+ RPXNOW.token_url = '#{url}';
120
+ RPXNOW.realm = '#{subdomain}';
121
+ RPXNOW.overlay = true;
122
+ #{ "RPXNOW.language_preference = '#{options[:language]}';" if options[:language] }
123
+ #{ "RPXNOW.default_provider = '#{options[:default_provider]}';" if options[:default_provider] }
124
+ #{ "RPXNOW.flags = '#{options[:flags]}';" if options[:flags] }
125
+ });
126
+ //]]>
127
+ </script>
128
+ EOF
129
+ end
130
+
131
+ # url for unobtrusive popup window
132
+ # options: :language, :flags, :api_version, :default_provider
133
+ def popup_url(subdomain, url, options={})
134
+ "#{Api.host(subdomain)}/openid/v#{extract_version(options)}/signin?#{embed_params(url, options)}"
135
+ end
136
+
137
+ def extract_version(options)
138
+ options[:api_version] || api_version
139
+ end
140
+
141
+ private
142
+
143
+ def self.embed_params(url, options)
144
+ {
145
+ :token_url => CGI::escape( url ),
146
+ :language_preference => options[:language],
147
+ :flags => options[:flags],
148
+ :default_provider => options[:default_provider]
149
+ }.map{|k,v| "#{k}=#{v}" if v}.compact.join('&amp;')
150
+ end
151
+
152
+ def self.parse_user_data(response, options)
153
+ user_data = response['profile']
154
+ data = {}
155
+ data[:identifier] = user_data['identifier']
156
+ data[:email] = user_data['verifiedEmail'] || user_data['email']
157
+ data[:username] = user_data['preferredUsername'] || data[:email].to_s.sub(/@.*/,'')
158
+ data[:name] = user_data['displayName'] || data[:username]
159
+ data[:id] = user_data['primaryKey'] unless user_data['primaryKey'].to_s.empty?
160
+
161
+ additional = (options[:additional] || [])
162
+ additional << :extended if options[:extended]
163
+ additional.each do |key|
164
+ data[key] = case key
165
+ when :raw
166
+ warn "RPXNow :raw is deprecated, please use :raw_response + e.g. data['raw_response']['profile']['verifiedEmail']"
167
+ user_data
168
+ when :raw_response
169
+ response
170
+ when :extended
171
+ response.reject{|k,v| ['profile','stat'].include?(k) }
172
+ else
173
+ user_data[key.to_s]
174
+ end
175
+ end
176
+ data
177
+ end
178
+
179
+ def unobtrusive_popup_code(text, subdomain, url, options={})
180
+ options = options.dup
181
+ html_options = options.delete(:html) || {}
182
+ html_options[:class] = "rpxnow #{html_options[:class]}".strip
183
+ html_options[:href] ||= popup_url(subdomain, url, options)
184
+ html_options = html_options.sort_by{|k,v|k.to_s}.map{|k,v| %{#{k}="#{v}"}}
185
+
186
+ %{<a #{html_options.join(' ')}>#{text}</a>}
187
+ end
188
+
189
+ def obtrusive_popup_code(text, subdomain, url, options = {})
190
+ unobtrusive_popup_code(text, subdomain, url, options) +
191
+ popup_source(subdomain, url, options)
192
+ end
193
+
194
+ def with_indifferent_access(hash)
195
+ hash.respond_to?(:with_indifferent_access) ? hash.with_indifferent_access : hash
196
+ end
197
+
198
+ class ServerError < RuntimeError; end #backwards compatibility / catch all
199
+ class ApiError < ServerError; end
200
+ class ServiceUnavailableError < ServerError; end
201
+ end
data/rpx_now.gemspec ADDED
@@ -0,0 +1,63 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{rpx_now}
8
+ s.version = "0.6.24"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Michael Grosser"]
12
+ s.date = %q{2011-02-21}
13
+ s.email = %q{grosser.michael@gmail.com}
14
+ s.files = [
15
+ "CHANGELOG",
16
+ "Gemfile",
17
+ "Gemfile.lock",
18
+ "MIGRATION",
19
+ "Rakefile",
20
+ "Readme.md",
21
+ "VERSION",
22
+ "certs/ssl_cert.pem",
23
+ "init.rb",
24
+ "lib/rpx_now.rb",
25
+ "lib/rpx_now/api.rb",
26
+ "lib/rpx_now/contacts_collection.rb",
27
+ "lib/rpx_now/user_integration.rb",
28
+ "lib/rpx_now/user_proxy.rb",
29
+ "rpx_now.gemspec",
30
+ "spec/fixtures/get_contacts_response.json",
31
+ "spec/integration/mapping_spec.rb",
32
+ "spec/rpx_now/api_spec.rb",
33
+ "spec/rpx_now/contacts_collection_spec.rb",
34
+ "spec/rpx_now/user_proxy_spec.rb",
35
+ "spec/rpx_now_spec.rb",
36
+ "spec/spec_helper.rb"
37
+ ]
38
+ s.homepage = %q{http://github.com/grosser/rpx_now}
39
+ s.require_paths = ["lib"]
40
+ s.rubygems_version = %q{1.4.2}
41
+ s.summary = %q{Helper to simplify RPX Now user login/creation}
42
+ s.test_files = [
43
+ "spec/integration/mapping_spec.rb",
44
+ "spec/rpx_now/api_spec.rb",
45
+ "spec/rpx_now/contacts_collection_spec.rb",
46
+ "spec/rpx_now/user_proxy_spec.rb",
47
+ "spec/rpx_now_spec.rb",
48
+ "spec/spec_helper.rb"
49
+ ]
50
+
51
+ if s.respond_to? :specification_version then
52
+ s.specification_version = 3
53
+
54
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
55
+ s.add_runtime_dependency(%q<json_pure>, [">= 0"])
56
+ else
57
+ s.add_dependency(%q<json_pure>, [">= 0"])
58
+ end
59
+ else
60
+ s.add_dependency(%q<json_pure>, [">= 0"])
61
+ end
62
+ end
63
+
@@ -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 == 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 :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
@@ -0,0 +1,33 @@
1
+ require 'spec/spec_helper'
2
+ require 'rpx_now/user_integration'
3
+
4
+ class User
5
+ include RPXNow::UserIntegration
6
+
7
+ def id
8
+ 5
9
+ end
10
+ end
11
+
12
+ describe RPXNow::UserProxy do
13
+ before { @user = User.new }
14
+
15
+ it "has a proxy" do
16
+ @user.rpx.class.should == RPXNow::UserProxy
17
+ end
18
+
19
+ it "has identifiers" do
20
+ RPXNow.should_receive(:mappings).with(@user.id).and_return(['identifiers'])
21
+ @user.rpx.identifiers.should == ['identifiers']
22
+ end
23
+
24
+ it "can map" do
25
+ RPXNow.should_receive(:map).with('identifier', @user.id)
26
+ @user.rpx.map('identifier')
27
+ end
28
+
29
+ it "can unmap" do
30
+ RPXNow.should_receive(:unmap).with('identifier', @user.id)
31
+ @user.rpx.unmap('identifier')
32
+ end
33
+ end