devfu-rack-openid-proxy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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