hancock 0.0.2

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.
Files changed (37) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +134 -0
  3. data/Rakefile +72 -0
  4. data/lib/hancock.rb +47 -0
  5. data/lib/models/consumer.rb +24 -0
  6. data/lib/models/user.rb +63 -0
  7. data/lib/sinatra/hancock/defaults.rb +25 -0
  8. data/lib/sinatra/hancock/openid_server.rb +96 -0
  9. data/lib/sinatra/hancock/sessions.rb +53 -0
  10. data/lib/sinatra/hancock/users.rb +79 -0
  11. data/lib/sinatra/hancock/views/openid_server/yadis.erb +13 -0
  12. data/lib/sinatra/hancock/views/sessions/unauthenticated.haml +14 -0
  13. data/lib/sinatra/hancock/views/users/register_form.haml +12 -0
  14. data/lib/sinatra/hancock/views/users/signup_confirmation.haml +13 -0
  15. data/lib/sinatra/hancock/views/users/signup_form.haml +18 -0
  16. data/spec/acceptance/signing_up_spec.rb +90 -0
  17. data/spec/app.rb +29 -0
  18. data/spec/features/sessions.feature +24 -0
  19. data/spec/features/signup.feature +26 -0
  20. data/spec/features/sso.feature +35 -0
  21. data/spec/features/step_definitions/sessions_steps.rb +31 -0
  22. data/spec/features/step_definitions/signup_steps.rb +56 -0
  23. data/spec/features/step_definitions/sso_steps.rb +74 -0
  24. data/spec/features/support/env.rb +16 -0
  25. data/spec/fixtures.rb +39 -0
  26. data/spec/matchers.rb +170 -0
  27. data/spec/spec_helper.rb +61 -0
  28. data/spec/units/app_spec.rb +15 -0
  29. data/spec/units/consumer_spec.rb +43 -0
  30. data/spec/units/identity_provider_spec.rb +27 -0
  31. data/spec/units/landing_page_spec.rb +42 -0
  32. data/spec/units/login_spec.rb +61 -0
  33. data/spec/units/logout_spec.rb +22 -0
  34. data/spec/units/openid_spec.rb +154 -0
  35. data/spec/units/signup_spec.rb +34 -0
  36. data/spec/units/user_spec.rb +10 -0
  37. metadata +143 -0
