hancock-client 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -3,18 +3,11 @@ hancock-client
3
3
 
4
4
  A gem that integrates [sinatra][sinatra] applications into the Hancock SSO
5
5
  environment. It also doubles as rack middleware that can be used in
6
- rails(>= 2.3.2) and merb(>= 1.0)
6
+ rails(>= 2.3.2) and sinatra applications.
7
7
 
8
8
  Dependencies
9
9
  ============
10
- % gem sources
11
- *** CURRENT SOURCES ***
12
-
13
- http://gems.rubyforge.org
14
- % sudo gem sources -a http://gems.github.com
15
- http://gems.github.com added to sources
16
-
17
- % sudo gem install sinatra hancock haml
10
+ % sudo geminstaller
18
11
 
19
12
  testing
20
13
  =======
@@ -30,22 +23,24 @@ The goal is to make it simple to write sso enabled apps.
30
23
  require 'hancock-client'
31
24
  require 'logger'
32
25
 
33
- # OpenID writes to STDERR by default. This is closed under passenger so
34
- # to make this reliable you should have openid write failures somewhere.
35
- OpenID::Util.logger = Logger.new(Dir.tmpdir + "/openid.log")
36
-
37
26
  class HancockClientDemo < Sinatra::Default
38
27
  set :views, File.dirname(__FILE__) + '/views'
39
28
  set :public, File.dirname(__FILE__) + '/public'
40
- use Hancock::Client::Default do |sso|
29
+ use Hancock::Client::Middleware do |sso|
41
30
  sso.sso_url = 'http://hancock.atmos.org/sso'
31
+ sso.exclude_paths = %w(/api/)
42
32
  end
43
33
 
44
34
  get '/' do
45
- haml(%Q{%h3= "#{session[:first_name]} #{session[:last_name]} - #{session[:email]}"})
35
+ haml(%Q{%h3= "#{sso_user_full_name} - #{sso_user_email}"})
46
36
  end
47
37
  end
48
38
 
49
39
  run HancockClientDemo
50
40
 
41
+ Feedback
42
+ ========
43
+ * [Google Group][googlegroup]
44
+
51
45
  [sinatra]: http://www.sinatrarb.com
46
+ [googlegroup]: http://groups.google.com/group/hancock-users
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require 'spec/rake/spectask'
6
6
  require 'cucumber/rake/task'
7
7
 
8
8
  GEM = "hancock-client"
9
- GEM_VERSION = "0.0.7"
9
+ GEM_VERSION = "0.0.8"
10
10
  AUTHOR = "Corey Donohoe"
11
11
  EMAIL = ['atmos@atmos.org', 'tim@spork.in']
12
12
  HOMEPAGE = "http://github.com/atmos/hancock-client"
@@ -28,6 +28,8 @@ spec = Gem::Specification.new do |s|
28
28
  s.add_dependency "sinatra", "~>0.9.2"
29
29
  s.add_dependency "ruby-openid", "~>2.1.6"
30
30
  s.add_dependency "haml", "~>2.0.9"
31
+ s.add_dependency "rack-openid", "~>0.2"
32
+
31
33
  s.require_path = 'lib'
32
34
  s.autorequire = GEM
33
35
  s.files = %w(LICENSE README.md Rakefile) + Dir.glob("{lib,spec}/**/*")
@@ -3,25 +3,18 @@ require 'sinatra/base'
3
3
 
4
4
  gem 'ruby-openid', '>=2.1.6'
5
5
  require 'openid'
6
+ require 'openid/store/memory'
7
+
8
+ gem 'rack-openid', '>=0.2'
9
+ require 'rack-openid'
6
10
 
7
11
  gem 'haml', '~>2.0.9'
8
12
  require 'haml'
9
13
 
10
- require File.dirname(__FILE__)+'/sinatra/hancock/sso'
11
-
12
- module Hancock
13
- module Client
14
- class Default < ::Sinatra::Base
15
- enable :sessions
16
- disable :raise_errors
17
- disable :show_exceptions
18
-
19
- set :sso_url, nil
14
+ require 'tmpdir'
20
15
 
21
- def sso_url=(url)
22
- options.sso_url = url
23
- end
24
- register Sinatra::Hancock::SSO
25
- end
26
- end
27
- end
16
+ require File.dirname(__FILE__)+'/hancock-client/helpers/rack'
17
+ require File.dirname(__FILE__)+'/hancock-client/default'
18
+ require File.dirname(__FILE__)+'/hancock-client/sso'
19
+ require File.dirname(__FILE__)+'/hancock-client/middleware'
20
+ require File.dirname(__FILE__)+'/hancock-client/mock_middleware'
@@ -0,0 +1,20 @@
1
+ module Hancock
2
+ module Client
3
+ class DefaultMiddleware < ::Sinatra::Base
4
+ enable :sessions
5
+ enable :raise_errors
6
+ disable :show_exceptions
7
+
8
+ set :sso_url, nil
9
+ set :exclude_paths, nil
10
+
11
+ def sso_url=(url)
12
+ options.sso_url = url
13
+ end
14
+
15
+ def exclude_paths=(paths)
16
+ options.exclude_paths = paths
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,55 @@
1
+ module Hancock
2
+ module Client
3
+ module Helpers
4
+ module Rack
5
+ def sso_logged_in?
6
+ session[:sso] && sso_user_id
7
+ end
8
+
9
+ def sso_login_as(user_id, sreg_params)
10
+ session.delete(:last_oidreq)
11
+ session.delete('OpenID::Consumer::last_requested_endpoint')
12
+ session.delete('OpenID::Consumer::DiscoveredServices::OpenID::Consumer::')
13
+
14
+ session[:sso] ||= { }
15
+ session[:sso][:user_id] = user_id
16
+ sreg_params.each { |key, value| session[:sso][key.to_sym] = value.to_s }
17
+ end
18
+
19
+ def sso_user_id
20
+ session[:sso][:user_id]
21
+ end
22
+
23
+ def sso_user_email
24
+ session[:sso][:email]
25
+ end
26
+
27
+ def sso_user_first_name
28
+ session[:sso][:first_name]
29
+ end
30
+
31
+ def sso_user_last_name
32
+ session[:sso][:last_name]
33
+ end
34
+
35
+ def sso_user_full_name
36
+ "#{session[:sso][:first_name]} #{session[:sso][:last_name]}"
37
+ end
38
+
39
+ def absolute_url(suffix = nil)
40
+ port_part = case request.scheme
41
+ when "http"
42
+ request.port == 80 ? "" : ":#{request.port}"
43
+ when "https"
44
+ request.port == 443 ? "" : ":#{request.port}"
45
+ end
46
+ "#{request.scheme}://#{request.host}#{port_part}#{suffix}"
47
+ end
48
+
49
+ def excluded_path?
50
+ options.exclude_paths && options.exclude_paths.include?(request.path_info)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,7 @@
1
+ module Hancock
2
+ module Client
3
+ class Middleware < DefaultMiddleware
4
+ register ::Hancock::Client::SSO
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ module Hancock
2
+ module Client
3
+ class MockMiddleware < DefaultMiddleware
4
+ helpers Hancock::Client::Helpers::Rack
5
+
6
+ before do
7
+ unless sso_logged_in?
8
+ sso_login_as((0..42).pick, {
9
+ :email => "#{/\w{3,8}/.gen.downcase}@example.com",
10
+ :first_name => Randgen.first_name,
11
+ :last_name => Randgen.first_name
12
+ })
13
+ end
14
+ end
15
+
16
+ register Hancock::Client::SSO
17
+
18
+ end
19
+ end
20
+ end
@@ -1,33 +1,15 @@
1
- require 'sinatra/base'
2
- require File.dirname(__FILE__)+'/../../rack-openid'
3
-
4
- module Sinatra
5
- module Hancock
1
+ module Hancock
2
+ module Client
6
3
  module SSO
7
- module Helpers
8
- def absolute_url(suffix = nil)
9
- port_part = case request.scheme
10
- when "http"
11
- request.port == 80 ? "" : ":#{request.port}"
12
- when "https"
13
- request.port == 443 ? "" : ":#{request.port}"
14
- end
15
- "#{request.scheme}://#{request.host}#{port_part}#{suffix}"
16
- end
17
- end
18
-
19
4
  def self.registered(app)
20
- app.use(Rack::OpenID)
21
- app.helpers Hancock::SSO::Helpers
22
-
23
- app.not_found do
24
- next if session[:user_id]
25
- end
5
+ app.use(Rack::OpenID, OpenID::Store::Filesystem.new("#{Dir.tmpdir}/openid"))
6
+ app.helpers Hancock::Client::Helpers::Rack
26
7
 
27
8
  app.before do
28
9
  next if request.path_info == '/sso/login'
29
10
  next if request.path_info == '/sso/logout'
30
- next if session[:user_id]
11
+ next if excluded_path?
12
+ next if sso_logged_in?
31
13
  throw(:halt, [302, {'Location' => '/sso/login'}, ''])
32
14
  end
33
15
 
@@ -41,13 +23,8 @@ module Sinatra
41
23
  elsif openid = request.env["rack.openid.response"]
42
24
  if openid.status == :success
43
25
  if contact_id = openid.display_identifier.split("/").last
44
- session.delete(:last_oidreq)
45
- session.delete('OpenID::Consumer::last_requested_endpoint')
46
- session.delete('OpenID::Consumer::DiscoveredServices::OpenID::Consumer::')
47
-
48
- session[:user_id] = contact_id
49
- params = openid.message.get_args("http://openid.net/extensions/sreg/1.1")
50
- params.each { |key, value| session[key.to_sym] = value.to_s }
26
+ sreg_params = openid.message.get_args("http://openid.net/extensions/sreg/1.1")
27
+ sso_login_as(contact_id, sreg_params)
51
28
  redirect '/'
52
29
  else
53
30
  raise "No contact could be found for #{openid.display_identifier}"
@@ -1,25 +1,23 @@
1
1
  require File.dirname(__FILE__) + '/spec_helper'
2
2
 
3
- describe Sinatra::Hancock::SSO do
4
-
5
- it "should protect the root url" do
6
- get '/'
7
- last_response.headers['Location'].should eql('/sso/login')
8
- get last_response.headers['Location']
9
- last_response.headers['Location'].should eql('http://localhost:20000/login?return_to=http://example.org/sso/login')
3
+ describe 'Hancock::Client::Default' do
4
+ def app
5
+ @app = Rack::Builder.new do
6
+ use Hancock::Client::Middleware do |sso|
7
+ sso.sso_url = 'http://localhost:20000'
8
+ end
9
+ map '/' do
10
+ run Proc.new {|env| [200, {'Content-Type' => 'text/html', 'Content-Length' => '5'}, ['HELLO']] }
11
+ end
12
+ end
10
13
  end
11
14
 
12
- %w(get post put head delete).each do |verb|
13
- it "should protect the root url from HTTP #{verb}" do
15
+ it "should protect the root url from all HTTP verbs" do
16
+ %w(get post put head delete).each do |verb|
14
17
  send(verb, '/')
15
18
  last_response.headers['Location'].should eql('/sso/login')
16
19
  get last_response.headers['Location']
17
20
  last_response.headers['Location'].should eql('http://localhost:20000/login?return_to=http://example.org/sso/login')
18
21
  end
19
22
  end
20
- it "should greet the user when authenticated" do
21
- pending
22
- get '/'
23
- last_response.should have_selector('p')
24
- end
25
23
  end
@@ -0,0 +1,26 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ class MyMockApp
4
+ def self.call(env)
5
+ request = Rack::Request.new(env)
6
+ [200, {'Content-Type' => 'text/html', 'Content-Length' => '5'}, ["HELLO #{request.session[:sso][:email]}"] ]
7
+ end
8
+ end
9
+
10
+ describe "Hancock::Client::MockMiddleware" do
11
+ def app
12
+ @app = Rack::Builder.new do
13
+ use Hancock::Client::MockMiddleware do |sso|
14
+ sso.sso_url = 'http://localhost:20000'
15
+ end
16
+ map '/' do
17
+ run MyMockApp
18
+ end
19
+ end
20
+ end
21
+ it "should have a valid users logged in" do
22
+ get '/'
23
+ last_response.body.should match(/^HELLO \w{3,12}@example.com$/)
24
+ last_response.status.should eql(200)
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "Hancock::Client::Default.exclude_paths=" do
4
+ def app
5
+ @app = Rack::Builder.new do
6
+ use Hancock::Client::Middleware do |sso|
7
+ sso.sso_url = 'http://localhost:20000'
8
+ sso.exclude_paths = %w(/foo/bar /api/tokenz)
9
+ end
10
+ map '/' do
11
+ run Proc.new {|env| [200, {'Content-Type' => 'text/html', 'Content-Length' => '5'}, ['HELLO']] }
12
+ end
13
+ map '/api/tokenz' do
14
+ run Proc.new {|env| [200, {'Content-Type' => 'text/html', 'Content-Length' => '5'}, ['HELLO']] }
15
+ end
16
+ end
17
+ end
18
+ it "should exclude the specified path" do
19
+ get '/api/tokenz'
20
+ last_response.status.should eql(200)
21
+ end
22
+ it "should exclude the specified path given query parameters" do
23
+ get '/api/tokenz?foo=bar'
24
+ last_response.status.should eql(200)
25
+ end
26
+ it "should exclude the specified path at a nested level" do
27
+ get '/api/tokenz/foo/bar/'
28
+ last_response.status.should eql(302)
29
+ end
30
+ end
@@ -1,30 +1,19 @@
1
- $:.push File.join(File.dirname(__FILE__), '..', 'lib')
2
- require 'rubygems'
3
1
  require 'pp'
