slayer-rpx_now 0.6.24

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'
@@ -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