hancock-client 0.0.7 → 0.0.8

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.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