grosser-rpx_now 0.3.1 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -2,11 +2,13 @@ Problem
2
2
  =======
3
3
  - OpenID is complex
4
4
  - OpenID is not universally used
5
+ - Facebook / Myspace / MS-LiveId / AOL connections require different libraries and knowledge
6
+ - Multiple heterogenouse providers are hard to map to a single user
5
7
 
6
8
  Solution
7
9
  ========
8
10
  - Use [RPX](http://rpxnow.com) for universal and usable user login
9
- - Make view/controller helpers for easy integration
11
+ - Use view/controller helpers for easy integration
10
12
 
11
13
  Usage
12
14
  =====
@@ -19,7 +21,7 @@ Install
19
21
  =======
20
22
  - As Rails plugin: `script/plugin install git://github.com/grosser/rpx_now.git `
21
23
  - As gem: `sudo gem install grosser-rpx_now --source http://gems.github.com/`
22
- - As gem from source: `git clone git://github.com/grosser/rpx_now.git`,`cd rpx_now`,`rake manifest`,`rake install`
24
+ - As gem from source: `git clone git://github.com/grosser/rpx_now.git`,`cd rpx_now && rake install`
23
25
 
24
26
  Examples
25
27
  ========
@@ -32,6 +34,8 @@ View
32
34
  OR
33
35
  <%=RPXNow.popup_code('Login here...','mywebsite',rpx_token_sessions_url,:language=>'de')%>
34
36
 
37
+ (`popup_code` can also be called with `:unobstrusive=>true`)
38
+
35
39
  Controller
36
40
  ----------
37
41
  # simple: use defaults
@@ -54,8 +58,15 @@ Controller
54
58
  RPXNow.api_key = 'YOUR RPX API KEY'
55
59
  RPXNow.user_data(params[:token],:extended=>'true')
56
60
 
57
- Advanced: Mappings
58
- ------------------
61
+ Advanced
62
+ --------
63
+ ###Versions
64
+ The version of RPXNow api can be set globally:
65
+ RPXNow.api_version = 2
66
+ Or local on each call:
67
+ RPXNow.mappings(primary_key, :api_version=>1)
68
+
69
+ ###Mappings
59
70
  You can map your primary keys (e.g. user.id) to identifiers, so that
60
71
  users can login to the same account with multiple identifiers.
61
72
  #add a mapping
@@ -72,11 +83,13 @@ After a primary key is mapped to an identifier, when a user logs in with this id
72
83
 
73
84
  TODO
74
85
  ====
75
- - remove activesupport dependency, use something smaller (json gem looks good but: JSON.parse("{x:1}")==BOOM)
76
86
  - validate RPXNow.com SSL certificate
77
87
 
78
88
  Author
79
89
  ======
80
- Michael Grosser
90
+ ###Contributors
91
+ - [DBA](http://github.com/DBA)
92
+
93
+ [Michael Grosser](http://pragmatig.wordpress.com)
81
94
  grosser.michael@gmail.com
82
95
  Hereby placed under public domain, do what you want, just do not hold me accountable...
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :patch: 1
3
3
  :major: 0
4
- :minor: 3
4
+ :minor: 4
data/lib/rpx_now.rb CHANGED
@@ -1,14 +1,20 @@
1
- require 'activesupport'
1
+ require 'json'
2
+
2
3
  module RPXNow
3
4
  extend self
4
5
 
5
6
  attr_writer :api_key
7
+ attr_accessor :api_version
8
+ self.api_version = 2
9
+
10
+ # retrieve the users data, or return nil when nothing could be read/token was invalid
11
+ # or data was not found
12
+ def user_data(token, *args)
13
+ api_key, version, options = extract_key_version_and_options!(args)
14
+ options = {:token=>token,:apiKey=>api_key}.merge options
6
15
 
7
- # retrieve the users data, or return nil when nothing could be read/token was invalid or data was not found
8
- def user_data(token,*args)
9
- api_key, parameters = extract_key_and_options(args)
10
16
  begin
11
- data = secure_json_post('https://rpxnow.com/api/v2/auth_info',{:token=>token,:apiKey=>api_key||@api_key}.merge(parameters))
17
+ data = secure_json_post("https://rpxnow.com/api/v#{version}/auth_info", options)
12
18
  rescue ServerError
13
19
  return nil if $!.to_s =~ /token/ or $!.to_s=~/Data not found/
14
20
  raise
@@ -17,19 +23,24 @@ module RPXNow
17
23
  end
18
24
 
19
25
  # maps an identifier to an primary-key (e.g. user.id)
20
- def map(identifier,primary_key,*args)
21
- api_key, options = extract_key_and_options(args)
22
- secure_json_post('https://rpxnow.com/api/v2/map',{:identifier=>identifier,:primaryKey=>primary_key,:apiKey=>api_key||@api_key}.merge(options))
26
+ def map(identifier, primary_key, *args)
27
+ api_key, version, options = extract_key_version_and_options!(args)
28
+ options = {:identifier=>identifier,:primaryKey=>primary_key,:apiKey=>api_key}.merge options
29
+ secure_json_post("https://rpxnow.com/api/v#{version}/map", options)
23
30
  end
24
31
 
25
32
  # un-maps an identifier to an primary-key (e.g. user.id)
26
- def unmap(identifier,primary_key,api_key=nil)
27
- secure_json_post('https://rpxnow.com/api/v2/unmap',{:identifier=>identifier,:primaryKey=>primary_key,:apiKey=>api_key||@api_key})
33
+ def unmap(identifier, primary_key, *args)
34
+ api_key, version, options = extract_key_version_and_options!(args)
35
+ options = {:identifier=>identifier,:primaryKey=>primary_key,:apiKey=>api_key}.merge options
36
+ secure_json_post("https://rpxnow.com/api/v#{version}/unmap", options)
28
37
  end
29
38
 
30
39
  # returns an array of identifiers which are mapped to one of your primary-keys (e.g. user.id)
31
- def mappings(primary_key,api_key=nil)
32
- data = secure_json_post('https://rpxnow.com/api/v2/mappings',{:primaryKey=>primary_key,:apiKey=>api_key||@api_key})
40
+ def mappings(primary_key, *args)
41
+ api_key, version, options = extract_key_version_and_options!(args)
42
+ options = {:primaryKey=>primary_key,:apiKey=>api_key}.merge options
43
+ data = secure_json_post("https://rpxnow.com/api/v#{version}/mappings", options)
33
44
  data['identifiers']
34
45
  end
35
46
 
@@ -41,12 +52,28 @@ module RPXNow
41
52
  EOF
42
53
  end
43
54
 
44
- def popup_code(text,subdomain,url,options={})
45
- <<EOF
46
- <a class="rpxnow" onclick="return false;" href="https://#{subdomain}.rpxnow.com/openid/v2/signin?token_url=#{url}">
55
+ def popup_code(text, subdomain, url, options = {})
56
+ if options[:unobtrusive]
57
+ unobtrusive_popup_code(text, subdomain, url, options)
58
+ else
59
+ obtrusive_popup_code(text, subdomain, url, options)
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def unobtrusive_popup_code(text, subdomain, url, options={})
66
+ version = extract_version! options
67
+ "<a class=\"rpxnow\" href=\"https://#{subdomain}.rpxnow.com/openid/v#{version}/signin?token_url=#{url}\">#{text}</a>"
68
+ end
69
+
70
+ def obtrusive_popup_code(text, subdomain, url, options = {})
71
+ version = extract_version! options
72
+ <<EOF
73
+ <a class="rpxnow" onclick="return false;" href="https://#{subdomain}.rpxnow.com/openid/v#{version}/signin?token_url=#{url}">
47
74
  #{text}
48
75
  </a>
49
- <script src="https://rpxnow.com/openid/v2/widget" type="text/javascript"></script>
76
+ <script src="https://rpxnow.com/openid/v#{version}/widget" type="text/javascript"></script>
50
77
  <script type="text/javascript">
51
78
  //<![CDATA[
52
79
  RPXNOW.token_url = "#{url}";
@@ -59,7 +86,12 @@ EOF
59
86
  EOF
60
87
  end
61
88
 
62
- private
89
+ def extract_key_version_and_options!(args)
90
+ key, options = extract_key_and_options(args)
91
+ version = extract_version! options
92
+ [key, version, options]
93
+ end
94
+
63
95
  # [API_KEY,{options}] or
64
96
  # [{options}] or
65
97
  # []
@@ -74,6 +106,10 @@ private
74
106
  end
75
107
  end
76
108
 
109
+ def extract_version!(options)
110
+ options.delete(:api_version) || api_version
111
+ end
112
+
77
113
  def read_user_data_from_response(response)
78
114
  user_data = response['profile']
79
115
  data = {}
@@ -85,7 +121,7 @@ private
85
121
  end
86
122
 
87
123
  def secure_json_post(url,data={})
88
- data = ActiveSupport::JSON.decode(post(url,data))
124
+ data = JSON.parse(post(url,data))
89
125
  raise ServerError.new(data['err']) if data['err']
90
126
  raise ServerError.new(data.inspect) unless data['stat']=='ok'
91
127
  data
@@ -101,11 +137,24 @@ private
101
137
  #TODO do we really want to verify the certificate? http://notetoself.vrensk.com/2008/09/verified-https-in-ruby/
102
138
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
103
139
  end
104
- resp, data = http.post(url.path, data.to_query)
140
+ resp, data = http.post(url.path, to_query(data))
105
141
  raise "POST FAILED:"+resp.inspect unless resp.is_a? Net::HTTPOK
106
142
  data
107
143
  end
108
144
 
145
+ def to_query(data = {})
146
+ return data.to_query if Hash.respond_to? :to_query
147
+ return "" if data.empty?
148
+
149
+ #simpler to_query
150
+ query_data = []
151
+ data.each do |k, v|
152
+ query_data << "#{k}=#{v}"
153
+ end
154
+
155
+ return query_data.join('&')
156
+ end
157
+
109
158
  class ServerError < Exception
110
159
  #to_s returns message(which is a hash...)
111
160
  def to_s
data/spec/rpx_now_spec.rb CHANGED
@@ -1,33 +1,61 @@
1
1
  require File.expand_path("spec_helper", File.dirname(__FILE__))
2
2
 
3
3
  API_KEY = '4b339169026742245b754fa338b9b0aebbd0a733'
4
+ API_VERSION = RPXNow.api_version
4
5
 
5
6
  describe RPXNow do
6
7
  before do
7
- RPXNow.api_key=nil
8
+ RPXNow.api_key = nil
9
+ RPXNow.api_version = API_VERSION
8
10
  end
9
11
 
10
12
  describe :api_key= do
11
13
  it "stores the api key, so i do not have to supply everytime" do
12
14
  RPXNow.api_key='XX'
13
- RPXNow.expects(:post).with{|x,data|data[:apiKey]=='XX'}.returns '{"stat":"ok"}'
15
+ RPXNow.expects(:post).with{|x,data|data[:apiKey]=='XX'}.returns %Q({"stat":"ok"})
14
16
  RPXNow.mappings(1)
15
17
  end
16
18
  end
19
+
20
+ describe :api_version= do
21
+ it "can be set to a api_version globally" do
22
+ RPXNow.api_version = 5
23
+ RPXNow.popup_code('x','y','z').should =~ %r(/openid/v5/signin)
24
+ end
25
+ end
17
26
 
18
27
  describe :embed_code do
19
28
  it "contains the subdomain" do
20
29
  RPXNow.embed_code('xxx','my_url').should =~ /xxx/
21
30
  end
31
+
22
32
  it "contains the url" do
23
33
  RPXNow.embed_code('xxx','my_url').should =~ /token_url=my_url/
24
34
  end
25
35
  end
26
36
 
27
37
  describe :popup_code do
38
+ it "defaults to obtrusive output" do
39
+ RPXNow.popup_code('sign on', 'subdomain', 'http://fake.domain.com/').should =~ /script src=/
40
+ end
41
+
42
+ it "can build an unobtrusive widget with specific version" do
43
+ expected = %Q(<a class="rpxnow" href="https://subdomain.rpxnow.com/openid/v300/signin?token_url=http://fake.domain.com/">sign on</a>)
44
+ RPXNow.popup_code('sign on', 'subdomain', 'http://fake.domain.com/', { :unobtrusive => true, :api_version => 300 }).should == expected
45
+ end
46
+
47
+ it "allows to specify the version of the widget" do
48
+ RPXNow.popup_code('x','y','z', :api_version => 300).should =~ %r(/openid/v300/signin)
49
+ end
50
+
51
+ it "defaults to widget version 2" do
52
+ RPXNow.popup_code('x','y','z').should =~ %r(/openid/v2/signin)
53
+ end
54
+
28
55
  it "defaults to english" do
29
56
  RPXNow.popup_code('x','y','z').should =~ /RPXNOW.language_preference = 'en'/
30
57
  end
58
+
31
59
  it "has a changeable language" do
32
60
  RPXNow.popup_code('x','y','z',:language=>'de').should =~ /RPXNOW.language_preference = 'de'/
33
61
  end
@@ -35,32 +63,39 @@ describe RPXNow do
35
63
 
36
64
  describe :user_data do
37
65
  def fake_response
38
- '{"profile":{"verifiedEmail":"grosser.michael@googlemail.com","displayName":"Michael Grosser","preferredUsername":"grosser.michael","identifier":"https:\/\/www.google.com\/accounts\/o8\/id?id=AItOawmaOlyYezg_WfbgP_qjaUyHjmqZD9qNIVM","email":"grosser.michael@gmail.com"},"stat":"ok"}'
66
+ %Q({"profile":{"verifiedEmail":"grosser.michael@googlemail.com","displayName":"Michael Grosser","preferredUsername":"grosser.michael","identifier":"https:\/\/www.google.com\/accounts\/o8\/id?id=AItOawmaOlyYezg_WfbgP_qjaUyHjmqZD9qNIVM","email":"grosser.michael@gmail.com"},"stat":"ok"})
39
67
  end
68
+
40
69
  it "is empty when used with an invalid token" do
41
70
  RPXNow.user_data('xxxx',API_KEY).should == nil
42
71
  end
72
+
43
73
  it "is empty when used with an unknown token" do
44
74
  RPXNow.user_data('60d8c6374f4e9d290a7b55f39da7cc6435aef3d3',API_KEY).should == nil
45
75
  end
76
+
46
77
  it "parses JSON response to user data" do
47
78
  RPXNow.expects(:post).returns fake_response
48
79
  RPXNow.user_data('','x').should == {:name=>'Michael Grosser',:email=>'grosser.michael@googlemail.com',:identifier=>"https://www.google.com/accounts/o8/id?id=AItOawmaOlyYezg_WfbgP_qjaUyHjmqZD9qNIVM"}
49
80
  end
81
+
50
82
  it "adds a :id when primaryKey was returned" do
51
- RPXNow.expects(:post).returns fake_response.sub('"verifiedEmail"','primaryKey:"2","verifiedEmail"')
83
+ RPXNow.expects(:post).returns fake_response.sub(%Q("verifiedEmail"), %Q("primaryKey":"2","verifiedEmail"))
52
84
  RPXNow.user_data('','x')[:id].should == 2
53
85
  end
86
+
54
87
  it "hands JSON response to supplied block" do
55
- RPXNow.expects(:post).returns "{x:1,stat:'ok'}"
88
+ RPXNow.expects(:post).returns %Q({"x":"1","stat":"ok"})
56
89
  response = nil
57
90
  RPXNow.user_data('','x'){|data| response = data}
58
- response.should == {'x'=>1,'stat'=>'ok'}
91
+ response.should == {"x" => "1", "stat" => "ok"}
59
92
  end
93
+
60
94
  it "returns what the supplied block returned" do
61
- RPXNow.expects(:post).returns "{x:1,stat:'ok'}"
95
+ RPXNow.expects(:post).returns %Q({"x":"1","stat":"ok"})
62
96
  RPXNow.user_data('','x'){|data| "x"}.should == 'x'
63
97
  end
98
+
64
99
  it "can send additional parameters" do
65
100
  RPXNow.expects(:post).with{|url,data|
66
101
  data[:extended].should == 'true'
@@ -73,6 +108,7 @@ describe RPXNow do
73
108
  it "reads secondary names" do
74
109
  RPXNow.send(:read_user_data_from_response,{'profile'=>{'preferredUsername'=>'1'}})[:name].should == '1'
75
110
  end
111
+
76
112
  it "parses email when no name is found" do
77
113
  RPXNow.send(:read_user_data_from_response,{'profile'=>{'email'=>'1@xxx.com'}})[:name].should == '1'
78
114
  end
@@ -80,38 +116,45 @@ describe RPXNow do
80
116
 
81
117
  describe :secure_json_post do
82
118
  it "parses json when status is ok" do
83
- RPXNow.expects(:post).returns '{stat:"ok",data:"xx"}'
84
- RPXNow.send(:secure_json_post,'xx')['data'].should == 'xx'
119
+ RPXNow.expects(:post).returns %Q({"stat":"ok","data":"xx"})
120
+ RPXNow.send(:secure_json_post, %Q("yy"))['data'].should == "xx"
85
121
  end
122
+
86
123
  it "raises when there is a communication error" do
87
- RPXNow.expects(:post).returns '{"err":"wtf",stat:"ok"}'
124
+ RPXNow.expects(:post).returns %Q({"err":"wtf","stat":"ok"})
88
125
  lambda{RPXNow.send(:secure_json_post,'xx')}.should raise_error RPXNow::ServerError
89
126
  end
127
+
90
128
  it "raises when status is not ok" do
91
- RPXNow.expects(:post).returns '{"stat":"err"}'
129
+ RPXNow.expects(:post).returns %Q({"stat":"err"})
92
130
  lambda{RPXNow.send(:secure_json_post,'xx')}.should raise_error RPXNow::ServerError
93
131
  end
94
132
  end
95
133
 
96
134
  describe :mappings do
97
135
  it "parses JSON response to unmap data" do
98
- RPXNow.expects(:post).returns '{"stat":"ok", "identifiers": ["http://test.myopenid.com/"]}'
136
+ RPXNow.expects(:post).returns %Q({"stat":"ok", "identifiers": ["http://test.myopenid.com/"]})
99
137
  RPXNow.mappings(1, "x").should == ["http://test.myopenid.com/"]
100
138
  end
101
139
  end
102
140
 
103
141
  describe :map do
104
142
  it "adds a mapping" do
105
- RPXNow.expects(:post).returns '{"stat":"ok"}'
143
+ RPXNow.expects(:post).returns %Q({"stat":"ok"})
106
144
  RPXNow.map('http://test.myopenid.com',1, API_KEY)
107
145
  end
108
146
  end
109
147
 
110
148
  describe :unmap do
111
149
  it "unmaps a indentifier" do
112
- RPXNow.expects(:post).returns '{"stat":"ok"}'
150
+ RPXNow.expects(:post).returns %Q({"stat":"ok"})
113
151
  RPXNow.unmap('http://test.myopenid.com', 1, "x")
114
152
  end
153
+
154
+ it "can be called with a specific version" do
155
+ RPXNow.expects(:secure_json_post).with{|a,b|a == "https://rpxnow.com/api/v300/unmap"}
156
+ RPXNow.unmap('http://test.myopenid.com', 1, :api_key=>'xxx', :api_version=>300)
157
+ end
115
158
  end
116
159
 
117
160
  describe :mapping_integration do
@@ -144,4 +187,15 @@ describe RPXNow do
144
187
  RPXNow.mappings(1,API_KEY).should == [@k1]
145
188
  end
146
189
  end
190
+
191
+ describe :to_query do
192
+ it "should not depend on active support" do
193
+ RPXNow.send('to_query', {:one => " abc"}).should == "one= abc"
194
+ end
195
+
196
+ it "should use ActiveSupport core extensions" do
197
+ require 'activesupport'
198
+ RPXNow.send('to_query', {:one => " abc"}).should == "one=+abc"
199
+ end
200
+ end
147
201
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grosser-rpx_now
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
@@ -9,11 +9,12 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-02-13 00:00:00 -08:00
12
+ date: 2009-04-15 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
17
+ type: :runtime
17
18
  version_requirement:
18
19
  version_requirements: !ruby/object:Gem::Requirement
19
20
  requirements: