hancock 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Corey Donohoe <atmos@atmos.org>, 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,134 @@
1
+ hancock
2
+ =======
3
+
4
+ It's like your [John Hancock][johnhancock] for all of your company's apps.
5
+
6
+ A lot of this is extracted from our internal single sign on server at [Engine
7
+ Yard][ey]. We use a different [datamapper][datamapper] backend but it should
8
+ be a great start for most people.
9
+
10
+ Features
11
+ ========
12
+ An [OpenID][openid] based [Single Sign On][sso] server that provides:
13
+
14
+ * a single authoritative source for user authentication
15
+ * a [whitelist][whitelist] for consumer applications
16
+ * integration with the big ruby frameworks via [rack][hancock_examples].
17
+ * configurable [sreg][sreg] parameters to consumers
18
+
19
+ How it Works
20
+ ============
21
+ ![SSO Handshake](http://img.skitch.com/20090305-be6wwmbc4gfsi9euy3w7np31mm.jpg)
22
+
23
+ This handshake seems kind of complex but it only happens when you need to
24
+ validate a user session on the consumer.
25
+
26
+ Your Rackup File
27
+ ================
28
+ # thin start -p PORT -R config.ru
29
+ require 'rubygems'
30
+ gem 'sinatra', '~>0.9.1.1'
31
+ require 'hancock'
32
+ require 'sinatra/ditties'
33
+
34
+ DataMapper.setup(:default, "sqlite3:///#{Dir.pwd}/development.db")
35
+
36
+ Sinatra::Mailer.config = {
37
+ :host => 'smtp.example.com',
38
+ :port => '25',
39
+ :user => 'sso',
40
+ :pass => 'lolerskates',
41
+ :auth => :plain # :plain, :login, :cram_md5, the default is no auth
42
+ :domain => "example.com" # the HELO domain provided by the client to the server
43
+ }
44
+
45
+ if ENV['MIGRATE_ME']
46
+ DataMapper.auto_migrate!
47
+ Hancock::Consumer.create(:url => 'http://localhost:3000/sso/login', :label => 'Local Dev', :internal => false)
48
+ Hancock::Consumer.create(:url => 'http://localhost:4000/sso/login', :label => 'Local Dev', :internal => false)
49
+ Hancock::Consumer.create(:url => 'http://localhost:5000/sso/login', :label => 'Local Dev', :internal => false)
50
+ end
51
+
52
+ class Dragon < Hancock::App
53
+ set :views, 'views'
54
+ set :public, 'public'
55
+ set :environment, :production
56
+
57
+ set :provider_name, 'Example SSO Provider'
58
+ set :do_not_reply, 'sso@atmos.org'
59
+
60
+ get '/' do
61
+ redirect '/sso/login' unless session[:user_id]
62
+ erb "<h2>Hello <%= session[:first_name] %><!-- <%= session.inspect %>"
63
+ end
64
+ end
65
+ run Dragon
66
+
67
+ Installation
68
+ ============
69
+ % gem sources
70
+ *** CURRENT SOURCES ***
71
+
72
+ http://gems.rubyforge.org/
73
+ http://gems.engineyard.com
74
+ http://gems.github.com
75
+
76
+ You need a few gems to function
77
+
78
+ % sudo gem install dm-core do_sqlite3
79
+ % sudo gem install sinatra guid rspec ruby-openid webrat
80
+
81
+ Deployment Setup
82
+ ================
83
+ You can deploy hancock on any rack compatible setup. You need a database that
84
+ datamapper can connect to. Generate an example rackup file for yourself based
85
+ on the example above.
86
+
87
+ % irb
88
+ >> require 'rubygems'
89
+ => false
90
+ >> require 'hancock'
91
+ => true
92
+ >> DataMapper.setup(:default, "sqlite3:///#{Dir.pwd}/development.db")
93
+ => #<DataMapper::Adapters::Sqlite3Adapter:0x1ae639c ...>
94
+ >> DataMapper.auto_migrate!
95
+ => [Hancock::User, Hancock::Consumer]
96
+
97
+ Consult the datamapper documentation if you need to connect to something other
98
+ than sqlite. This runs the initial user migration to bootstrap your db.
99
+
100
+ >> Hancock::Consumer.create(:url => 'http://hr.example.com/sso/login', :label => 'Human Resources', :internal => true)
101
+ => #<Hancock::Consumer id=1 url="http://hr.example.com/sso/login" label="Human Resources" internal=true>
102
+
103
+ This portion setup a consumer application that will be allowed access to the SSO
104
+ server. You need to explicitly add each application you wish to grant access to.
105
+
106
+ On the horizon
107
+ ==============
108
+ * signup with email based validation
109
+
110
+ Possibilities
111
+ =============
112
+ * single sign off
113
+ * some kinda awesome [oauth][oauth] hooks
114
+
115
+ Sponsored By
116
+ ============
117
+ * [Engine Yard][ey]
118
+
119
+ [johnhancock]: http://www.urbandictionary.com/define.php?term=john+hancock
120
+ [ey]: http://www.engineyard.com/
121
+ [sr]: http://github.com/sr
122
+ [atmos]: http://github.com/atmos
123
+ [halorgium]: http://github.com/halorgium
124
+ [adelcambre]: http://github.com/adelcambre
125
+ [srfork]: http://github.com/sr/webrat/tree/sinatra
126
+ [webrat]: http://github.com/brynary/webrat
127
+ [hancock_examples]: http://github.com/atmos/hancock-client/tree/98aae96077a8fbfa0097f33ec3ecd628fc549c54/examples/dragon
128
+ [datamapper]: http://datamapper.org
129
+ [openid]: http://openid.net/
130
+ [sso]: http://en.wikipedia.org/wiki/Single_sign-on
131
+ [whitelist]: http://en.wikipedia.org/wiki/Whitelist
132
+ [oauth]: http://oauth.net/
133
+ [sreg]: http://openid.net/specs/openid-simple-registration-extension-1_0.html#response_format
134
+ [simpledb]: http://aws.amazon.com/simpledb/
data/Rakefile ADDED
@@ -0,0 +1,72 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+ require 'spec/rake/spectask'
6
+ require 'cucumber/rake/task'
7
+
8
+ GEM = "hancock"
9
+ GEM_VERSION = "0.0.2"
10
+ AUTHOR = ["Corey Donohoe", "Tim Carey-Smith"]
11
+ EMAIL = [ "atmos@atmos.org", "tim@spork.in" ]
12
+ HOMEPAGE = "http://github.com/atmos/hancock"
13
+ SUMMARY = "A gem that provides a Single Sign On server"
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.name = GEM
17
+ s.version = GEM_VERSION
18
+ s.platform = Gem::Platform::RUBY
19
+ s.has_rdoc = true
20
+ s.extra_rdoc_files = ["README.md", "LICENSE"]
21
+ s.summary = SUMMARY
22
+ s.description = s.summary
23
+ s.authors = AUTHOR
24
+ s.email = EMAIL
25
+ s.homepage = HOMEPAGE
26
+
27
+ # Uncomment this to add a dependency
28
+ s.add_dependency "dm-core", "~>0.9.10"
29
+ s.add_dependency "ruby-openid", "~>2.1.2"
30
+ s.add_dependency "sinatra", "~>0.9.1.1"
31
+ s.add_dependency "guid", "~>0.1.1"
32
+
33
+ s.require_path = 'lib'
34
+ s.autorequire = GEM
35
+ s.files = %w(LICENSE README.md Rakefile) + Dir.glob("{lib,spec}/**/*")
36
+ end
37
+
38
+ task :default => [:spec, :features]
39
+
40
+ desc "Run specs"
41
+ Spec::Rake::SpecTask.new do |t|
42
+ t.spec_files = FileList['spec/**/*_spec.rb']
43
+ t.spec_opts = %w(-fp --color)
44
+
45
+ t.rcov = true
46
+ t.rcov_opts << '--text-summary'
47
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
48
+ t.rcov_opts << '--exclude' << '.gem/,spec,examples'
49
+ #t.rcov_opts << '--only-uncovered'
50
+ end
51
+
52
+ Rake::GemPackageTask.new(spec) do |pkg|
53
+ pkg.gem_spec = spec
54
+ end
55
+
56
+ desc "create a gemspec file"
57
+ task :make_spec do
58
+ File.open("#{GEM}.gemspec", "w") do |file|
59
+ file.puts spec.to_ruby
60
+ end
61
+ end
62
+
63
+ Cucumber::Rake::Task.new(:features) do |t|
64
+ t.libs << 'lib'
65
+ t.cucumber_opts = "--format pretty"
66
+ t.step_list = 'spec/features/**/*.rb'
67
+ t.feature_list = 'spec/features/**/*.feature'
68
+ t.rcov = true
69
+ t.rcov_opts << '--text-summary'
70
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
71
+ t.rcov_opts << '--exclude' << '.gem/,spec,examples'
72
+ end
data/lib/hancock.rb ADDED
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+
3
+ gem 'dm-core', '~>0.9.10'
4
+ require 'dm-core'
5
+ require 'dm-validations'
6
+ require 'dm-timestamps'
7
+
8
+ gem 'ruby-openid', '~>2.1.2'
9
+ require 'openid'
10
+ require 'openid/store/filesystem'
11
+ require 'openid/extensions/sreg'
12
+
13
+ gem 'sinatra', '~>0.9.1.1'
14
+ require 'sinatra/base'
15
+
16
+ gem 'sinatra-ditties', '~>0.0.3'
17
+ require 'sinatra/ditties'
18
+
19
+ gem 'guid', '~>0.1.1'
20
+ require 'guid'
21
+
22
+ module Hancock; end
23
+
24
+ require File.expand_path(File.dirname(__FILE__)+'/models/user')
25
+ require File.expand_path(File.dirname(__FILE__)+'/models/consumer')
26
+ require File.expand_path(File.dirname(__FILE__)+'/sinatra/hancock/defaults')
27
+ require File.expand_path(File.dirname(__FILE__)+'/sinatra/hancock/sessions')
28
+ require File.expand_path(File.dirname(__FILE__)+'/sinatra/hancock/users')
29
+ require File.expand_path(File.dirname(__FILE__)+'/sinatra/hancock/openid_server')
30
+
31
+ module Hancock
32
+ class ConfigurationError < StandardError; end
33
+
34
+ class App < Sinatra::Default
35
+ enable :sessions
36
+
37
+ set :sreg_params, [:email, :first_name, :last_name, :internal]
38
+ set :provider_name, 'Hancock SSO Provider!'
39
+ set :do_not_reply, nil
40
+
41
+ register Sinatra::Hancock::Defaults
42
+ register Sinatra::Hancock::Sessions
43
+ register Sinatra::Hancock::Users
44
+ register Sinatra::Hancock::OpenIDServer
45
+ end
46
+ end
47
+
@@ -0,0 +1,24 @@
1
+ class Hancock::Consumer
2
+ include DataMapper::Resource
3
+
4
+ property :id, Serial
5
+ property :url, String, :nullable => false, :unique => true, :unique_index => true, :length => 1024
6
+ property :label, String, :nullable => true, :default => nil
7
+ property :internal, Boolean, :nullable => false, :defalut => false
8
+
9
+ def self.allowed?(host)
10
+ !first(:url => host).nil?
11
+ end
12
+
13
+ def self.visible
14
+ all(:internal => false).select do |c|
15
+ c.label
16
+ end
17
+ end
18
+
19
+ def self.internal
20
+ all(:internal => true).select do |c|
21
+ c.label
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,63 @@
1
+ class Hancock::User
2
+ include DataMapper::Resource
3
+
4
+ property :id, Serial
5
+ property :first_name, String
6
+ property :last_name, String
7
+ property :email, String, :unique => true, :unique_index => true
8
+ property :internal, Boolean, :default => false
9
+
10
+ property :salt, String
11
+ property :crypted_password, String
12
+
13
+ property :enabled, Boolean, :default => false
14
+ property :access_token, String
15
+
16
+ attr_accessor :password, :password_confirmation
17
+
18
+ def reset_access_token
19
+ @access_token = Digest::SHA1.hexdigest(Guid.new.to_s)
20
+ end
21
+
22
+ def authenticated?(password)
23
+ crypted_password == encrypt(password)
24
+ end
25
+
26
+ def encrypt(password)
27
+ self.class.encrypt(password, salt)
28
+ end
29
+
30
+ def password_required?
31
+ crypted_password.blank? || !password.blank?
32
+ end
33
+
34
+ def encrypt_password
35
+ return if password.blank?
36
+ @salt = Digest::SHA1.hexdigest("--#{Guid.new.to_s}}--email--") if new_record?
37
+ @crypted_password = encrypt(password)
38
+ end
39
+
40
+ validates_present :password, :if => proc{|m| m.password_required?}
41
+ validates_is_confirmed :password, :if => proc{|m| m.password_required?}
42
+
43
+ before :save, :encrypt_password
44
+ before :save, :reset_access_token
45
+
46
+ def self.encrypt(password, salt)
47
+ Digest::SHA1.hexdigest("--#{salt}--#{password}--")
48
+ end
49
+
50
+ def self.signup(params)
51
+ seed = Guid.new.to_s
52
+ new(:email => params['email'],
53
+ :first_name => params['first_name'],
54
+ :last_name => params['last_name'],
55
+ :password => Digest::SHA1.hexdigest(seed),
56
+ :password_confirmation => Digest::SHA1.hexdigest(seed))
57
+ end
58
+
59
+ def self.authenticate(email, password)
60
+ u = first(:email => email)
61
+ u && u.authenticated?(password) && u.enabled ? u : nil
62
+ end
63
+ end
@@ -0,0 +1,25 @@
1
+ module Sinatra
2
+ module Hancock
3
+ module Defaults
4
+ module Helpers
5
+ def forbidden!
6
+ throw :halt, [403, 'Forbidden']
7
+ end
8
+
9
+ def absolute_url(suffix = nil)
10
+ port_part = case request.scheme
11
+ when "http"
12
+ request.port == 80 ? "" : ":#{request.port}"
13
+ when "https"
14
+ request.port == 443 ? "" : ":#{request.port}"
15
+ end
16
+ "#{request.scheme}://#{request.host}#{port_part}#{suffix}"
17
+ end
18
+ end
19
+ def self.registered(app)
20
+ app.helpers Helpers
21
+ end
22
+ end
23
+ end
24
+ register Hancock::Defaults
25
+ end
@@ -0,0 +1,96 @@
1
+ module Sinatra
2
+ module Hancock
3
+ module OpenIDServer
4
+ def self.openid_server_template(file, suffix = 'erb')
5
+ template = File.expand_path(File.dirname(__FILE__)+'/views/openid_server')
6
+ File.read("#{template}/#{file}.#{suffix}")
7
+ end
8
+ module Helpers
9
+ def server
10
+ if @server.nil?
11
+ server_url = absolute_url('/sso')
12
+ dir = File.join(Dir.tmpdir, 'openid-store')
13
+ store = OpenID::Store::Filesystem.new(dir)
14
+ @server = OpenID::Server::Server.new(store, server_url)
15
+ end
16
+ return @server
17
+ end
18
+
19
+ def url_for_user
20
+ absolute_url("/sso/users/#{session_user.id}")
21
+ end
22
+
23
+ def render_response(oidresp)
24
+ if oidresp.needs_signing
25
+ signed_response = server.signatory.sign(oidresp)
26
+ end
27
+ web_response = server.encode_response(oidresp)
28
+
29
+ case web_response.code
30
+ when 302
31
+ session.delete(:return_to)
32
+ redirect web_response.headers['location']
33
+ else
34
+ web_response.body
35
+ end
36
+ end
37
+ end
38
+
39
+ def self.registered(app)
40
+ app.send(:include, Sinatra::Hancock::OpenIDServer::Helpers)
41
+
42
+ app.template(:yadis) { openid_server_template('yadis') }
43
+
44
+ app.get '/sso/xrds' do
45
+ response.headers['Content-Type'] = 'application/xrds+xml'
46
+ @types = [ OpenID::OPENID_IDP_2_0_TYPE ]
47
+ erb :yadis, :layout => false
48
+ end
49
+
50
+ app.get '/sso/users/:id' do
51
+ @types = [ OpenID::OPENID_2_0_TYPE, OpenID::SREG_URI ]
52
+ response.headers['Content-Type'] = 'application/xrds+xml'
53
+ response.headers['X-XRDS-Location'] = absolute_url("/sso/users/#{params['id']}")
54
+
55
+ erb :yadis, :layout => false
56
+ end
57
+
58
+ [:get, :post].each do |meth|
59
+ app.send(meth, '/sso') do
60
+ begin
61
+ oidreq = server.decode_request(params)
62
+ rescue OpenID::Server::ProtocolError => e
63
+ oidreq = session[:last_oidreq]
64
+ end
65
+ throw(:halt, [400, 'Bad Request']) unless oidreq
66
+
67
+ oidresp = nil
68
+ if oidreq.kind_of?(OpenID::Server::CheckIDRequest)
69
+ session[:last_oidreq] = oidreq
70
+ session[:return_to] = absolute_url('/sso')
71
+
72
+ ensure_authenticated
73
+ unless oidreq.identity == url_for_user
74
+ forbidden!
75
+ end
76
+ forbidden! unless ::Hancock::Consumer.allowed?(oidreq.trust_root)
77
+
78
+ oidresp = oidreq.answer(true, nil, oidreq.identity)
79
+ sreg_data = {
80
+ 'last_name' => session_user.last_name,
81
+ 'first_name' => session_user.first_name,
82
+ 'email' => session_user.email
83
+ }
84
+ sregresp = OpenID::SReg::Response.new(sreg_data)
85
+ oidresp.add_extension(sregresp)
86
+ else
87
+ oidresp = server.handle_request(oidreq) #associate and more?
88
+ end
89
+ render_response(oidresp)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ register Hancock::OpenIDServer
96
+ end
@@ -0,0 +1,53 @@
1
+ module Sinatra
2
+ module Hancock
3
+ module Sessions
4
+ def self.sessions_template(file)
5
+ template = File.expand_path(File.dirname(__FILE__)+'/views/sessions')
6
+ File.read("#{template}/#{file}.haml")
7
+ end
8
+
9
+ module Helpers
10
+ def session_user
11
+ session['user_id'].nil? ? nil : ::Hancock::User.get(session['user_id'])
12
+ end
13
+
14
+ def ensure_authenticated
15
+ if trust_root = session['return_to'] || params['return_to']
16
+ if ::Hancock::Consumer.allowed?(trust_root)
17
+ if session_user
18
+ redirect "#{trust_root}?id=#{session_user.id}"
19
+ else
20
+ session['return_to'] = trust_root
21
+ end
22
+ else
23
+ throw(:halt, [403, 'Forbidden'])
24
+ end
25
+ end
26
+ throw(:halt, [401, haml(:unauthenticated)]) unless session_user
27
+ end
28
+ end
29
+
30
+ def self.registered(app)
31
+ app.send(:include, Sinatra::Hancock::Sessions::Helpers)
32
+ app.template(:unauthenticated) { sessions_template('unauthenticated') }
33
+ app.get '/sso/login' do
34
+ ensure_authenticated
35
+ end
36
+ app.post '/sso/login' do
37
+ @user = ::Hancock::User.authenticate(params['email'], params['password'])
38
+ if @user
39
+ session['user_id'] = @user.id
40
+ end
41
+ ensure_authenticated
42
+ redirect session['return_to'] || '/'
43
+ end
44
+
45
+ app.get '/sso/logout' do
46
+ session.clear
47
+ redirect '/'
48
+ end
49
+ end
50
+ end
51
+ end
52
+ register Hancock::Sessions
53
+ end
@@ -0,0 +1,79 @@
1
+ module Sinatra
2
+ module Hancock
3
+ module Users
4
+ def self.users_template(file)
5
+ template = File.expand_path(File.dirname(__FILE__)+'/views/users')
6
+ File.read("#{template}/#{file}.haml")
7
+ end
8
+
9
+ module Helpers
10
+ def user_by_token(token)
11
+ user = ::Hancock::User.first(:access_token => token)
12
+ throw(:halt, [400, 'BadRequest']) unless user
13
+ session['user_id'] = user.id
14
+ user
15
+ end
16
+
17
+ def signup_email
18
+ <<-HAML
19
+ Hello #{@user.first_name},
20
+
21
+ Thanks for signing up for #{::Hancock::App.provider_name}! In order to
22
+ complete your registration you will need to click on the following link.
23
+
24
+ #{@registration_url}
25
+
26
+ Thanks,
27
+ The #{::Hancock::App.provider_name} team
28
+ #{absolute_url('/')}
29
+ HAML
30
+ end
31
+ end
32
+
33
+ def self.registered(app)
34
+ app.helpers Helpers
35
+ app.helpers Sinatra::Mailer
36
+
37
+ app.template(:signup_confirmation) { users_template('signup_confirmation') }
38
+ app.template(:signup_form) { users_template('signup_form') }
39
+ app.template(:register_form) { users_template('register_form') }
40
+
41
+ app.get '/sso/register/:token' do
42
+ user_by_token(params['token'])
43
+ haml :register_form
44
+ end
45
+
46
+ app.post '/sso/register/:token' do
47
+ user = user_by_token(params['token'])
48
+ user.update_attributes(:enabled => true,
49
+ :access_token => nil,
50
+ :password => params['password'],
51
+ :password_confirmation => params['password_confirmation'])
52
+ destination = session.delete('return_to') || '/'
53
+ session.reject! { |key,value| key != 'user_id' }
54
+ redirect destination
55
+ end
56
+
57
+ app.get '/sso/signup' do
58
+ haml :signup_form
59
+ end
60
+
61
+ app.post '/sso/signup' do
62
+ @user = ::Hancock::User.signup(params)
63
+ from = options.do_not_reply
64
+
65
+ if @user.save
66
+ raise ::Hancock::ConfigurationError.new("You need to define options.do_not_reply") unless from
67
+ @registration_url = absolute_url("/sso/register/#{@user.access_token}")
68
+ email :to => @user.email,
69
+ :from => from,
70
+ :subject => "Welcome to #{::Hancock::App.provider_name}!",
71
+ :body => haml(signup_email)
72
+ end
73
+ haml :signup_confirmation
74
+ end
75
+ end
76
+ end
77
+ end
78
+ register Hancock::Users
79
+ end
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xrds:XRDS
3
+ xmlns:xrds="xri://$xrds"
4
+ xmlns="xri://$xrd*($v*2.0)">
5
+ <XRD>
6
+ <Service priority="0">
7
+ <% @types.each do |typ| %>
8
+ <Type><%= typ %></Type>
9
+ <% end %>
10
+ <URI><%= absolute_url('/sso') %></URI>
11
+ </Service>
12
+ </XRD>
13
+ </xrds:XRDS>
@@ -0,0 +1,14 @@
1
+ %fieldset
2
+ %legend You need to log in, buddy.
3
+ %form{:action => '/sso/login', :method => 'POST'}
4
+ %label{:for => 'email'}
5
+ Email:
6
+ %input{:type => 'text', :name => 'email'}
7
+ %br
8
+ %label{:for => 'password'}
9
+ Password:
10
+ %input{:type => 'password', :name => 'password'}
11
+ %br
12
+ %input{:type => 'submit', :value => 'Login'}
13
+ or
14
+ %a{:href => '/sso/signup'} Signup
@@ -0,0 +1,12 @@
1
+ %fieldset
2
+ %legend Enter your new password
3
+ %form{:action => "/sso/register/#{params['token']}", :method => 'POST'}
4
+ %label{:for => 'password'}
5
+ Password:
6
+ %input{:type => 'password', :name => 'password'}
7
+ %br
8
+ %label{:for => 'password_confirmation'}
9
+ Password(Again):
10
+ %input{:type => 'password', :name => 'password_confirmation'}
11
+ %br
12
+ %input{:type => 'submit', :value => 'Am I Done Yet?'}
@@ -0,0 +1,13 @@
1
+ - if @user.valid?
2
+ %h3 Success!
3
+ %p Check your email and you'll see a registration link!
4
+ - if Hancock::App.environment == :development
5
+ /
6
+ %a{:href => absolute_url("/sso/register/#{@user.access_token}")} Clicky Clicky
7
+ - else
8
+ %h3 Signup Failed
9
+ #errors
10
+ - @user.errors.each do |message|
11
+ %p= message
12
+ %p
13
+ %a{:href => '/sso/signup'} Try Again?