devfu-rack-openid-proxy 0.1.0

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/README.markdown ADDED
@@ -0,0 +1,21 @@
1
+ = OpenID::Proxy
2
+
3
+ This is an OpenID Proxy application.
4
+
5
+ Sometimes applications run in an environment where it would be impossible
6
+ or very difficult to use an OpenID consumer.
7
+
8
+ This proxy is basically a very, very simple web service for making OpenID requests,
9
+ without having to have a full-blown local OpenID consumer.
10
+
11
+ This can be used as a Rack application or as a Rack middleware (to add a proxy to your local application).
12
+
13
+ This uses Rack::OpenID (which used ruby-openid) to do the heavy lifting.
14
+
15
+ == Installation
16
+
17
+ ... coming soon ...
18
+
19
+ == Usage
20
+
21
+ ... coming soon ...
data/Rakefile ADDED
@@ -0,0 +1,68 @@
1
+ require 'rake'
2
+ require 'rubygems'
3
+ require 'rake/rdoctask'
4
+ require 'spec/rake/spectask'
5
+
6
+ puts "\nGem: rack-openid-proxy\n\n"
7
+
8
+ begin
9
+ require 'jeweler'
10
+ Jeweler::Tasks.new do |s|
11
+ s.name = 'rack-openid-proxy'
12
+ s.summary = 'A Rack app/middleware to act as a proxy for OpenID authentication'
13
+ s.email = 'remi@remitaylor.com'
14
+ s.homepage = 'http://github.com/devfu/rack-openid-proxy'
15
+ s.description = 'A Rack app/middleware to act as a proxy for OpenID authentication'
16
+ s.authors = %w( remi )
17
+ s.files = FileList['[A-Z]*', '{lib,spec,bin,examples}/**/*']
18
+ # s.add_dependency 'person-gemname'
19
+ # s.executables << 'script'
20
+ # s.rubyforge_project = 'gemname'
21
+ # s.extra_rdoc_files = %w( README.rdoc )
22
+ end
23
+ rescue LoadError
24
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
25
+ end
26
+
27
+ Spec::Rake::SpecTask.new do |t|
28
+ t.spec_files = FileList['spec/**/*_spec.rb']
29
+ end
30
+
31
+ desc "Run all examples with RCov"
32
+ Spec::Rake::SpecTask.new('rcov') do |t|
33
+ t.spec_files = FileList['spec/**/*_spec.rb']
34
+ t.rcov = true
35
+ end
36
+
37
+ # require 'hanna'
38
+ # require 'darkfish-rdoc'
39
+
40
+ Rake::RDocTask.new do |rdoc|
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = 'rack-openid-proxy'
43
+ rdoc.options << '--line-numbers' << '--inline-source'
44
+ # rdoc.options += ["--template=#{`allison --path`}"] # sudo gem install allison
45
+ # rdoc.options += %w( -f darkfish ) # sudo gem install darkfish-rdoc
46
+ # rdoc.options += %w( -T hanna ) # sudo gem install mislav-hanna
47
+ rdoc.options += %w( -m README.rdoc ) # the initial page displayed
48
+ rdoc.rdoc_files.include('README.rdoc')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
51
+
52
+ desc 'Confirm that gemspec is $SAFE'
53
+ task :safe do
54
+ require 'yaml'
55
+ require 'rubygems/specification'
56
+ data = File.read('rack-openid-proxy.gemspec')
57
+ spec = nil
58
+ if data !~ %r{!ruby/object:Gem::Specification}
59
+ Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
60
+ else
61
+ spec = YAML.load(data)
62
+ end
63
+ spec.validate
64
+ puts spec
65
+ puts "OK"
66
+ end
67
+
68
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,153 @@
1
+ %w( rubygems rack rack/openid digest/sha2 uri ).each {|lib| require lib }
2
+
3
+ class Object
4
+ def to_hash
5
+ YAML.load to_yaml.gsub(/ !ruby\/object.*/, '')
6
+ end
7
+ end
8
+
9
+ module Rack #:nodoc:
10
+
11
+ class OpenID #:nodoc:
12
+
13
+ # A simple OpenID Proxy Rack application / middleware
14
+ #
15
+ # == Notes
16
+ #
17
+ # * This *requires* that a Rack::Session middleware be enabled
18
+ # * This *requires* that the Rack::OpenID middleware be enabled (for now)
19
+ #
20
+ class Proxy
21
+
22
+ # this is stored in the Rack env (it gets set by Rack::OpenID)
23
+ OPENID_RESPONSE = 'rack.openid.response'
24
+
25
+ # this is stored in the session
26
+ RETURN_PATH = 'rack-openid-proxy.return_path'
27
+
28
+ class << self
29
+ attr_accessor :formatters
30
+ end
31
+
32
+ @formatters = {
33
+ :yml => lambda {|o| o.to_yaml },
34
+ :json => lambda {|o| require 'json'; o.to_hash.to_json },
35
+ :xml => lambda {|o| require 'yaxml'; YAXML::Yaxml.new(o.to_hash.to_yaml, :root_name => 'openid').to_yaxml }
36
+ }
37
+
38
+ DEFAULT_OPTIONS = {
39
+ :path => '/openid',
40
+ :token_store => {},
41
+ :force_ssl => false,
42
+ :delete_token_on_request => true
43
+ }
44
+
45
+ # The path that, when requested, does OpenID authentication
46
+ #
47
+ # * When used as a middleware, any other paths will call the inner Rack app
48
+ # * When used as a standalone all, any other paths will return a 404
49
+ #
50
+ attr_accessor :path
51
+
52
+ # If set to true, any OpenID-related requests that come in to
53
+ # this app as http:// will be redirected to https://
54
+ attr_accessor :force_ssl
55
+
56
+ # If set to true, tokens and their responses will be deleted after the
57
+ # first time they are requested
58
+ attr_accessor :delete_token_on_request
59
+
60
+ attr_accessor :token_store
61
+
62
+ def initialize *args
63
+ @app = args.shift if args.first.respond_to? :call
64
+ options = args.first || {}
65
+
66
+ DEFAULT_OPTIONS.each {|name, value| send "#{name}=", value }
67
+ options.each {|name, value| send "#{name}=", value } if options
68
+
69
+ raise_validation_exception unless valid?
70
+ end
71
+
72
+ def call env
73
+ request = Rack::Request.new env
74
+
75
+ if request.path_info =~ /^#{ path }\.?(.*)/
76
+ format = $1
77
+
78
+ if response = env[OPENID_RESPONSE]
79
+ token = "#{ Time.now.to_i }-#{ Digest::SHA2.hexdigest(response.to_yaml) }"
80
+ set token, response
81
+ uri = URI.parse env['rack.session'][RETURN_PATH]
82
+ uri.query = (uri.query.nil?) ? "token=#{token}" : "#{uri.query}&token=#{URI.escape(token)}"
83
+ [ 302, {'Location' => uri.to_s }, [] ]
84
+
85
+ elsif token = request.params['token']
86
+ return [ 302, {'Location' => request.url.to_s.sub('http','https')}, []
87
+ ] if force_ssl && request.scheme != 'https' && env['HTTP_X_FORWARDED_PROTO'] != 'https'
88
+ response = get token
89
+ if response
90
+ render response, format
91
+ else
92
+ [ 200, {'Content-Type' => 'text/html'}, ["Token not found"] ]
93
+ end
94
+
95
+ elsif request.params['url']
96
+ env['rack.session'][RETURN_PATH] = request.referer
97
+ return [ 302, {'Location' => request.url.to_s.sub('http','https')}, []
98
+ ] if force_ssl && request.scheme != 'https' && env['HTTP_X_FORWARDED_PROTO'] != 'https'
99
+ [ 401, {'WWW-Authenticate' => "OpenID identifier=\"#{ request.params['url'] }\""}, ["Redirecting to OpenID login ..."] ]
100
+
101
+ else
102
+ return [ 302, {'Location' => request.url.to_s.sub('http','https')}, []
103
+ ] if force_ssl && request.scheme != 'https' && env['HTTP_X_FORWARDED_PROTO'] != 'https'
104
+ [ 200, {'Content-Type' => 'text/html'}, ["Unknown action. Did you forget to set ?url= or ?token= ?"] ]
105
+ end
106
+
107
+ elsif @app
108
+ @app.call env
109
+
110
+ else
111
+ [ 404, {'Content-Type' => 'text/html'}, ["Page not found"] ]
112
+ end
113
+ end
114
+
115
+ def get token
116
+ response = token_store[token]
117
+ token_store.delete token if delete_token_on_request
118
+ response
119
+ end
120
+
121
+ def set token, response
122
+ token_store[token] = response
123
+ end
124
+
125
+ protected
126
+
127
+ def formatters= hash_of_formatters
128
+ self.class.formatters.merge! hash_of_formatters
129
+ end
130
+
131
+ def render response, format = :json
132
+ format = :json if format.to_s.strip.empty?
133
+ raise "Invalid format: #{ format.inspect }" unless self.class.formatters.keys.include? format.to_sym
134
+ [ 200, {'Content-Type' => 'text/plain'}, [ self.class.formatters[format.to_sym].call(response) ] ] # all text/plain for now
135
+ end
136
+
137
+ # VALIDATIONS
138
+
139
+ def valid?
140
+ @errors = []
141
+ # @errors << ":foo option is required" unless path
142
+ @errors.empty?
143
+ end
144
+
145
+ def raise_validation_exception
146
+ raise @errors.join(', ')
147
+ end
148
+
149
+ end
150
+
151
+ end
152
+
153
+ end
@@ -0,0 +1 @@
1
+ require 'rack-openid-proxy'
@@ -0,0 +1,162 @@
1
+ require File.dirname(__FILE__) + '/../lib/rack-openid-proxy'
2
+ require 'spec'
3
+ require 'rackbox'
4
+ require 'rack/contrib'
5
+
6
+ # NOTE these actually hit OpenID servers! (yikes!)
7
+
8
+ # Rack::OpenID::Proxy has 3 different states that it can be in in ... these let us reproduce those
9
+
10
+ def openid_request options = {}
11
+ referer = options.delete(:referer) || 'http://www.google.com'
12
+
13
+ Rack::Session::Cookie.new(Rack::Config.new(Rack::OpenID.new( Rack::OpenID::Proxy.new(options) )){ |env|
14
+ env['rack.session'][Rack::OpenID::Proxy::RETURN_PATH] = referer
15
+ })
16
+ end
17
+
18
+ def openid_response options = {}
19
+ referer = options.delete(:referer) || 'http://www.google.com'
20
+ response = options.delete(:response) || { :OpenID => %w( response object ) }
21
+
22
+ Rack::Session::Cookie.new(Rack::Config.new(Rack::OpenID.new( Rack::OpenID::Proxy.new(options) )){ |env|
23
+ env[Rack::OpenID::Proxy::OPENID_RESPONSE] = response
24
+ env['rack.session'][Rack::OpenID::Proxy::RETURN_PATH] = referer
25
+ })
26
+ end
27
+
28
+ def token_request options = {}
29
+ token = options.delete(:token) || '1234567890-qwertyuiop'
30
+ response = options.delete(:response) || { :OpenID => %w( response object ) }
31
+
32
+ app = Rack::OpenID::Proxy.new({ :delete_token_on_request => false }.merge(options))
33
+ app.set token, response
34
+
35
+ # Rack::Session::Cookie.new(Rack::Config.new(Rack::OpenID.new( app )){ |env| })
36
+ Rack::Session::Cookie.new(Rack::OpenID.new( app ))
37
+ end
38
+
39
+ def get_token url_or_response
40
+ if url_or_response.respond_to? :headers
41
+ get_token url_or_response.headers['Location']
42
+ else
43
+ url_or_response.match(/token=(\d{10}-\w{64})/)[1]
44
+ end
45
+ end
46
+
47
+ Spec::Matchers.define :start_with do |string_should_start_with|
48
+ match do |actual_string|
49
+ actual_string.start_with? string_should_start_with
50
+ end
51
+ end
52
+
53
+ describe Rack::OpenID::Proxy do
54
+
55
+ it 'should work as a standalone Rack app' do
56
+ response = RackBox.request openid_request, '/openid?url=remitaylor.wordpress.com'
57
+ response.status.should == 303
58
+ response.headers['Location'].should start_with('http://remitaylor.wordpress.com/?openidserver')
59
+ end
60
+
61
+ it 'should 404 if path other than /openid is hit (standalone app)' do
62
+ response = RackBox.request openid_request, '/'
63
+ response.status.should == 404
64
+ end
65
+
66
+ it 'should work as a Rack middleware'
67
+
68
+ it 'should persist referer across session'
69
+
70
+ it 'should redirect back to the original referer (with token) after OpenID auth is complete' do
71
+ response = RackBox.request openid_response(:referer => 'http://remi.org'), '/openid'
72
+ response.status.should == 302
73
+ response.headers['Location'].should start_with('http://remi.org?token=')
74
+ end
75
+
76
+ it 'should be able to override where we get redirected to via ?redirect_to= (override referer)'
77
+
78
+ it 'should be able to customize where/how data (tokens) is persisted [data store]'
79
+
80
+ it 'should be able to customize where/how data (tokens) is persisted [get/set]'
81
+
82
+ it 'should return response when token provided' do
83
+ response = RackBox.request token_request(:token => 'abc', :response => 'Hello'), '/openid?token=abc'
84
+ response.status.should == 200
85
+ response.body.should == '"Hello"' # should default to JSON
86
+ end
87
+
88
+ it 'should be able to return response in various formats' do
89
+ app = token_request(:token => 'abc', :response => { :hello => %w( there people ) })
90
+
91
+ response = RackBox.request app, '/openid.json?token=abc'
92
+ response.status.should == 200
93
+ response.body.should == '{"hello":["there","people"]}'
94
+
95
+ response = RackBox.request app, '/openid.yml?token=abc'
96
+ response.status.should == 200
97
+ response.body.should == ({ :hello => %w( there people ) }).to_yaml
98
+
99
+ response = RackBox.request app, '/openid.xml?token=abc'
100
+ response.status.should == 200
101
+ response.body.should == "<openid xmlns:yaml='http://yaml.org/xml'><hello><_>there</_><_>people</_></hello></openid>" # gross but works
102
+ end
103
+
104
+ it 'should return useful code and message if token is not found' do
105
+ response = RackBox.request token_request(:token => 'abc', :response => 'Hello'), '/openid?token=no-exist'
106
+ response.status.should == 200
107
+ response.body.downcase.should include('not found')
108
+ end
109
+
110
+ it 'should allow you to manage formatters' do
111
+ app = token_request(:token => 'abc', :response => { :hello => %w( there people ) },
112
+ :formatters => { :json => lambda {|o| require 'json'; o.to_json.upcase } })
113
+
114
+ response = RackBox.request app, '/openid.json?token=abc'
115
+ response.status.should == 200
116
+ response.body.should == '{"HELLO":["THERE","PEOPLE"]}'
117
+
118
+ app = token_request(:token => 'abc', :response => { :hello => %w( there people ) },
119
+ :formatters => { :test => lambda {|o| 'hello from custom formatter' } })
120
+ response = RackBox.request app, '/openid.test?token=abc'
121
+ response.status.should == 200
122
+ response.body.should == 'hello from custom formatter'
123
+ end
124
+
125
+ it 'should allow you to customize the path (which does the OpenID request)' do
126
+ app = openid_response(:referer => 'http://remi.org', :path => '/do-openid')
127
+
128
+ response = RackBox.request app, '/openid'
129
+ response.status.should == 404
130
+
131
+ response = RackBox.request app, '/do-openid'
132
+ response.status.should == 302
133
+ response.headers['Location'].should start_with('http://remi.org?token=')
134
+ end
135
+
136
+ it 'should allow you to white|black list referers'
137
+
138
+ it 'should allow you to expire tokens (based on time since creation)'
139
+
140
+ it 'should allow you to expire tokens (based on number of times accessed)' do
141
+ app = token_request(:token => 'xyz', :response => "hi", :delete_token_on_request => true)
142
+ RackBox.request(app, '/openid?token=xyz').body.downcase.should == '"hi"'
143
+ RackBox.request(app, '/openid?token=xyz').body.downcase.should include("not found")
144
+ end
145
+
146
+ it 'should allow you to specify that all requests must be via SSL' do
147
+ response = RackBox.request openid_request(:referer => 'http://remi.org', :force_ssl => true), '/openid'
148
+ response.status.should == 302
149
+ response.headers['Location'].should start_with("https://")
150
+ end
151
+
152
+ it 'should return a useful error message if no url' do
153
+ response = RackBox.request openid_request(:referer => 'http://remi.org'), '/openid'
154
+ response.status.should == 200
155
+ response.body.downcase.should include('url')
156
+ end
157
+
158
+ it 'should return a useful error message if no session'
159
+
160
+ it 'should return a useful error message if no Rack::OpenID'
161
+
162
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: devfu-rack-openid-proxy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - remi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-30 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A Rack app/middleware to act as a proxy for OpenID authentication
17
+ email: remi@remitaylor.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.markdown
24
+ files:
25
+ - README.markdown
26
+ - Rakefile
27
+ - VERSION
28
+ - lib/rack-openid-proxy.rb
29
+ - lib/rack/openid/proxy.rb
30
+ - spec/rack-openid-proxy_spec.rb
31
+ has_rdoc: false
32
+ homepage: http://github.com/devfu/rack-openid-proxy
33
+ post_install_message:
34
+ rdoc_options:
35
+ - --charset=UTF-8
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: "0"
43
+ version:
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ requirements: []
51
+
52
+ rubyforge_project:
53
+ rubygems_version: 1.2.0
54
+ signing_key:
55
+ specification_version: 3
56
+ summary: A Rack app/middleware to act as a proxy for OpenID authentication
57
+ test_files:
58
+ - spec/rack-openid-proxy_spec.rb