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