hancock-client 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Corey Donohoe <cdonohoe@engineyard.com, Tim Carey-Smith <tim@spork.in>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ hancock-client
2
+ ==============
3
+
4
+ A gem that integrates [sinatra][sinatra] applications into the Hancock SSO
5
+ environment. It also doubles as rack middleware that can be used in
6
+ rails(>= 2.3.2) and merb(>= 1.0)
7
+
8
+ Dependencies
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-sinatra
18
+ % sudo gem install atmos-hancock
19
+
20
+ testing
21
+ =======
22
+ Rake works but I'm not 100% sure how to test this correctly
23
+
24
+ Application
25
+ ===========
26
+ The goal is to make it simple to write sso enabled apps.
27
+
28
+ require 'rubygems'
29
+ require 'sinatra'
30
+ require 'sinatra/base'
31
+ require 'hancock-client'
32
+
33
+ run Hancock::Client
34
+ class ConsumerApp < Hancock::Client::Default
35
+ set :sso_url, 'http://hancock.atmos.org/sso'
36
+
37
+ set :views, 'views'
38
+ set :public, 'public'
39
+ set :environment, :production
40
+
41
+ get '/' do
42
+ redirect '/login' unless session[:user_id]
43
+ haml(%Q{%h3= "#{session[:first_name]} #{session[:last_name]} - #{session[:email]}"})
44
+ end
45
+ end
46
+
47
+ run ConsumerApp
48
+
49
+ [sinatra]: http://www.sinatrarb.com
data/Rakefile ADDED
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+ require 'spec/rake/spectask'
6
+
7
+ GEM = "hancock-client"
8
+ GEM_VERSION = "0.0.2"
9
+ AUTHOR = "Corey Donohoe"
10
+ EMAIL = ['atmos@atmos.org', 'tim@spork.in']
11
+ HOMEPAGE = "http://github.com/atmos/hancock-client"
12
+ SUMMARY = "A gem that SSO enables sinatra applications with hancock"
13
+
14
+ spec = Gem::Specification.new do |s|
15
+ s.name = GEM
16
+ s.version = GEM_VERSION
17
+ s.platform = Gem::Platform::RUBY
18
+ s.has_rdoc = true
19
+ s.extra_rdoc_files = ["README.md", "LICENSE"]
20
+ s.summary = SUMMARY
21
+ s.description = s.summary
22
+ s.author = AUTHOR
23
+ s.email = EMAIL
24
+ s.homepage = HOMEPAGE
25
+
26
+ # Uncomment this to add a dependency
27
+ s.add_dependency "sinatra", ">=0.9.1.1"
28
+ s.add_dependency "ruby-openid", ">=2.1.2"
29
+
30
+ s.require_path = 'lib'
31
+ s.autorequire = GEM
32
+ s.files = %w(LICENSE README.md Rakefile) + Dir.glob("{lib,spec}/**/*")
33
+ end
34
+
35
+ task :default => :spec
36
+
37
+ desc "Run specs"
38
+ Spec::Rake::SpecTask.new do |t|
39
+ t.spec_files = FileList['spec/**/*_spec.rb']
40
+ t.spec_opts = %w(-fs --color)
41
+ end
42
+
43
+
44
+ Rake::GemPackageTask.new(spec) do |pkg|
45
+ pkg.gem_spec = spec
46
+ end
47
+
48
+ desc "install the gem locally"
49
+ task :install => [:package] do
50
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
51
+ end
52
+
53
+ desc "create a gemspec file"
54
+ task :make_spec do
55
+ File.open("#{GEM}.gemspec", "w") do |file|
56
+ file.puts spec.to_ruby
57
+ end
58
+ end
@@ -0,0 +1,26 @@
1
+ gem 'sinatra', '~>0.9.1'
2
+ require 'rack'
3
+ require 'sinatra/base'
4
+ gem 'dm-core', '~>0.9.10'
5
+ require 'dm-core'
6
+
7
+ gem 'ruby-openid', '>=2.1.2'
8
+ require 'openid'
9
+
10
+ require File.dirname(__FILE__)+'/sinatra/hancock/sso'
11
+
12
+ module Hancock
13
+ module Client
14
+ class Default < ::Sinatra::Default
15
+ enable :sessions
16
+ cattr_accessor :sso_url
17
+
18
+ register Sinatra::Hancock::SSO
19
+
20
+ def self.sso_configure(&block)
21
+ yield self
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,195 @@
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 self.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
@@ -0,0 +1,67 @@
1
+ require 'sinatra/base'
2
+ require File.dirname(__FILE__)+'/../../rack-openid'
3
+
4
+ module Sinatra
5
+ module Hancock
6
+ module SSO
7
+ module Helpers
8
+ def sso_url
9
+ @app.class.sso_url
10
+ end
11
+
12
+ def absolute_url(suffix = nil)
13
+ port_part = case request.scheme
14
+ when "http"
15
+ request.port == 80 ? "" : ":#{request.port}"
16
+ when "https"
17
+ request.port == 443 ? "" : ":#{request.port}"
18
+ end
19
+ "#{request.scheme}://#{request.host}#{port_part}#{suffix}"
20
+ end
21
+ end
22
+
23
+ def self.registered(app)
24
+ app.use(Rack::OpenID)
25
+ app.helpers Hancock::SSO::Helpers
26
+ app.enable :sessions
27
+ app.disable :raise_errors
28
+ app.before { ensure_sso_authenticated unless request.path_info =~ %r!^/sso/log(in|out)! }
29
+
30
+ app.get '/sso/login' do
31
+ if contact_id = params['id']
32
+ response['WWW-Authenticate'] = Rack::OpenID.build_header(
33
+ :identifier => "#{sso_url}/users/#{contact_id}",
34
+ :trust_root => absolute_url('/sso/login')
35
+ )
36
+ throw :halt, [401, 'got openid?']
37
+ elsif openid = request.env["rack.openid.response"]
38
+ if openid.status == :success
39
+ if contact_id = openid.display_identifier.split("/").last
40
+ session.delete(:last_oidreq)
41
+ session.delete('OpenID::Consumer::last_requested_endpoint')
42
+ session.delete('OpenID::Consumer::DiscoveredServices::OpenID::Consumer::')
43
+
44
+ session[:user_id] = contact_id
45
+ params = openid.message.get_args("http://openid.net/extensions/sreg/1.1")
46
+ params.each { |key, value| session[key.to_sym] = value.to_s }
47
+ redirect '/'
48
+ else
49
+ raise "No contact could be found for #{openid.display_identifier}"
50
+ end
51
+ else
52
+ throw :halt, [503, "Error: #{openid.status}"]
53
+ end
54
+ else
55
+ redirect "#{sso_url}/login?return_to=#{absolute_url('/sso/login')}"
56
+ end
57
+ end
58
+
59
+ app.get '/sso/logout' do
60
+ session.clear
61
+ redirect "#{sso_url}/logout"
62
+ end
63
+ end
64
+ end
65
+ end
66
+ register Hancock::SSO
67
+ end
data/spec/client.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'sinatra/base'
2
+
3
+ module Hancock
4
+ module Spec
5
+ class Client < ::Hancock::Client::Default
6
+ get '/' do
7
+ redirect '/sso/login' unless session[:user_id]
8
+ haml(<<-HAML
9
+ %p
10
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
11
+ HAML
12
+ )
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe Sinatra::Hancock::SSO do
4
+ it "should protect the root url" do
5
+ get '/'
6
+ @response.headers['Location'].should eql('/sso/login')
7
+ end
8
+ it "should greet the user when authenticated" do
9
+ get '/', { }, :session => {:user_id => 42}
10
+ @response.body.should have_selector('p')
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require 'rubygems'
3
+ require 'pp'
4
+ gem 'rspec', '~>1.2.0'
5
+ require 'spec'
6
+ gem 'webrat', '~>0.4.2'
7
+ require 'webrat'
8
+ require 'dm-sweatshop'
9
+
10
+ gem 'sinatra', '~>0.9.1'
11
+ require 'sinatra/base'
12
+ require 'sinatra/test'
13
+ require 'sinatra/hancock/sso'
14
+
15
+ require 'hancock-client'
16
+
17
+ require File.dirname(__FILE__)+'/client'
18
+
19
+ Spec::Runner.configure do |config|
20
+ config.include(Sinatra::Test)
21
+ config.include(Webrat::Methods)
22
+ config.include(Webrat::Matchers)
23
+ config.before(:each) do
24
+ @app = Rack::Builder.new do
25
+ run Hancock::Spec::Client
26
+ end
27
+ end
28
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hancock-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Corey Donohoe
8
+ autorequire: hancock-client
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-20 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: sinatra
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.9.1.1
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: ruby-openid
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.1.2
34
+ version:
35
+ description: A gem that SSO enables sinatra applications with hancock
36
+ email:
37
+ - atmos@atmos.org
38
+ - tim@spork.in
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files:
44
+ - README.md
45
+ - LICENSE
46
+ files:
47
+ - LICENSE
48
+ - README.md
49
+ - Rakefile
50
+ - lib/hancock-client.rb
51
+ - lib/rack-openid.rb
52
+ - lib/sinatra
53
+ - lib/sinatra/hancock
54
+ - lib/sinatra/hancock/sso.rb
55
+ - spec/client.rb
56
+ - spec/hancock_sinatra_spec.rb
57
+ - spec/spec_helper.rb
58
+ has_rdoc: true
59
+ homepage: http://github.com/atmos/hancock-client
60
+ post_install_message:
61
+ rdoc_options: []
62
+
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ requirements: []
78
+
79
+ rubyforge_project:
80
+ rubygems_version: 1.3.1
81
+ signing_key:
82
+ specification_version: 2
83
+ summary: A gem that SSO enables sinatra applications with hancock
84
+ test_files: []
85
+