@@ -0,0 +1,18 @@
1
+ %fieldset
2
+ %legend Signup for an account
3
+ %form{:action => '/sso/signup', :method => 'POST'}
4
+ %label{:for => 'first_name'}
5
+ First Name:
6
+ %input{:type => 'text', :name => 'first_name'}
7
+ %br
8
+ %label{:for => 'last_name'}
9
+ Last Name:
10
+ %input{:type => 'text', :name => 'last_name'}
11
+ %br
12
+ %label{:for => 'email'}
13
+ Email:
14
+ %input{:type => 'text', :name => 'email'}
15
+ %br
16
+ %input{:type => 'submit', :value => 'Signup'}
17
+ or
18
+ %a{:href => '/'} Cancel
@@ -0,0 +1,90 @@
1
+ require File.expand_path(File.dirname(__FILE__)+'/../spec_helper')
2
+
3
+ describe "visiting /sso/signup" do
4
+ def app
5
+ Hancock::App
6
+ end
7
+ before(:each) do
8
+ @user = Hancock::User.new(:email => /\w+@\w+\.\w{2,3}/.gen.downcase,
9
+ :first_name => /\w+/.gen.capitalize,
10
+ :last_name => /\w+/.gen.capitalize)
11
+ end
12
+ describe "when signing up" do
13
+ it "should sign the user up" do
14
+ get '/sso/signup'
15
+
16
+ post '/sso/signup', :email => @user.email,
17
+ :first_name => @user.first_name,
18
+ :last_name => @user.last_name
19
+
20
+ confirmation_url = last_response.body.to_s.match(%r!/sso/register/\w{40}!)
21
+ confirmation_url.should_not be_nil
22
+
23
+ get "#{confirmation_url}"
24
+ password = /\w+{9,32}/.gen
25
+
26
+ last_response.body.to_s.should have_selector("form[action='#{confirmation_url}']")
27
+
28
+ post "#{confirmation_url}", :password => password, :password_confirmation => password
29
+ follow_redirect!
30
+
31
+ last_response.body.to_s.should have_selector("h3:contains('Hello #{@user.first_name} #{@user.last_name}')")
32
+ end
33
+
34
+ describe "and form hacking" do
35
+ it "should be unauthorized" do
36
+ get '/sso/signup'
37
+
38
+ post '/sso/signup', :email => @user.email,
39
+ :first_name => @user.first_name,
40
+ :last_name => @user.last_name
41
+
42
+ fake_url = /\w+{9,40}/.gen
43
+ get "/sso/register/#{fake_url}"
44
+ last_response.body.to_s.should match(/BadRequest/)
45
+ end
46
+ end
47
+ end
48
+
49
+ if ENV['WATIR']
50
+ begin
51
+ require 'safariwatir'
52
+ describe "with no valid browser sessions" do
53
+ before(:each) do
54
+ @sso_server = 'http://hancock.atmos.org/sso'
55
+ @browser = Watir::Safari.new
56
+ @browser.goto("http://localhost:5000/sso/logout")
57
+ end
58
+ it "should browse properly in safari" do
59
+ # session cookie fails on localhost :\
60
+ # sso_server = 'http://localhost:20000/sso'
61
+
62
+ # make a request and signup to access the site
63
+ @browser.goto('http://localhost:5000/')
64
+ @browser.link(:url, "#{@sso_server}/signup").click
65
+ @browser.text_field(:name, :first_name).set(@user.first_name)
66
+ @browser.text_field(:name, :last_name).set(@user.last_name)
67
+ @browser.text_field(:name, :email).set(@user.email)
68
+ @browser.button(:value, 'Signup').click
69
+
70
+ # hacky way to strip this outta the markup in development mode
71
+ register_url = @browser.html.match(%r!#{@sso_server}/register/\w{40}!).to_s
72
+ register_url.should_not be_nil
73
+ password = /\w+{9,32}/.gen
74
+
75
+ # register from the url from their email
76
+ @browser.goto(register_url)
77
+ @browser.text_field(:name, :password).set(password)
78
+ @browser.text_field(:name, :password_confirmation).set(password)
79
+ @browser.button(:value, 'Am I Done Yet?').click
80
+
81
+ sleep 2
82
+
83
+ # sent back to be greeted on the consumer
84
+ @browser.html.should match(%r!Hancock Client: Sinatra!)
85
+ @browser.html.should have_selector("h2 a[href='mailto:#{@user.email}']:contains('#{@user.first_name} #{@user.last_name}')")
86
+ end
87
+ end
88
+ rescue; end
89
+ end
90
+ end
data/spec/app.rb ADDED
@@ -0,0 +1,29 @@
1
+ module Sinatra
2
+ module Hancock
3
+ module TestApp
4
+ module Helpers
5
+ def landing_page
6
+ <<-HAML
7
+ %h3 Hello #{session_user.first_name} #{session_user.last_name}!
8
+ - unless @consumers.empty?
9
+ %ul#consumers
10
+ - @consumers.each do |consumer|
11
+ %li
12
+ %a{:href => consumer.url}= consumer.label
13
+ HAML
14
+ end
15
+ end
16
+ def self.registered(app)
17
+ app.helpers Helpers
18
+ app.set :sessions, true
19
+ app.get '/' do
20
+ ensure_authenticated
21
+ @consumers = ::Hancock::Consumer.visible
22
+ @consumers += ::Hancock::Consumer.internal if session_user.internal?
23
+ haml landing_page
24
+ end
25
+ end
26
+ end
27
+ end
28
+ ::Hancock::App.register(Hancock::TestApp)
29
+ end
@@ -0,0 +1,24 @@
1
+ Feature: Logging In to an SSO Account
2
+ In order to authenticate existing users
3
+ As an existing user
4
+ Scenario: logging in
5
+ Given I am not logged in on the sso provider
6
+ And a valid consumer and user exists
7
+ When I request the landing page
8
+ Then I should see the login form
9
+ When I login
10
+ Then I should see a list of consumers
11
+ Scenario: logging in as a redirect from a consumer
12
+ Given I am not logged in on the sso provider
13
+ And a valid consumer and user exists
14
+ When I request authentication returning to the consumer app
15
+ Then I should see the login form
16
+ When I login
17
+ Then I should be redirected to the consumer app to start the handshake
18
+ Scenario: logging in
19
+ Given I am not logged in on the sso provider
20
+ And a valid consumer and user exists
21
+ When I request authentication
22
+ Then I should see the login form
23
+ When I login
24
+ Then I should be redirected to the sso provider root on login
@@ -0,0 +1,26 @@
1
+ Feature: Signing Up for an SSO Account
2
+ In order to get users involved
3
+ As a new user
4
+ Scenario: signing up as a redirect from a consumer
5
+ Given I am not logged in on the sso provider
6
+ And a valid consumer exists
7
+ When I request authentication returning to the consumer app
8
+ Then I should see the login form
9
+ When I click signup
10
+ Then I should see the signup form
11
+ When I signup with valid info
12
+ Then I should receive a registration url via email
13
+ When I hit the registration url and provide a password
14
+ Then I should be redirected to the consumer app
15
+
16
+ Scenario: signing up
17
+ Given I am not logged in on the sso provider
18
+ And a valid consumer exists
19
+ Given I request authentication
20
+ Then I should see the login form
21
+ When I click signup
22
+ Then I should see the signup form
23
+ When I signup with valid info
24
+ Then I should receive a registration url via email
25
+ When I hit the registration url and provide a password
26
+ Then I should be redirected to the sso provider root
@@ -0,0 +1,35 @@
1
+ Feature: Authenticating Against the SSO Provider
2
+ In order to authenticate existing users on a consumer via openid
3
+ Scenario: SSO Server should be an OpenID Identity Provider
4
+ When I request the OpenID Identity Provider Page
5
+ Then I should receive a yadis document from the sso server
6
+
7
+ Scenario: OpenID Mode CheckIDSetup
8
+ Given a valid consumer and user exists
9
+ And I am logged in on the sso provider
10
+ When I request the sso page with a checkid mode of checkIDSetup
11
+ Then I should be redirected to the consumer app with openid params
12
+ Scenario: OpenID Mode CheckIDSetup
13
+ Given a valid consumer and user exists
14
+ And I am logged in on the sso provider
15
+ When I request the sso page with a checkid mode of checkIDSetup for another users's page
16
+ Then I should not be redirected to the consumer app with openid params
17
+ Scenario: OpenID Mode CheckIDSetup unauthenticated
18
+ Given a valid consumer and user exists
19
+ When I request the sso page with a checkid mode of checkIDSetup
20
+ Then I should see the login form
21
+
22
+ Scenario: OpenID Mode Immediate
23
+ Given a valid consumer and user exists
24
+ And I am logged in on the sso provider
25
+ When I request the sso page with a checkid mode of immediate
26
+ Then I should be redirected to the consumer app with openid params
27
+ Scenario: OpenID Mode Immediate unauthenticated
28
+ Given a valid consumer and user exists
29
+ When I request the sso page with a checkid mode of immediate
30
+ Then I should see the login form
31
+
32
+ Scenario: OpenID Mode Associate
33
+ Given a valid consumer and user exists
34
+ When I request the sso page with a checkid mode of associate
35
+ Then I should receive an associate response from the sso server
@@ -0,0 +1,31 @@
1
+ Given /^a valid consumer and user exists$/ do
2
+ @consumer = ::Hancock::Consumer.gen(:internal)
3
+ @user = ::Hancock::User.gen
4
+ get '/sso/logout' # log us out if we're logged in
5
+ end
6
+
7
+ Then /^I login$/ do
8
+ post "/sso/login", :email => @user.email,
9
+ :password => @user.password
10
+ end
11
+
12
+ Then /^I should be redirected to the consumer app to start the handshake$/ do
13
+ redirection = Addressable::URI.parse(last_response.headers['Location'])
14
+
15
+ "#{redirection.scheme}://#{redirection.host}#{redirection.path}".should eql(@consumer.url)
16
+ redirection.query_values['id'].to_i.should eql(@user.id)
17
+ end
18
+
19
+ Then /^I should be redirected to the sso provider root on login$/ do
20
+ last_response.headers['Location'].should eql('/')
21
+ follow_redirect!
22
+ end
23
+
24
+ When /^I request the landing page$/ do
25
+ get '/'
26
+ end
27
+
28
+ Then /^I should see a list of consumers$/ do
29
+ last_response.headers['Location'].should eql('/')
30
+ follow_redirect!
31
+ end
@@ -0,0 +1,56 @@
1
+ Given /^I am not logged in on the sso provider$/ do
2
+ @user = Hancock::User.new(:email => /\w+@\w+\.\w{2,3}/.gen.downcase,
3
+ :first_name => /\w+/.gen.capitalize,
4
+ :last_name => /\w+/.gen.capitalize)
5
+ get "/sso/logout"
6
+ end
7
+
8
+ Given /^a valid consumer exists$/ do
9
+ @consumer = ::Hancock::Consumer.gen(:internal)
10
+ end
11
+
12
+ Given /^I request authentication$/ do
13
+ get "/sso/login"
14
+ end
15
+
16
+ Given /^I request authentication returning to the consumer app$/ do
17
+ get "/sso/login?return_to=#{@consumer.url}"
18
+ end
19
+
20
+ Then /^I should see the login form$/ do
21
+ last_response.should be_a_login_form
22
+ end
23
+
24
+ Given /^I click signup$/ do
25
+ get "/sso/signup"
26
+ end
27
+
28
+ Then /^I should see the signup form$/ do
29
+ last_response.should be_a_signup_form
30
+ end
31
+
32
+ Given /^I signup with valid info$/ do
33
+ post "/sso/signup", 'email' => @user.email,
34
+ 'first_name' => @user.first_name,
35
+ 'last_name' => @user.last_name
36
+ last_response.status.should eql(200)
37
+ end
38
+
39
+ Then /^I should receive a registration url via email$/ do
40
+ @confirmation_url = last_response.body.to_s.match(%r!/sso/register/\w{40}!).to_s
41
+ @confirmation_url.should_not match(/^\s*$/)
42
+ end
43
+
44
+ Given /^I hit the registration url and provide a password$/ do
45
+ get @confirmation_url
46
+ post @confirmation_url, 'user[password]' => @user.password,
47
+ 'used[password_confirmation]' => @user.password
48
+ end
49
+
50
+ Then /^I should be redirected to the sso provider root$/ do
51
+ last_response.headers['Location'].should eql('/')
52
+ end
53
+
54
+ Then /^I should be redirected to the consumer app$/ do
55
+ last_response.headers['Location'].should eql(@consumer.url)
56
+ end
@@ -0,0 +1,74 @@
1
+ When /^I am logged in on the sso provider$/ do
2
+ @identity_url = "http://example.org/sso/users/#{@user.id}"
3
+ post "/sso/login", :email => @user.email,
4
+ :password => @user.password
5
+ end
6
+
7
+ When /^I request the sso page with a checkid mode of checkIDSetup$/ do
8
+ params = {
9
+ "openid.ns" => "http://specs.openid.net/auth/2.0",
10
+ "openid.mode" => "checkid_setup",
11
+ "openid.return_to" => @consumer.url,
12
+ "openid.identity" => @identity_url,
13
+ "openid.claimed_id" => @identity_url
14
+ }
15
+
16
+ get "/sso", params
17
+ end
18
+ When %r!^I request the sso page with a checkid mode of checkIDSetup for another users's page$! do
19
+ params = {
20
+ "openid.ns" => "http://specs.openid.net/auth/2.0",
21
+ "openid.mode" => "checkid_setup",
22
+ "openid.return_to" => @consumer.url,
23
+ "openid.identity" => 'http://example.org/sso/users/42',
24
+ "openid.claimed_id" => 'http://example.org/sso/users/42'
25
+ }
26
+
27
+ get "/sso", params
28
+ end
29
+
30
+ When /^I request the sso page with a checkid mode of associate$/ do
31
+ @openid_session = OpenID::Consumer::AssociationManager.create_session("DH-SHA1")
32
+ params = {
33
+ "openid.ns" => 'http://specs.openid.net/auth/2.0',
34
+ "openid.mode" => "associate",
35
+ "openid.session_type" => 'DH-SHA1',
36
+ "openid.assoc_type" => 'HMAC-SHA1',
37
+ "openid.dh_consumer_public"=> @openid_session.get_request['dh_consumer_public']
38
+ }
39
+
40
+ get "/sso", params
41
+ end
42
+
43
+ When /^I request the sso page with a checkid mode of immediate$/ do
44
+ params = {
45
+ "openid.ns" => "http://specs.openid.net/auth/2.0",
46
+ "openid.mode" => "checkid_immediate",
47
+ "openid.return_to" => @consumer.url,
48
+ "openid.identity" => @identity_url,
49
+ "openid.claimed_id" => @identity_url
50
+ }
51
+
52
+ get "/sso", params
53
+ end
54
+
55
+ Then /^I should be redirected to the consumer app with openid params$/ do
56
+ last_response.should be_a_redirect_to_the_consumer(@consumer, @user)
57
+ end
58
+
59
+ Then /^I should receive an associate response from the sso server$/ do
60
+ last_response.should be_an_openid_associate_response(@openid_session)
61
+ end
62
+
63
+ Then /^I should not be redirected to the consumer app with openid params$/ do
64
+ last_response.status.should eql(403)
65
+ end
66
+
67
+ When /^I request the OpenID Identity Provider Page$/ do
68
+ get '/sso/xrds'
69
+ end
70
+
71
+ Then /^I should receive a yadis document from the sso server$/ do
72
+ last_response.should be_an_identity_provider
73
+ end
74
+
@@ -0,0 +1,16 @@
1
+ require File.expand_path(File.dirname(__FILE__)+'/../../spec_helper')
2
+ require 'haml'
3
+
4
+ Hancock::App.set :environment, :development
5
+
6
+ World do
7
+ def app
8
+ @app = Rack::Builder.new do
9
+ run Hancock::App
10
+ end
11
+ end
12
+ include Rack::Test::Methods
13
+ include Webrat::Methods
14
+ include Webrat::Matchers
15
+ include Hancock::Matchers
16
+ end
data/spec/fixtures.rb ADDED
@@ -0,0 +1,39 @@
1
+ Hancock::User.fix {{
2
+ :enabled => true,
3
+ :email => /\w+@\w+.\w{2,3}/.gen.downcase,
4
+ :first_name => /\w+/.gen.capitalize,
5
+ :last_name => /\w+/.gen.capitalize,
6
+ :password => (pass = /\w+/.gen.downcase),
7
+ :password_confirmation => pass,
8
+ :salt => (salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--email--")),
9
+ :crypted_password => Hancock::User.encrypt(pass, salt)
10
+ }}
11
+
12
+ Hancock::User.fix(:internal) {{
13
+ :enabled => true,
14
+ :email => /\w+@\w+.\w{2,3}/.gen.downcase,
15
+ :first_name => /\w+/.gen.capitalize,
16
+ :last_name => /\w+/.gen.capitalize,
17
+ :password => (pass = /\w+/.gen.downcase),
18
+ :password_confirmation => pass,
19
+ :salt => (salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--email--")),
20
+ :crypted_password => Hancock::User.encrypt(pass, salt),
21
+ :internal => true
22
+ }}
23
+
24
+ Hancock::Consumer.fix(:internal) {{
25
+ :url => %r!http://(\w+).example.org/login!.gen.downcase,
26
+ :label => /(\w+) (\w+)/.gen,
27
+ :internal => true
28
+ }}
29
+
30
+ Hancock::Consumer.fix(:visible_to_all) {{
31
+ :url => %r!http://(\w+).consumerapp.com/login!.gen.downcase,
32
+ :label => /(\w+) (\w+)/.gen,
33
+ :internal => false
34
+ }}
35
+
36
+ Hancock::Consumer.fix(:hidden) {{
37
+ :url => 'http://localhost:9292/login',
38
+ :internal => false
39
+ }}
data/spec/matchers.rb ADDED
@@ -0,0 +1,170 @@
1
+ module Hancock
2
+ module Matchers
3
+ class LoginForm
4
+ include Webrat::Methods
5
+ include Webrat::Matchers
6
+ def matches?(target)
7
+ target.should have_selector("form[action='/sso/login'][method='POST']")
8
+ target.should have_selector("form[action='/sso/login'][method='POST'] input[type='text'][name='email']")
9
+ target.should have_selector("form[action='/sso/login'][method='POST'] input[type='password'][name='password']")
10
+ target.should have_selector("form[action='/sso/login'][method='POST'] input[type='submit'][value='Login']")
11
+ true
12
+ end
13
+ def failure_message
14
+ puts "Expected a login form to be displayed, it wasn't"
15
+ end
16
+ end
17
+
18
+ def be_a_login_form
19
+ LoginForm.new
20
+ end
21
+
22
+ class SignupForm
23
+ include Webrat::Methods
24
+ include Webrat::Matchers
25
+ def matches?(target)
26
+ target.should have_selector("form[action='/sso/signup'][method='POST']")
27
+ target.should have_selector("form[action='/sso/signup'][method='POST'] input[type='text'][name='email']")
28
+ target.should have_selector("form[action='/sso/signup'][method='POST'] input[type='text'][name='first_name']")
29
+ target.should have_selector("form[action='/sso/signup'][method='POST'] input[type='text'][name='last_name']")
30
+ target.should have_selector("form[action='/sso/signup'][method='POST'] input[type='submit'][value='Signup']")
31
+ true
32
+ end
33
+
34
+ def failure_message
35
+ puts "Expected a signup form to be displayed, it wasn't"
36
+ end
37
+ end
38
+ def be_a_signup_form
39
+ SignupForm.new
40
+ end
41
+ class IdentityProviderDocument
42
+ include Webrat::Methods
43
+ include Webrat::Matchers
44
+ include Spec::Matchers
45
+ def matches?(target)
46
+ target.header['Content-Type'].should eql('application/xrds+xml')
47
+ target.body.should have_xpath("//xrd/service[uri='http://example.org/sso']")
48
+ target.body.should have_xpath("//xrd/service[type='http://specs.openid.net/auth/2.0/server']")
49
+ true
50
+ end
51
+ def failure_message
52
+ puts "Expected a identity provider yadis document"
53
+ end
54
+ end
55
+
56
+ def be_an_identity_provider
57
+ IdentityProviderDocument.new
58
+ end
59
+
60
+ class RedirectToConsumer
61
+ include Spec::Matchers
62
+ include Webrat::Methods
63
+ include Webrat::Matchers
64
+ def initialize(consumer, user)
65
+ @consumer, @user = consumer, user
66
+ @identity_url = "http://example.org/sso/users/#{user.id}"
67
+ end
68
+
69
+ def matches?(target)
70
+ target.status.should == 302
71
+
72
+ redirect_params = Addressable::URI.parse(target.headers['Location']).query_values
73
+
74
+ redirect_params['openid.ns'].should == 'http://specs.openid.net/auth/2.0'
75
+ redirect_params['openid.mode'].should == 'id_res'
76
+ redirect_params['openid.return_to'].should == @consumer.url
77
+ redirect_params['openid.assoc_handle'].should =~ /^\{HMAC-SHA1\}\{[^\}]{8}\}\{[^\}]{8}\}$/
78
+ redirect_params['openid.op_endpoint'].should == 'http://example.org/sso'
79
+ redirect_params['openid.claimed_id'].should == @identity_url
80
+ redirect_params['openid.identity'].should == @identity_url
81
+
82
+ redirect_params['openid.sreg.email'].should == @user.email
83
+ redirect_params['openid.sreg.last_name'].should == @user.last_name
84
+ redirect_params['openid.sreg.first_name'].should == @user.first_name
85
+
86
+ redirect_params['openid.sig'].should_not be_nil
87
+ redirect_params['openid.signed'].should_not be_nil
88
+ redirect_params['openid.response_nonce'].should_not be_nil
89
+ true
90
+ end
91
+ def failure_message
92
+ puts "Expected a redirect to the consumer"
93
+ end
94
+ end
95
+
96
+ def be_a_redirect_to_the_consumer(consumer, user)
97
+ RedirectToConsumer.new(consumer, user)
98
+ end
99
+
100
+ class ReturnAnOpenIDAssociateResponse
101
+ include Spec::Matchers
102
+ include Webrat::Methods
103
+ include Webrat::Matchers
104
+ def initialize(session)
105
+ @openid_session = session
106
+ end
107
+
108
+ def matches?(target)
109
+ message = OpenID::Message.from_kvform("#{target.body}") # wtf do i have to interpolate this!
110
+ secret = @openid_session.extract_secret(message)
111
+ secret.should_not be_nil
112
+
113
+ args = message.get_args(OpenID::OPENID_NS)
114
+
115
+ args['assoc_type'].should == 'HMAC-SHA1'
116
+ args['assoc_handle'].should =~ /^\{HMAC-SHA1\}\{[^\}]{8}\}\{[^\}]{8}\}$/
117
+ args['session_type'].should == 'DH-SHA1'
118
+ args['enc_mac_key'].size.should == 28
119
+ args['expires_in'].should =~ /^\d+$/
120
+ args['dh_server_public'].size.should == 172
121
+ true
122
+ end
123
+ def failure_message
124
+ puts "Expected an OpenID Associate Response"
125
+ end
126
+ end
127
+ def be_an_openid_associate_response(openid_session)
128
+ ReturnAnOpenIDAssociateResponse.new(openid_session)
129
+ end
130
+
131
+ class ReturnAnOpenIDImmediateResponse
132
+ include Spec::Matchers
133
+ include Webrat::Methods
134
+ include Webrat::Matchers
135
+ def initialize(consumer, user)
136
+ @consumer, @user = consumer, user
137
+ @identity_url = "http://example.org/sso/users/#{user.id}"
138
+ end
139
+
140
+ def matches?(target)
141
+ target.status.should == 302
142
+
143
+ redirect_params = Addressable::URI.parse(target.headers['Location']).query_values
144
+
145
+ redirect_params['openid.ns'].should == 'http://specs.openid.net/auth/2.0'
146
+ redirect_params['openid.mode'].should == 'id_res'
147
+ redirect_params['openid.return_to'].should == @consumer.url
148
+ redirect_params['openid.assoc_handle'].should =~ /^\{HMAC-SHA1\}\{[^\}]{8}\}\{[^\}]{8}\}$/
149
+ redirect_params['openid.op_endpoint'].should == 'http://example.org/sso'
150
+ redirect_params['openid.claimed_id'].should == @identity_url
151
+ redirect_params['openid.identity'].should == @identity_url
152
+
153
+ redirect_params['openid.sreg.email'].should == @user.email
154
+ redirect_params['openid.sreg.last_name'].should == @user.last_name
155
+ redirect_params['openid.sreg.first_name'].should == @user.first_name
156
+
157
+ redirect_params['openid.sig'].should_not be_nil
158
+ redirect_params['openid.signed'].should_not be_nil
159
+ redirect_params['openid.response_nonce'].should_not be_nil
160
+ true
161
+ end
162
+ def failure_message
163
+ puts "Expected an OpenID Associate Response"
164
+ end
165
+ end
166
+ def be_an_openid_immediate_response(consumer, user)
167
+ ReturnAnOpenIDImmediateResponse.new(consumer, user)
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,61 @@
1
+ require 'rubygems'
2
+ require 'pp'
3
+ gem 'rspec', '~>1.2.0'
4
+ require 'spec'
5
+ require 'sinatra/test'
6
+ require 'dm-sweatshop'
7
+
8
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
9
+ require 'hancock'
10
+ gem 'webrat', '~>0.4.2'
11
+ require 'webrat/sinatra'
12
+
13
+ gem 'rack-test', '~>0.1.0'
14
+ require 'rack/test'
15
+
16
+ require File.expand_path(File.dirname(__FILE__) + '/app')
17
+ require File.expand_path(File.dirname(__FILE__) + '/matchers')
18
+ require File.expand_path(File.dirname(__FILE__) + '/fixtures')
19
+
20
+ DataMapper.setup(:default, 'sqlite3::memory:')
21
+ DataMapper.auto_migrate!
22
+ Sinatra::Mailer.delivery_method = :test_send
23
+
24
+ Webrat.configure do |config|
25
+ if ENV['SELENIUM'].nil?
26
+ config.mode = :sinatra
27
+ else
28
+ gem 'selenium-client', '~>1.2.10'
29
+ config.mode = :selenium
30
+ config.application_framework = :sinatra
31
+ config.application_port = 4567
32
+ require 'webrat/selenium'
33
+ end
34
+ end
35
+
36
+ Hancock::App.set :environment, :development
37
+ Hancock::App.set :do_not_reply, 'sso@example.com'
38
+
39
+ Spec::Runner.configure do |config|
40
+ def app
41
+ @app = Rack::Builder.new do
42
+ run Hancock::App
43
+ end
44
+ end
45
+
46
+ def login(user)
47
+ post '/sso/login', :email => user.email, :password => user.password
48
+ end
49
+
50
+ config.include(Rack::Test::Methods)
51
+ config.include(Webrat::Methods)
52
+ config.include(Webrat::Matchers)
53
+ config.include(Hancock::Matchers)
54
+ config.after do
55
+ Sinatra::Mailer::Email.deliveries = [ ]
56
+ end
57
+ unless ENV['SELENIUM'].nil?
58
+ config.include(Webrat::Selenium::Methods)
59
+ config.include(Webrat::Selenium::Matchers)
60
+ end
61
+ end