rack_my_openid 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3@rails_openid_server
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rack_my_openid.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'rack-test', :git => 'git://github.com/brynary/rack-test.git'
8
+ end
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # rack_my_openid - a one-user OpenID provider solution for rack
2
+
3
+ So you have a Rack/Sinatra/Rails-powered blog and you want to make it an OpenID?
4
+
5
+ Now you can do it in under 5 minutes.
6
+
7
+ ## Operation details
8
+
9
+ Rack_my_openid is a simple, single-user OpenID provider inspired by (now deprecated) [phpMyId](http://siege.org/phpmyid.php). It uses
10
+
11
+ * [ruby-openid](https://github.com/openid/ruby-openid) for the protocol implementation;
12
+ * simple Yaml files for storing configuration;
13
+ * in-memory storage for authentication data;
14
+ * HTTP Digest authentication for security;
15
+ * Sinatra and Rack as the server backend.
16
+
17
+ It's designed to be drop-in compatible with any Rails application, since implementing OpenID is a confusing exercise even with ruby-openid. I extracted it from my own site/blog and am continuing to use it there.
18
+
19
+ It's fully covered by RSpec tests.
20
+
21
+ See the [OpenID specs](http://openid.net/specs/openid-authentication-2_0.html) if you really want to understand how the whole thing works.
22
+
23
+ ## Installation - Rails 3
24
+
25
+ * Add the `rack_my_openid` gem to your Gemfile
26
+ * Add this to your routes:
27
+
28
+ openid_provider = RackMyOpenid::Provider.new(YAML.load_file('config/rack_my_openid.yml'))
29
+ match '/openid' => openid_provider
30
+ match '/openid/*whatever' => openid_provider
31
+
32
+ The `/openid` path can't be changed, as of this release.
33
+
34
+ * Create a `config/rack_my_openid.yml` file (see below)
35
+ * Restart your Rails app and you're good to go.
36
+ * If you make any changes to the config you'll have to restart the app to pick them up.
37
+
38
+ ## Installation - Standalone
39
+
40
+ This assumes that the OpenID is the root path.
41
+
42
+ * Install the `rack_my_openid` gem.
43
+ * Create a `config.ru` in your desired path with these contents:
44
+
45
+ require 'rack_my_openid'
46
+ run RackMyOpenid::Provider.new(YAML.load_file('rack_my_openid.yml'))
47
+
48
+ * Create a `rack_my_openid.yml` file (see below) in the same path
49
+ * Create empty `/public` and `/tmp` directories in the same path
50
+ * Deploy with [Passenger](http://www.modrails.com/documentation/Users%20guide%20Nginx.html#deploying_a_rack_app), [Rackup](https://github.com/rack/rack/wiki/(tutorial)-rackup-howto) or whatever Rack handler you fancy.
51
+
52
+ ## `rack_my_openid.yml`
53
+
54
+ This is a simple flat Yaml file. The keys are symbols (as of this release).
55
+
56
+ * `:credentials` - run `md5 -s 'yourusername:rack_my_openid:yourpassword'` (or replace rack_my_openid with your realm name if you changed it);
57
+ * `:openid` - the actual OpenID identifier that you want to provide;
58
+ * `:realm` - the realm for HTTP Digest auth. The default is `"rack_my_openid"`, why would you change it?
59
+ * `:endpoint_url` - the URL of the OpenID endpoint (the one that's '/openid'). You shouldn't explicitly declare it
60
+
61
+ ## TODO
62
+
63
+ * Support stores other than memory store
64
+ * Support SReg data provision
65
+
66
+ ~ ~ ~
67
+
68
+ (c) Leonid Shevtsov http://leonid.shevtsov.me
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,89 @@
1
+ require 'openid/server'
2
+ require 'openid/store/memory'
3
+
4
+ module RackMyOpenid
5
+ class Handler
6
+ class BadRequest < RuntimeError; end
7
+ class UntrustedRealm < RuntimeError
8
+ attr_reader :realm
9
+ def initialize(realm); @realm = realm; end
10
+ def message; "OpenID realm #{@realm} not trusted by user."; end
11
+ end
12
+
13
+ def initialize(options)
14
+ @options = options
15
+ end
16
+
17
+ def handle(params, session)
18
+ if request = openid_server.decode_request(params)
19
+ return openid_server.encode_response handle_openid_request(request, session)
20
+ else
21
+ raise BadRequest
22
+ end
23
+ end
24
+
25
+ def cancel(params)
26
+ if request = openid_server.decode_request(params)
27
+ return openid_server.encode_response cancel_check_id_request(request)
28
+ else
29
+ raise BadRequest
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def openid_server
36
+ @openid_server ||= OpenID::Server::Server.new(@options[:endpoint_url], openid_store)
37
+ end
38
+
39
+ def openid_store
40
+ @openid_store ||= OpenID::Store::Memory
41
+ end
42
+
43
+ # Handle a valid OpenID request
44
+ #
45
+ # Requests are handled by the standard openid server,
46
+ # except the main one - which validates your OpenID.
47
+ # So we only have to handle a check_id request
48
+ def handle_openid_request(request, session)
49
+ case request
50
+ when OpenID::Server::CheckIDRequest
51
+ return handle_check_id_request(request, session)
52
+ else
53
+ return openid_server.handle_request(request)
54
+ end
55
+ end
56
+
57
+ def handle_check_id_request(request, session)
58
+ if request_provided_invalid_openid?(request)
59
+ return request.answer(false)
60
+ elsif trusted_realm?(request.realm, session)
61
+ return request.answer(true, nil, @options[:openid])
62
+ else
63
+ raise UntrustedRealm.new(request.realm)
64
+ end
65
+ end
66
+
67
+ def cancel_check_id_request(request)
68
+ request.answer(false)
69
+ end
70
+
71
+ # Returns true if the request's claimed id is different from ours
72
+ #
73
+ # OK, this is an 'inverted truth' method, but it
74
+ # seems appropriate since the opposite assertion isn't
75
+ # 'inverted' (request can either not pass an id or
76
+ # pass a valid id)
77
+ def request_provided_invalid_openid?(request)
78
+ !request.id_select && (request.claimed_id != @options[:openid])
79
+ end
80
+
81
+ def trusted_realm?(realm, session)
82
+ trusted_realms(session).include? realm
83
+ end
84
+
85
+ def trusted_realms(session={})
86
+ @options.fetch(:trusted_realms, []) + session.fetch(:trusted_realms, [])
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,91 @@
1
+ require 'cgi'
2
+ require 'sinatra/base'
3
+ require 'rack_my_openid/handler'
4
+
5
+ module RackMyOpenid
6
+ class Provider < Sinatra::Base
7
+ set :root, File.dirname(__FILE__)
8
+
9
+ use Rack::Session::Pool
10
+
11
+ # authorization
12
+ def authorize!
13
+ response = @auth.call(request.env)
14
+ unless response===true
15
+ throw(:halt, response)
16
+ end
17
+ end
18
+
19
+ # Options can contain
20
+ #
21
+ # * :credentials (required) - `md5 -s 'username:realm:password'`
22
+ # * :openid (required) - the OpenID you want to authorize
23
+ # * :realm (optional, the default is "rack_my_openid") - an arbitrary resource name for HTTP Authentication
24
+ def initialize(options = {})
25
+ super()
26
+ @options = default_options.merge options
27
+ @auth = Rack::Auth::Digest::MD5.new(lambda{|e| true}, @options[:realm]) do
28
+ @options[:credentials]
29
+ end
30
+ @auth.opaque = $$.to_s
31
+ @auth.passwords_hashed = true
32
+ end
33
+
34
+ get '/openid' do
35
+ if params.empty?
36
+ erb :endpoint
37
+ else
38
+ authorize!
39
+ @options[:endpoint_url] ||= "#{request.scheme}://#{request.host_with_port}#{request.fullpath}"
40
+ begin
41
+ render_openid_response RackMyOpenid::Handler.new(@options).handle(params, session)
42
+ rescue RackMyOpenid::Handler::BadRequest
43
+ status 400
44
+ body 'Bad Request'
45
+ rescue RackMyOpenid::Handler::UntrustedRealm => e
46
+ session[:openid_request_params] = params
47
+ session[:realm] = e.realm
48
+ redirect to('/openid/decide'), 302
49
+ end
50
+ end
51
+ end
52
+
53
+ get '/openid/decide' do
54
+ authorize!
55
+ if @realm = session[:realm]
56
+ erb :decide
57
+ else
58
+ redirect to('/openid'), 302
59
+ end
60
+ end
61
+
62
+ post '/openid/decide' do
63
+ authorize!
64
+ @realm = session.delete(:realm)
65
+ begin
66
+ if params[:yes]
67
+ session[:trusted_realms] ||= []
68
+ session[:trusted_realms] << @realm
69
+ response = RackMyOpenid::Handler.new(@options).handle(session[:openid_request_params], session)
70
+ else
71
+ response RackMyOpenid::Handler.new(@options).cancel(session[:openid_request_params])
72
+ end
73
+ render_openid_response response
74
+ rescue RackMyOpenid::Handler::BadRequest
75
+ erb :expired
76
+ end
77
+ end
78
+
79
+ def render_openid_response(response)
80
+ status response.code
81
+ headers response.headers
82
+ body response.body
83
+ end
84
+
85
+ def default_options
86
+ {
87
+ :realm => 'rack_my_openid'
88
+ }
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,3 @@
1
+ module RackMyOpenid
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,7 @@
1
+ <form method="POST" action="<%=request.path_info%>">
2
+ <p>
3
+ Do you wish to trust <strong><%=@realm %></strong> with your identity?
4
+ </p>
5
+ <input type="submit" value="Yes" name="yes">
6
+ <input type="submit" value="No" name="no">
7
+ </form>
@@ -0,0 +1,3 @@
1
+ <p>
2
+ This is an OpenID endpoint.
3
+ </p>
@@ -0,0 +1,3 @@
1
+ <p>
2
+ Your session has expired. Please try authenticating again.
3
+ </p>
@@ -0,0 +1,9 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>rack_my_openid OpenID endpoint</title>
5
+ </head>
6
+ <body>
7
+ <%= yield %>
8
+ </body>
9
+ </html>
@@ -0,0 +1,6 @@
1
+ require 'rack_my_openid/version'
2
+ require 'rack_my_openid/provider'
3
+
4
+ module RackMyOpenid
5
+
6
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rack_my_openid/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rack_my_openid"
7
+ s.version = RackMyOpenid::VERSION
8
+ s.authors = ["Leonid Shevtsov"]
9
+ s.email = ["leonid@shevtsov.me"]
10
+ s.homepage = "https://github.com/leonid-shevtsov/rack_my_openid"
11
+ s.summary = %q{Single-user OpenID provider implemented as a Rack (Sinatra) application.}
12
+ s.description = %q{Would be useful to enable OpenID authorisation with your Ruby/Rails-based blog, personal website or whatever.}
13
+
14
+ s.rubyforge_project = "rack_my_openid"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_runtime_dependency 'sinatra'
22
+ s.add_runtime_dependency 'ruby-openid'
23
+
24
+ s.add_development_dependency 'rspec'
25
+ s.add_development_dependency 'capybara'
26
+ end
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for 'a valid request' do
4
+ context 'when realm is trusted via options' do
5
+ let(:options_with_realm) { options.merge({:trusted_realms => [request.realm]}) }
6
+ it 'should answer true' do
7
+ described_class.new(options_with_realm).handle(params, session).should == 'answered true'
8
+ end
9
+ end
10
+
11
+ context 'when realm is trusted via session' do
12
+ let(:session) { {:trusted_realms => [request.realm] } }
13
+ it 'should answer true' do
14
+ described_class.new(options).handle(params, session).should == 'answered true'
15
+ end
16
+ end
17
+
18
+ context 'when realm is not trusted' do
19
+ it 'should raise an error' do
20
+ expect {
21
+ described_class.new(options).handle(params, session)
22
+ }.to raise_error(RackMyOpenid::Handler::UntrustedRealm)
23
+ end
24
+ end
25
+ end
26
+
27
+ describe RackMyOpenid::Handler do
28
+ let(:options) { {:openid => 'http://myopenid.myopenid/' } }
29
+ let(:params) { {} }
30
+ let(:session) { {} }
31
+ let(:server) {
32
+ server = double('OpenID::Server::Server')
33
+ server.should_receive(:decode_request).with(params) { request }
34
+ server
35
+ }
36
+ before(:each) do
37
+ OpenID::Server::Server.stub(:new) { server }
38
+ end
39
+
40
+ context 'with bad params' do
41
+ let(:params) { {:bad => :params} }
42
+ let(:request) { false }
43
+
44
+ it 'should raise error' do
45
+ expect {
46
+ described_class.new(options).handle(params, session)
47
+ }.to raise_error(RackMyOpenid::Handler::BadRequest)
48
+ end
49
+ end
50
+
51
+ context 'with a non-check_id request' do
52
+ let(:params) { {:mode => 'some_mode' } }
53
+ let(:request) { Object.new }
54
+ let(:response) { double('Response') }
55
+
56
+ it 'should pass the request to the server' do
57
+ server.should_receive(:handle_request).with(request) { response }
58
+ server.should_receive(:encode_response).with(response)
59
+ described_class.new(options).handle(params, session)
60
+ end
61
+ end
62
+
63
+ context 'with a check_id request' do
64
+ let(:params) { {:mode => 'check_id'} }
65
+ let(:request) {
66
+ request = OpenID::Server::CheckIDRequest.new(nil, nil, nil)
67
+ request.stub(:realm) { 'http://my.realm' }
68
+ request.stub(:answer).with(true, nil, options[:openid]) { 'answered true' }
69
+ request.stub(:answer).with(false) { 'answered false' }
70
+ request
71
+ }
72
+ before do
73
+ server.stub(:encode_response) {|params| params }
74
+ end
75
+
76
+ context 'and a valid openid' do
77
+ before do
78
+ request.stub(:id_select) { false }
79
+ request.stub(:claimed_id) { options[:openid] }
80
+ end
81
+
82
+ it_should_behave_like 'a valid request'
83
+ end
84
+
85
+ context 'and an invalid openid' do
86
+ before do
87
+ request.stub(:id_select) { false }
88
+ request.stub(:claimed_id) { 'bad id' }
89
+ end
90
+
91
+ it 'should answer false' do
92
+ described_class.new(options).handle(params, session).should == 'answered false'
93
+ end
94
+ end
95
+
96
+ context 'and no openid' do
97
+ before do
98
+ request.stub(:id_select) { true }
99
+ request.stub(:claimed_id) { nil }
100
+ end
101
+
102
+ it_should_behave_like 'a valid request'
103
+ end
104
+ end
105
+ end
106
+
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ feature 'Authentication' do
4
+ setup_app
5
+ assume_authorization
6
+
7
+ scenario 'Bad request' do
8
+ handler.stub(:handle) { raise RackMyOpenid::Handler::BadRequest }
9
+ visit '/openid?foo=bar'
10
+ page.status_code.should == 400
11
+ end
12
+
13
+ scenario 'Untrusted realm' do
14
+ handler.stub(:handle) { raise RackMyOpenid::Handler::UntrustedRealm.new('http://my.realm') }
15
+ visit '/openid?foo=bar'
16
+ page.current_path.should == '/openid/decide'
17
+ page.should have_selector('form')
18
+ page.should have_content('http://my.realm')
19
+ end
20
+
21
+ scenario 'Happy case' do
22
+ handler.stub(:handle) { OpenID::Server::WebResponse.new(123, {'X-Test' => 'bar'}, 'testbody') }
23
+ visit '/openid?foo=bar'
24
+ page.status_code.should == 123
25
+ page.response_headers['X-Test'].should == 'bar'
26
+ page.source.should == 'testbody'
27
+ end
28
+ end
@@ -0,0 +1,38 @@
1
+ feature 'Decision whether to trust a realm or not' do
2
+ setup_app
3
+ assume_authorization
4
+
5
+ context 'when accessing decision page directly' do
6
+ scenario 'should redirect to the endpoint' do
7
+ visit '/openid/decide'
8
+ page.current_path.should == '/openid'
9
+ end
10
+ end
11
+
12
+ context 'when redirected to the decision page' do
13
+ before do
14
+ handler.stub(:handle) { raise RackMyOpenid::Handler::UntrustedRealm.new('http://my.realm') }
15
+ visit '/openid?foo=bar'
16
+ handler.stub(:handle) { OpenID::Server::WebResponse.new(200, {}, 'ok') }
17
+ end
18
+
19
+ scenario 'Trusting the realm - should allow authentication' do
20
+ handler.should_receive(:handle)
21
+ page.click_button('Yes')
22
+ end
23
+
24
+ scenario 'Not trusting the realm - should cancel authentication' do
25
+ handler.should_receive(:cancel)
26
+ page.click_button('No')
27
+ end
28
+
29
+ scenario 'Preserving realm trust' do
30
+ page.click_button('Yes')
31
+ handler.should_receive(:handle) { |params,session|
32
+ session[:trusted_realms].should == ['http://my.realm']
33
+ }
34
+ visit('/openid?foo=bar')
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ feature 'Human-readable endpoint' do
4
+ setup_app
5
+
6
+ scenario 'Visiting the endpoint' do
7
+ visit '/openid'
8
+ page.should have_content 'This is an OpenID endpoint.'
9
+ end
10
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ feature 'HTTP authorisation' do
4
+ setup_app
5
+
6
+ before do
7
+ handler.stub(:handle) { OpenID::Server::WebResponse.new(200, {}, 'ok') }
8
+ visit '/openid?foo=bar'
9
+ end
10
+
11
+ scenario 'Without authorisation it should propose to authorise' do
12
+ page.status_code.should == 401
13
+ end
14
+
15
+ scenario 'With improper authorisation it should reject the request' do
16
+ page.driver.browser.digest_authorize('vasya', 'pupkin')
17
+ visit '/openid?foo=bar'
18
+ page.status_code.should == 401
19
+ end
20
+
21
+ scenario 'With proper authorisation we should handle the request' do
22
+ page.driver.browser.digest_authorize('correct_login', 'correct_password')
23
+ visit '/openid?foo=bar'
24
+ page.status_code.should == 200
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ require 'rack_my_openid'
2
+ require 'rspec'
3
+ require 'capybara/rspec'
4
+
5
+ module AcceptanceHelper
6
+ def setup_app
7
+ before { Capybara.app = RackMyOpenid::Provider.new({:realm => 'myrealm', :credentials => 'bd1baa373b11c42826d3b15ef77a26d8'}) }
8
+
9
+ let(:handler) { double('RackMyOpenid::Handler') }
10
+ before { RackMyOpenid::Handler.stub(:new).and_return(handler) }
11
+ end
12
+
13
+ def assume_authorization
14
+ before {
15
+ visit '/openid?foo=bar'
16
+ page.driver.browser.digest_authorize('correct_login', 'correct_password')
17
+ visit '/openid?foo=bar'
18
+ }
19
+ end
20
+ end
21
+
22
+ RSpec.configure do |config|
23
+ config.extend AcceptanceHelper, :type => :request
24
+ end
25
+
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack_my_openid
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Leonid Shevtsov
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sinatra
16
+ requirement: &70135767507380 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70135767507380
25
+ - !ruby/object:Gem::Dependency
26
+ name: ruby-openid
27
+ requirement: &70135767506760 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70135767506760
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &70135767506180 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70135767506180
47
+ - !ruby/object:Gem::Dependency
48
+ name: capybara
49
+ requirement: &70135767503520 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70135767503520
58
+ description: Would be useful to enable OpenID authorisation with your Ruby/Rails-based
59
+ blog, personal website or whatever.
60
+ email:
61
+ - leonid@shevtsov.me
62
+ executables: []
63
+ extensions: []
64
+ extra_rdoc_files: []
65
+ files:
66
+ - .gitignore
67
+ - .rvmrc
68
+ - Gemfile
69
+ - README.md
70
+ - Rakefile
71
+ - lib/rack_my_openid.rb
72
+ - lib/rack_my_openid/handler.rb
73
+ - lib/rack_my_openid/provider.rb
74
+ - lib/rack_my_openid/version.rb
75
+ - lib/rack_my_openid/views/decide.erb
76
+ - lib/rack_my_openid/views/endpoint.erb
77
+ - lib/rack_my_openid/views/expired.erb
78
+ - lib/rack_my_openid/views/layout.erb
79
+ - rack_my_openid.gemspec
80
+ - spec/handler_spec.rb
81
+ - spec/provider/authentication_spec.rb
82
+ - spec/provider/decision_spec.rb
83
+ - spec/provider/endpoint_spec.rb
84
+ - spec/provider/http_auth_spec.rb
85
+ - spec/spec_helper.rb
86
+ homepage: https://github.com/leonid-shevtsov/rack_my_openid
87
+ licenses: []
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project: rack_my_openid
106
+ rubygems_version: 1.8.11
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: Single-user OpenID provider implemented as a Rack (Sinatra) application.
110
+ test_files:
111
+ - spec/handler_spec.rb
112
+ - spec/provider/authentication_spec.rb
113
+ - spec/provider/decision_spec.rb
114
+ - spec/provider/endpoint_spec.rb
115
+ - spec/provider/http_auth_spec.rb
116
+ - spec/spec_helper.rb