2
+ require 'rubygems'
4
3
  gem 'rspec', '~>1.2.0'
5
4
  require 'spec'
6
- gem 'webrat', '~>0.4.2'
5
+ gem 'webrat', '~>0.4.4'
7
6
  require 'webrat'
8
7
  require 'dm-sweatshop'
9
8
 
10
- require 'sinatra/hancock/sso'
11
- gem 'rack-test', '~>0.3.0'
9
+ gem 'rack-test', '~>0.4.0'
12
10
  require 'rack/test'
13
11
 
14
- require 'hancock-client'
12
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'hancock-client')
13
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'hancock-client', 'mock_middleware')
15
14
 
16
15
  Spec::Runner.configure do |config|
17
16
  config.include(Rack::Test::Methods)
18
17
  config.include(Webrat::Methods)
19
18
  config.include(Webrat::Matchers)
20
- def app
21
- @app = Rack::Builder.new do
22
- use Hancock::Client::Default do |sso|
23
- sso.sso_url = 'http://localhost:20000'
24
- end
25
- map '/' do
26
- run Proc.new {|env| [200, {'Content-Type' => 'text/html', 'Content-Length' => '5'}, ['HELLO']] }
27
- end
28
- end
29
- end
30
19
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hancock-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Corey Donohoe
@@ -9,7 +9,7 @@ autorequire: hancock-client
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-06-15 00:00:00 -07:00
12
+ date: 2009-07-04 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -42,6 +42,16 @@ dependencies:
42
42
  - !ruby/object:Gem::Version
43
43
  version: 2.0.9
44
44
  version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: rack-openid
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: "0.2"
54
+ version:
45
55
  description: Hancock SSO rack middleware written in sinatra
46
56
  email:
47
57
  - atmos@atmos.org
@@ -57,13 +67,17 @@ files:
57
67
  - LICENSE
58
68
  - README.md
59
69
  - Rakefile
70
+ - lib/hancock-client
71
+ - lib/hancock-client/default.rb
72
+ - lib/hancock-client/helpers
73
+ - lib/hancock-client/helpers/rack.rb
74
+ - lib/hancock-client/middleware.rb
75
+ - lib/hancock-client/mock_middleware.rb
76
+ - lib/hancock-client/sso.rb
60
77
  - lib/hancock-client.rb
61
- - lib/rack-openid.rb
62
- - lib/sinatra
63
- - lib/sinatra/hancock
64
- - lib/sinatra/hancock/mock.rb
65
- - lib/sinatra/hancock/sso.rb
66
78
  - spec/hancock_sinatra_spec.rb
79
+ - spec/mock_sso_user_spec.rb
80
+ - spec/pattern_exclusion_spec.rb
67
81
  - spec/spec_helper.rb
68
82
  has_rdoc: true
69
83
  homepage: http://github.com/atmos/hancock-client
