Browse files

merge conflic and now everything is one big commit :<

  • Loading branch information...
1 parent 74b5a8b commit 80c77e44aea164860d65d3898b3960f05e6ca985 @grosser committed Jun 17, 2009
Showing with 3,300 additions and 124 deletions.
  1. +5 −0 CHANGELOG
  2. +21 −33 README.markdown
  3. +2 −2 VERSION.yml
  4. +3,154 −0 certs/ssl_cert.pem
  5. +45 −43 lib/rpx_now.rb
  6. +4 −4 rpx_now.gemspec
  7. +69 −42 spec/rpx_now_spec.rb
View
5 CHANGELOG
@@ -1,3 +1,8 @@
+0.5.0
+----
+ * ApiError is raised when API KEY was invalid (before did only return nil)
+ * ServerError is now seperated into ApiError and ServiceUnavailableError
+
0.4.2
----
* default IDs are strings, not integers, since RPX supports both
View
54 README.markdown
@@ -1,8 +1,7 @@
Problem
=======
- - OpenID is complex
- - OpenID is not universally used
- - Facebook / Myspace / MS-LiveId / AOL connections require different libraries and knowledge
+ - OpenID is complex, limited and hard to use for users
+ - Facebook / Twitter / Myspace / Google / MS-LiveId / AOL connections require different libraries and knowledge
- Multiple heterogenouse providers are hard to map to a single user
Solution
@@ -25,70 +24,59 @@ Install
=======
- As Rails plugin: `script/plugin install git://github.com/grosser/rpx_now.git `
- As gem: `sudo gem install grosser-rpx_now --source http://gems.github.com/`
- - As gem from source: `git clone git://github.com/grosser/rpx_now.git`,`cd rpx_now && rake install`
Examples
========
View
----
- #login.erb
- #here 'mywebsite' is your subdomain/realm on RPX
+ #'mywebsite' is your subdomain/realm on RPX
<%=RPXNow.embed_code('mywebsite',rpx_token_sessions_url)%>
OR
<%=RPXNow.popup_code('Login here...','mywebsite',rpx_token_sessions_url,:language=>'de')%>
- (`popup_code` can also be called with `:unobstrusive=>true`)
+`popup_code` can also be called with `:unobstrusive=>true`
Controller
----------
- # simple: use defaults
- # user_data returns e.g.
- # {:name=>'John Doe', :username => 'john', :email=>'john@doe.com', :identifier=>'blug.google.com/openid/dsdfsdfs3f3'}
- # when no user_data was found (invalid token supplied), data is empty, you may want to handle that seperatly...
- # your user model must have an identifier column
+ RPXNow.api_key = "YOU RPX API KEY"
+
+ # user_data
+ # found: {:name=>'John Doe', :username => 'john', :email=>'john@doe.com', :identifier=>'blug.google.com/openid/dsdfsdfs3f3'}
+ # not found: nil (can happen with e.g. invalid tokens)
def rpx_token
- data = RPXNow.user_data(params[:token],'YOUR RPX API KEY')
+ raise "hackers?" unless data = RPXNow.user_data(params[:token])
self.current_user = User.find_by_identifier(data[:identifier]) || User.create!(data)
redirect_to '/'
end
- # process the raw response yourself:
- RPXNow.user_data(params[:token],'YOUR RPX API KEY'){|raw| {:email=>raw['profile']['verifiedEmail']}}
-
- # request extended parameters (most users and APIs do not supply them)
- RPXNow.user_data(params[:token],'YOUR RPX API KEY',:extended=>'true'){|raw| ...have a look at the RPX API DOCS...}
+ # raw request processing
+ RPXNow.user_data(params[:token]){|raw| {:email=>raw['profile']['verifiedEmail']} }
- # you can provide the api key once, and leave it out on all following calls
- RPXNow.api_key = 'YOUR RPX API KEY'
- RPXNow.user_data(params[:token],:extended=>'true')
+ # raw request with extended parameters (most users and APIs do not supply them)
+ RPXNow.user_data(params[:token], :extended=>'true'){|raw| ...have a look at the RPX API DOCS...}
Advanced
--------
###Versions
-The version of RPXNow api can be set globally:
RPXNow.api_version = 2
-Or local on each call:
- RPXNow.mappings(primary_key, :api_version=>1)
###Mappings
You can map your primary keys (e.g. user.id) to identifiers, so that
users can login to the same account with multiple identifiers.
- #add a mapping
- RPXNow.map(identifier,primary_key,'YOUR RPX API KEY')
-
- #remove a mapping
- RPXNow.unmap(identifier,primary_key,'YOUR RPX API KEY')
-
- #show mappings
- RPXNow.mappings(primary_key,'YOUR RPX API KEY') # [identifier1,identifier2,...]
+ RPXNow.map(identifier, primary_key) #add a mapping
+ RPXNow.unmap(identifier, primary_key) #remove a mapping
+ RPXNow.mappings(primary_key) # [identifier1,identifier2,...]
After a primary key is mapped to an identifier, when a user logs in with this identifier,
`RPXNow.user_data` will contain his `primaryKey` as `:id`.
TODO
====
- - validate RPXNow.com SSL certificate
+ - add provider?
+ - add get_contacts (premium rpx service)
+ - add all_mappings
+
Author
======
View
4 VERSION.yml
@@ -1,4 +1,4 @@
---
-:minor: 4
-:patch: 2
+:minor: 5
+:patch: 0
:major: 0
View
3,154 certs/ssl_cert.pem
3,154 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
88 lib/rpx_now.rb
@@ -3,7 +3,10 @@
module RPXNow
extend self
- attr_writer :api_key
+ HOST = 'rpxnow.com'
+ SSL_CERT = File.join(File.dirname(__FILE__), '..', 'certs', 'ssl_cert.pem')
+
+ attr_accessor :api_key
attr_accessor :api_version
self.api_version = 2
@@ -14,9 +17,9 @@ def user_data(token, *args)
options = {:token=>token,:apiKey=>api_key}.merge options
begin
- data = secure_json_post("https://rpxnow.com/api/v#{version}/auth_info", options)
+ data = secure_json_post("/api/v#{version}/auth_info", options)
rescue ServerError
- return nil if $!.to_s =~ /token/ or $!.to_s=~/Data not found/
+ return nil if $!.to_s=~/Data not found/
raise
end
if block_given? then yield(data) else read_user_data_from_response(data) end
@@ -26,27 +29,27 @@ def user_data(token, *args)
def map(identifier, primary_key, *args)
api_key, version, options = extract_key_version_and_options!(args)
options = {:identifier=>identifier,:primaryKey=>primary_key,:apiKey=>api_key}.merge options
- secure_json_post("https://rpxnow.com/api/v#{version}/map", options)
+ secure_json_post("/api/v#{version}/map", options)
end
# un-maps an identifier to an primary-key (e.g. user.id)
def unmap(identifier, primary_key, *args)
api_key, version, options = extract_key_version_and_options!(args)
options = {:identifier=>identifier,:primaryKey=>primary_key,:apiKey=>api_key}.merge options
- secure_json_post("https://rpxnow.com/api/v#{version}/unmap", options)
+ secure_json_post("/api/v#{version}/unmap", options)
end
# returns an array of identifiers which are mapped to one of your primary-keys (e.g. user.id)
def mappings(primary_key, *args)
api_key, version, options = extract_key_version_and_options!(args)
options = {:primaryKey=>primary_key,:apiKey=>api_key}.merge options
- data = secure_json_post("https://rpxnow.com/api/v#{version}/mappings", options)
+ data = secure_json_post("/api/v#{version}/mappings", options)
data['identifiers']
end
def embed_code(subdomain,url)
<<EOF
-<iframe src="https://#{subdomain}.rpxnow.com/openid/embed?token_url=#{url}"
+<iframe src="https://#{subdomain}.#{HOST}/openid/embed?token_url=#{url}"
scrolling="no" frameBorder="no" style="width:400px;height:240px;">
</iframe>
EOF
@@ -64,16 +67,16 @@ def popup_code(text, subdomain, url, options = {})
def unobtrusive_popup_code(text, subdomain, url, options={})
version = extract_version! options
- "<a class=\"rpxnow\" href=\"https://#{subdomain}.rpxnow.com/openid/v#{version}/signin?token_url=#{url}\">#{text}</a>"
+ "<a class=\"rpxnow\" href=\"https://#{subdomain}.#{HOST}/openid/v#{version}/signin?token_url=#{url}\">#{text}</a>"
end
def obtrusive_popup_code(text, subdomain, url, options = {})
version = extract_version! options
<<EOF
-<a class="rpxnow" onclick="return false;" href="https://#{subdomain}.rpxnow.com/openid/v#{version}/signin?token_url=#{url}">
+<a class="rpxnow" onclick="return false;" href="https://#{subdomain}.#{HOST}/openid/v#{version}/signin?token_url=#{url}">
#{text}
</a>
-<script src="https://rpxnow.com/openid/v#{version}/widget" type="text/javascript"></script>
+<script src="https://#{HOST}/openid/v#{version}/widget" type="text/javascript"></script>
<script type="text/javascript">
//<![CDATA[
RPXNOW.token_url = "#{url}";
@@ -115,51 +118,50 @@ def read_user_data_from_response(response)
data = {}
data[:identifier] = user_data['identifier']
data[:email] = user_data['verifiedEmail'] || user_data['email']
- data[:username] = user_data['preferredUsername'] || data[:email].sub(/@.*/,'')
+ data[:username] = user_data['preferredUsername'] || data[:email].to_s.sub(/@.*/,'')
data[:name] = user_data['displayName'] || data[:username]
data[:id] = user_data['primaryKey'] unless user_data['primaryKey'].to_s.empty?
data
end
- def secure_json_post(url,data={})
- data = JSON.parse(post(url,data))
- raise ServerError.new(data['err']) if data['err']
- raise ServerError.new(data.inspect) unless data['stat']=='ok'
- data
+ def secure_json_post(path, data)
+ parse_response(post(path,data))
end
- def post(url,data)
+ def post(path, data)
require 'net/http'
- url = URI.parse(url)
- http = Net::HTTP.new(url.host, url.port)
- if url.scheme == 'https'
- require 'net/https'
- http.use_ssl = true
- #TODO do we really want to verify the certificate? http://notetoself.vrensk.com/2008/09/verified-https-in-ruby/
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
- end
- resp, data = http.post(url.path, to_query(data))
- raise "POST FAILED:"+resp.inspect unless resp.is_a? Net::HTTPOK
- data
+ require 'net/https'
+ request = Net::HTTP::Get.new(path)
+ request.form_data = data
+ make_request(request)
end
- def to_query(data = {})
- return data.to_query if Hash.respond_to? :to_query
- return "" if data.empty?
-
- #simpler to_query
- query_data = []
- data.each do |k, v|
- query_data << "#{k}=#{v}"
- end
-
- return query_data.join('&')
+ def make_request(request)
+ http = Net::HTTP.new(HOST, 443)
+ http.use_ssl = true
+ http.ca_file = SSL_CERT
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ http.verify_depth = 5
+ http.request(request)
end
- class ServerError < Exception
- #to_s returns message(which is a hash...)
- def to_s
- super.to_s
+ def parse_response(response)
+ if response.code.to_i >= 400
+ raise ServiceUnavailableError, "The RPX service is temporarily unavailable. (4XX)"
+ else
+ result = JSON.parse(response.body)
+ return result unless result['err']
+
+ code = result['err']['code']
+ if code == -1
+ raise ServiceUnavailableError, "The RPX service is temporarily unavailable."
+ else
+ raise ApiError, "Got error: #{result['err']['msg']} (code: #{code}), HTTP status: #{response.code}"
+ end
end
end
+
+ class ServerError < Exception; end #backwards compatibility / catch all
+ class ApiError < ServerError; end
+ class ServiceUnavailableError < ServerError; end
end
View
8 rpx_now.gemspec
@@ -2,11 +2,11 @@
Gem::Specification.new do |s|
s.name = %q{rpx_now}
- s.version = "0.4.2"
+ s.version = "0.5.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Michael Grosser"]
- s.date = %q{2009-05-22}
+ s.date = %q{2009-06-17}
s.email = %q{grosser.michael@gmail.com}
s.extra_rdoc_files = [
"README.markdown"
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
s.homepage = %q{http://github.com/grosser/rpx_now}
s.rdoc_options = ["--charset=UTF-8"]
s.require_paths = ["lib"]
- s.rubygems_version = %q{1.3.2}
+ s.rubygems_version = %q{1.3.1}
s.summary = %q{Helper to simplify RPX Now user login/creation}
s.test_files = [
"spec/rpx_now_spec.rb",
@@ -32,7 +32,7 @@ Gem::Specification.new do |s|
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
- s.specification_version = 3
+ s.specification_version = 2
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<activesupport>, [">= 0"])
View
111 spec/rpx_now_spec.rb
@@ -5,14 +5,14 @@
describe RPXNow do
before do
- RPXNow.api_key = nil
+ RPXNow.api_key = API_KEY
RPXNow.api_version = API_VERSION
end
describe :api_key= do
it "stores the api key, so i do not have to supply everytime" do
RPXNow.api_key='XX'
- RPXNow.expects(:post).with{|x,data|data[:apiKey]=='XX'}.returns %Q({"stat":"ok"})
+ RPXNow.expects(:post).with{|x,data|data[:apiKey]=='XX'}.returns mock(:code=>'200', :body=>%Q({"stat":"ok"}))
RPXNow.mappings(1)
end
end
@@ -62,50 +62,77 @@
end
describe :user_data do
+ before do
+ @response_body = %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"})
+ @fake_user_data = {'profile'=>{}}
+ end
+
def fake_response
- %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"})
+ mock(:code=>"200",:body=>@response_body)
end
- it "is empty when used with an invalid token" do
- RPXNow.user_data('xxxx',API_KEY).should == nil
+ it "raises ApiError when used with an invalid token" do
+ lambda{
+ RPXNow.user_data('xxxx')
+ }.should raise_error(RPXNow::ApiError)
end
it "is empty when used with an unknown token" do
- RPXNow.user_data('60d8c6374f4e9d290a7b55f39da7cc6435aef3d3',API_KEY).should == nil
+ RPXNow.user_data('60d8c6374f4e9d290a7b55f39da7cc6435aef3d3').should == nil
end
it "parses JSON response to user data" do
RPXNow.expects(:post).returns fake_response
- 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", :username => 'grosser.michael'}
+ RPXNow.user_data('').should == {:name=>'Michael Grosser',:email=>'grosser.michael@googlemail.com',:identifier=>"https://www.google.com/accounts/o8/id?id=AItOawmaOlyYezg_WfbgP_qjaUyHjmqZD9qNIVM", :username => 'grosser.michael'}
end
it "adds a :id when primaryKey was returned" do
- RPXNow.expects(:post).returns fake_response.sub(%Q("verifiedEmail"), %Q("primaryKey":"2","verifiedEmail"))
- RPXNow.user_data('','x')[:id].should == '2'
+ @response_body.sub!(%Q("verifiedEmail"), %Q("primaryKey":"2","verifiedEmail"))
+ RPXNow.expects(:post).returns fake_response
+ RPXNow.user_data('')[:id].should == '2'
end
it "handles primaryKeys that are not numeric" do
- RPXNow.expects(:post).returns fake_response.sub(%Q("verifiedEmail"), %Q("primaryKey":"dbalatero","verifiedEmail"))
- RPXNow.user_data('','x')[:id].should == 'dbalatero'
+ @response_body.sub!(%Q("verifiedEmail"), %Q("primaryKey":"dbalatero","verifiedEmail"))
+ RPXNow.expects(:post).returns fake_response
+ RPXNow.user_data('')[:id].should == 'dbalatero'
end
it "hands JSON response to supplied block" do
- RPXNow.expects(:post).returns %Q({"x":"1","stat":"ok"})
+ RPXNow.expects(:post).returns mock(:code=>'200',:body=>%Q({"x":"1","stat":"ok"}))
response = nil
- RPXNow.user_data('','x'){|data| response = data}
+ RPXNow.user_data(''){|data| response = data}
response.should == {"x" => "1", "stat" => "ok"}
end
it "returns what the supplied block returned" do
- RPXNow.expects(:post).returns %Q({"x":"1","stat":"ok"})
- RPXNow.user_data('','x'){|data| "x"}.should == 'x'
+ RPXNow.expects(:post).returns mock(:code=>'200',:body=>%Q({"x":"1","stat":"ok"}))
+ RPXNow.user_data(''){|data| "x"}.should == 'x'
end
it "can send additional parameters" do
RPXNow.expects(:post).with{|url,data|
data[:extended].should == 'true'
}.returns fake_response
- RPXNow.user_data('','x',:extended=>'true')
+ RPXNow.user_data('',:extended=>'true')
+ end
+
+ it "works with api key as 2nd parameter (backwards compatibility)" do
+ RPXNow.expects(:secure_json_post).with('/api/v2/auth_info', :apiKey=>'THE KEY', :token=>'id').returns @fake_user_data
+ RPXNow.user_data('id', 'THE KEY')
+ RPXNow.api_key.should == API_KEY
+ end
+
+ it "works with api key as 2nd parameter and options (backwards compatibility)" do
+ RPXNow.expects(:secure_json_post).with('/api/v2/auth_info', :apiKey=>'THE KEY', :extended=>'abc', :token=>'id' ).returns @fake_user_data
+ RPXNow.user_data('id', 'THE KEY', :extended=>'abc')
+ RPXNow.api_key.should == API_KEY
+ end
+
+ it "works with api version as option (backwards compatibility)" do
+ RPXNow.expects(:secure_json_post).with('/api/v123/auth_info', :apiKey=>API_KEY, :token=>'id', :extended=>'abc').returns @fake_user_data
+ RPXNow.user_data('id', :extended=>'abc', :api_version=>123)
+ RPXNow.api_version.should == API_VERSION
end
end
@@ -119,45 +146,56 @@ def fake_response
end
end
- describe :secure_json_post do
+ describe :parse_response do
it "parses json when status is ok" do
- RPXNow.expects(:post).returns %Q({"stat":"ok","data":"xx"})
- RPXNow.send(:secure_json_post, %Q("yy"))['data'].should == "xx"
+ response = mock(:code=>'200', :body=>%Q({"stat":"ok","data":"xx"}))
+ RPXNow.send(:parse_response, response)['data'].should == "xx"
end
-
+
it "raises when there is a communication error" do
- RPXNow.expects(:post).returns %Q({"err":"wtf","stat":"ok"})
- lambda{RPXNow.send(:secure_json_post,'xx')}.should raise_error(RPXNow::ServerError)
+ response = stub(:code=>'200', :body=>%Q({"err":"wtf","stat":"ok"}))
+ lambda{
+ RPXNow.send(:parse_response,response)
+ }.should raise_error(RPXNow::ApiError)
end
-
- it "raises when status is not ok" do
- RPXNow.expects(:post).returns %Q({"stat":"err"})
- lambda{RPXNow.send(:secure_json_post,'xx')}.should raise_error(RPXNow::ServerError)
+
+ it "raises when service has downtime" do
+ response = stub(:code=>'200', :body=>%Q({"err":{"code":-1},"stat":"ok"}))
+ lambda{
+ RPXNow.send(:parse_response,response)
+ }.should raise_error(RPXNow::ServiceUnavailableError)
+ end
+
+ it "raises when service is down" do
+ response = stub(:code=>'400',:body=>%Q({"stat":"err"}))
+ lambda{
+ RPXNow.send(:parse_response,response)
+ }.should raise_error(RPXNow::ServiceUnavailableError)
end
end
describe :mappings do
it "parses JSON response to unmap data" do
- RPXNow.expects(:post).returns %Q({"stat":"ok", "identifiers": ["http://test.myopenid.com/"]})
+ RPXNow.expects(:post).returns mock(:code=>'200',:body=>%Q({"stat":"ok", "identifiers": ["http://test.myopenid.com/"]}))
RPXNow.mappings(1, "x").should == ["http://test.myopenid.com/"]
end
end
describe :map do
it "adds a mapping" do
- RPXNow.expects(:post).returns %Q({"stat":"ok"})
+ RPXNow.expects(:post).returns mock(:code=>'200',:body=>%Q({"stat":"ok"}))
RPXNow.map('http://test.myopenid.com',1, API_KEY)
end
end
describe :unmap do
it "unmaps a indentifier" do
- RPXNow.expects(:post).returns %Q({"stat":"ok"})
+ RPXNow.expects(:post).returns mock(:code=>'200',:body=>%Q({"stat":"ok"}))
RPXNow.unmap('http://test.myopenid.com', 1, "x")
end
it "can be called with a specific version" do
- RPXNow.expects(:secure_json_post).with{|a,b|a == "https://rpxnow.com/api/v300/unmap"}
+ RPXNow.expects(:secure_json_post).with{|a,b|a == "/api/v300/unmap"}
RPXNow.unmap('http://test.myopenid.com', 1, :api_key=>'xxx', :api_version=>300)
end
end
@@ -192,15 +230,4 @@ def fake_response
RPXNow.mappings(1,API_KEY).should == [@k1]
end
end
-
- describe :to_query do
- it "should not depend on active support" do
- RPXNow.send('to_query', {:one => " abc"}).should == "one= abc"
- end
-
- it "should use ActiveSupport core extensions" do
- require 'activesupport'
- RPXNow.send('to_query', {:one => " abc"}).should == "one=+abc"
- end
- end
-end
+end

0 comments on commit 80c77e4

Please sign in to comment.