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.
- data/LICENSE +20 -0
- data/README.md +134 -0
- data/Rakefile +72 -0
- data/lib/hancock.rb +47 -0
- data/lib/models/consumer.rb +24 -0
- data/lib/models/user.rb +63 -0
- data/lib/sinatra/hancock/defaults.rb +25 -0
- data/lib/sinatra/hancock/openid_server.rb +96 -0
- data/lib/sinatra/hancock/sessions.rb +53 -0
- data/lib/sinatra/hancock/users.rb +79 -0
- data/lib/sinatra/hancock/views/openid_server/yadis.erb +13 -0
- data/lib/sinatra/hancock/views/sessions/unauthenticated.haml +14 -0
- data/lib/sinatra/hancock/views/users/register_form.haml +12 -0
- data/lib/sinatra/hancock/views/users/signup_confirmation.haml +13 -0
- data/lib/sinatra/hancock/views/users/signup_form.haml +18 -0
- data/spec/acceptance/signing_up_spec.rb +90 -0
- data/spec/app.rb +29 -0
- data/spec/features/sessions.feature +24 -0
- data/spec/features/signup.feature +26 -0
- data/spec/features/sso.feature +35 -0
- data/spec/features/step_definitions/sessions_steps.rb +31 -0
- data/spec/features/step_definitions/signup_steps.rb +56 -0
- data/spec/features/step_definitions/sso_steps.rb +74 -0
- data/spec/features/support/env.rb +16 -0
- data/spec/fixtures.rb +39 -0
- data/spec/matchers.rb +170 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/units/app_spec.rb +15 -0
- data/spec/units/consumer_spec.rb +43 -0
- data/spec/units/identity_provider_spec.rb +27 -0
- data/spec/units/landing_page_spec.rb +42 -0
- data/spec/units/login_spec.rb +61 -0
- data/spec/units/logout_spec.rb +22 -0
- data/spec/units/openid_spec.rb +154 -0
- data/spec/units/signup_spec.rb +34 -0
- data/spec/units/user_spec.rb +10 -0
- 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
|
+

|
|
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
|
data/lib/models/user.rb
ADDED
|
@@ -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?
|