@@ -1,195 +0,0 @@
1
- require 'openid'
2
- require 'openid/consumer'
3
- require 'openid/extensions/sreg'
4
- require 'openid/store/filesystem'
5
-
6
- module Rack
7
- class OpenID
8
- def self.build_header(params = {})
9
- value = 'OpenID '
10
- value += params.map { |k, v|
11
- if v.is_a?(Array)
12
- "#{k}=\"#{v.join(',')}\""
13
- else
14
- "#{k}=\"#{v}\""
15
- end
16
- }.join(', ')
17
- value
18
- end
19
-
20
- def self.parse_header(str)
21
- params = {}
22
- if str =~ /^OpenID/
23
- str = str.gsub(/^OpenID /, '')
24
- str.split(', ').each { |e|
25
- k, v = e.split('=')
26
- v.gsub!(/^\"/, '').gsub!(/\"$/, "")
27
- v = v.split(',')
28
- params[k] = v.length > 1 ? v : v.first
29
- }
30
- end
31
- params
32
- end
33
-
34
- class TimeoutResponse
35
- include ::OpenID::Consumer::Response
36
- STATUS = :failure
37
- end
38
-
39
- class MissingResponse
40
- include ::OpenID::Consumer::Response
41
- STATUS = :missing
42
- end
43
-
44
- HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS)
45
-
46
- RESPONSE = "rack.openid.response".freeze
47
- AUTHENTICATE_HEADER = "WWW-Authenticate".freeze
48
-
49
-
50
- def initialize(app, store = nil)
51
- @app = app
52
- @store = store || ::OpenID::Store::Filesystem.new("tmp/openid")
53
- freeze
54
- end
55
-
56
- def call(env)
57
- req = Rack::Request.new(env)
58
- if env["REQUEST_METHOD"] == "GET" && req.GET["openid.mode"]
59
- complete_authentication(env)
60
- end
61
-
62
- status, headers, body = @app.call(env)
63
-
64
- if status.to_i == 401 && (qs = headers[AUTHENTICATE_HEADER])
65
- begin_authentication(env, qs)
66
- else
67
- [status, headers, body]
68
- end
69
- end
70
-
71
- private
72
- def begin_authentication(env, qs)
73
- req = Rack::Request.new(env)
74
- params = self.class.parse_header(qs)
75
-
76
- unless session = env["rack.session"]
77
- raise RuntimeError, "Rack::OpenID requires a session"
78
- end
79
-
80
- consumer = ::OpenID::Consumer.new(session, @store)
81
- identifier = params["identifier"]
82
-
83
- begin
84
- oidreq = consumer.begin(identifier)
85
- add_simple_registration_fields(oidreq, params)
86
- url = open_id_redirect_url(req, oidreq, params["trust_root"], params["return_to"], params["method"])
87
- return redirect_to(url)
88
- rescue ::OpenID::OpenIDError, Timeout::Error => e
89
- env[RESPONSE] = MissingResponse.new
90
- return @app.call(env)
91
- end
92
- end
93
-
94
- def complete_authentication(env)
95
- req = Rack::Request.new(env)
96
-
97
- unless session = env["rack.session"]
98
- raise RuntimeError, "Rack::OpenID requires a session"
99
- end
100
-
101
- oidresp = timeout_protection_from_identity_server {
102
- consumer = ::OpenID::Consumer.new(session, @store)
103
- consumer.complete(req.params, req.url)
104
- }
105
-
106
- if oidresp.status == :failure
107
- raise "Open ID Response is a failure: #{oidresp.inspect}"
108
- end
109
-
110
- env[RESPONSE] = oidresp
111
-
112
- if method = req.GET["_method"]
113
- method = method.upcase
114
- if HTTP_METHODS.include?(method)
115
- env["REQUEST_METHOD"] = method
116
- end
117
- end
118
-
119
- query_hash = env["rack.request.query_hash"]
120
- query_hash.delete("_method")
121
- query_hash.delete_if do |key, value|
122
- key =~ /^openid\./
123
- end
124
-
125
- env["QUERY_STRING"] = env["rack.request.query_string"] =
126
- Rack::Utils.build_query(env["rack.request.query_hash"])
127
-
128
- request_uri = env["PATH_INFO"]
129
- if env["QUERY_STRING"].any?
130
- request_uri << "?" + env["QUERY_STRING"]
131
- end
132
- env["REQUEST_URI"] = request_uri
133
- end
134
-
135
- def realm_url(req)
136
- url = req.scheme + "://"
137
- url << req.host
138
-
139
- if req.scheme == "https" && req.port != 443 ||
140
- req.scheme == "http" && req.port != 80
141
- url << ":#{req.port}"
142
- end
143
-
144
- url
145
- end
146
-
147
- def request_url(req)
148
- url = realm_url(req)
149
- url << req.script_name
150
- url << req.path_info
151
- url
152
- end
153
-
154
- def redirect_to(url)
155
- [303, {"Content-Type" => "text/html", "Location" => url}, []]
156
- end
157
-
158
- def open_id_redirect_url(req, oidreq, trust_root = nil, return_to = nil, method = nil)
159
- if return_to
160
- method ||= "get"
161
- else
162
- return_to = request_url(req)
163
- method ||= req.request_method
164
- end
165
-
166
- method = method.to_s.downcase
167
- oidreq.return_to_args['_method'] = method unless method == "get"
168
- oidreq.redirect_url(trust_root || realm_url(req), return_to || request_url(req))
169
- end
170
-
171
- def add_simple_registration_fields(oidreq, fields)
172
- sregreq = ::OpenID::SReg::Request.new
173
-
174
- if required = fields["required"]
175
- sregreq.request_fields(Array(required), true)
176
- end
177
-
178
- if optional = fields["optional"]
179
- sregreq.request_fields(Array(optional), false)
180
- end
181
-
182
- if policy_url = fields["policy_url"]
183
- sregreq.policy_url = policy_url
184
- end
185
-
186
- oidreq.add_extension(sregreq)
187
- end
188
-
189
- def timeout_protection_from_identity_server
190
- yield
191
- rescue Timeout::Error
192
- TimeoutResponse.new
193
- end
194
- end
195
- end
File without changes