pasaporte 0.0.1 → 0.0.3
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/History.txt +2 -2
- data/Manifest.txt +14 -5
- data/README.txt +52 -15
- data/Rakefile +4 -9
- data/bin/pasaporte-emit-app.rb +5 -0
- data/bin/pasaporte-fcgi.rb +5 -1
- data/lib/pasaporte.rb +200 -332
- data/lib/pasaporte/hacks.rb +10 -0
- data/lib/pasaporte/julik_state.rb +14 -10
- data/lib/pasaporte/lighttpd/cacert.pem +21 -0
- data/lib/pasaporte/lighttpd/cert_localhost_combined.pem +32 -0
- data/lib/pasaporte/lighttpd/sample-lighttpd-config.conf +27 -0
- data/lib/pasaporte/models.rb +254 -0
- data/lib/pasaporte/token_box.rb +43 -0
- data/test/helper.rb +7 -1
- data/test/test_edit_profile.rb +53 -0
- data/test/test_openid.rb +20 -13
- data/test/test_pasaporte.rb +0 -103
- data/test/test_profile.rb +3 -2
- data/test/test_public_signon.rb +26 -0
- data/test/test_settings.rb +1 -2
- data/test/test_signout.rb +24 -0
- data/test/test_token_box.rb +54 -0
- data/test/test_with_partial_ssl.rb +99 -0
- metadata +27 -9
- data/lib/pasaporte/.DS_Store +0 -0
- data/lib/pasaporte/assets/.DS_Store +0 -0
data/History.txt
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
===
|
2
|
-
*
|
1
|
+
=== 0.0.3 - 14.02.2009
|
2
|
+
* Most glaring bugs fixed
|
data/Manifest.txt
CHANGED
@@ -3,10 +3,9 @@ Manifest.txt
|
|
3
3
|
README.txt
|
4
4
|
Rakefile
|
5
5
|
TODO.txt
|
6
|
+
bin/pasaporte-emit-app.rb
|
6
7
|
bin/pasaporte-fcgi.rb
|
7
8
|
lib/pasaporte.rb
|
8
|
-
lib/pasaporte/.DS_Store
|
9
|
-
lib/pasaporte/assets/.DS_Store
|
10
9
|
lib/pasaporte/assets/bgbar.png
|
11
10
|
lib/pasaporte/assets/lock.png
|
12
11
|
lib/pasaporte/assets/mainbg_green.gif
|
@@ -20,22 +19,32 @@ lib/pasaporte/auth/remote_web_workplace.rb
|
|
20
19
|
lib/pasaporte/auth/yaml_digest_table.rb
|
21
20
|
lib/pasaporte/auth/yaml_table.rb
|
22
21
|
lib/pasaporte/faster_openid.rb
|
22
|
+
lib/pasaporte/hacks.rb
|
23
23
|
lib/pasaporte/iso_countries.yml
|
24
24
|
lib/pasaporte/julik_state.rb
|
25
|
+
lib/pasaporte/lighttpd/cacert.pem
|
26
|
+
lib/pasaporte/lighttpd/cert_localhost_combined.pem
|
27
|
+
lib/pasaporte/lighttpd/sample-lighttpd-config.conf
|
25
28
|
lib/pasaporte/markaby_ext.rb
|
29
|
+
lib/pasaporte/models.rb
|
26
30
|
lib/pasaporte/pasaporte_store.rb
|
27
31
|
lib/pasaporte/timezones.yml
|
32
|
+
lib/pasaporte/token_box.rb
|
28
33
|
test/fixtures/pasaporte_approvals.yml
|
29
34
|
test/fixtures/pasaporte_profiles.yml
|
30
35
|
test/fixtures/pasaporte_throttles.yml
|
31
36
|
test/helper.rb
|
32
37
|
test/mosquito.rb
|
33
|
-
test/test_throttle.rb
|
34
|
-
test/testable_openid_fetcher.rb
|
35
38
|
test/test_approval.rb
|
36
39
|
test/test_auth_backends.rb
|
40
|
+
test/test_edit_profile.rb
|
37
41
|
test/test_openid.rb
|
38
42
|
test/test_pasaporte.rb
|
39
43
|
test/test_profile.rb
|
44
|
+
test/test_public_signon.rb
|
40
45
|
test/test_settings.rb
|
41
|
-
test/
|
46
|
+
test/test_signout.rb
|
47
|
+
test/test_throttle.rb
|
48
|
+
test/test_token_box.rb
|
49
|
+
test/test_with_partial_ssl.rb
|
50
|
+
test/testable_openid_fetcher.rb
|
data/README.txt
CHANGED
@@ -4,7 +4,7 @@ This is Pasaporte, a small identity server with a colored bar on top. It's in th
|
|
4
4
|
of Crowd (but smaller). Will act as a mediator between OpenID and arbitary services where
|
5
5
|
users are distinguished by their nickname (login), their password and a domain name.
|
6
6
|
|
7
|
-
==The idea
|
7
|
+
== The idea
|
8
8
|
|
9
9
|
Pasaporte brings OpenID to the traditional simplicity of
|
10
10
|
|
@@ -15,29 +15,66 @@ that, when called, will return true or false. Yes, it's that simple. All the neg
|
|
15
15
|
smorgasbord, profile editing, encryptodecryption and other electrabombastic niceties are
|
16
16
|
going to be taken care of.
|
17
17
|
|
18
|
-
|
18
|
+
Here is an example of a simple auth procedure:
|
19
|
+
|
20
|
+
# Stick your super auth HERE. Should be a proc accepting login, pass and domain
|
21
|
+
Pasaporte::AUTH = lambda do | login, pass, domain |
|
22
|
+
allowd = {"joe" => "secret"}
|
23
|
+
return (allowd[login] && (allowd[login] == pass))
|
24
|
+
end
|
25
|
+
|
26
|
+
Obviously you can let your auth procedure look up in ACLs and things like that if you
|
27
|
+
really need it to.
|
28
|
+
|
29
|
+
If the password becomes stale or should the authentication backend say that it no
|
19
30
|
longer has the user in question the authorization tokens are immediately revoked, and any
|
20
31
|
authorization requests will be denied.
|
21
32
|
|
22
|
-
==
|
33
|
+
== Using SSL
|
34
|
+
|
35
|
+
It is recommended that you run pasaporte in full SSL mode. However,
|
36
|
+
some OpenID consumers disallow OpenID providers with self-signed (i.e. free)
|
37
|
+
SSL certificates. Pasaporte mitigates this by offering the "partial SSL" mode. When turned on,
|
38
|
+
only the signon page (where the password is entered) and subsequent pages with which the user
|
39
|
+
interacts will be protected with SSL encryption, while the public OpenID endpoint will NOT be
|
40
|
+
SSL-enabled. Same is true for the server-server step of OpenID handshake.
|
41
|
+
|
42
|
+
This will allow even stricter providers to use Pasaporte servers, but without passing the user login
|
43
|
+
over the wire in clear text.
|
44
|
+
|
45
|
+
When partial SSL is turned on, the profile page (OpenID identity) will forcibly be made
|
46
|
+
unencrypted (will redirect to non-secure port).
|
47
|
+
|
48
|
+
Partial SSL is disabled by default - to enable set PARTIAL_SSL to true.
|
49
|
+
|
50
|
+
== Current issues
|
51
|
+
|
52
|
+
As of now, we are not aware of sites that cannot consume OpenID from Pasaporte.
|
53
|
+
There is currently no provision for verifying that the URL you are logging in from is the
|
54
|
+
one Pasaporte manages.
|
55
|
+
|
56
|
+
== Configuration
|
23
57
|
|
24
58
|
The adventurous among us can override the defaults (Pasaporte constants) by placing a
|
25
59
|
hash-formatted YAML file called "config.yml" in the pasaporte dir. And don't ask me what
|
26
60
|
a "hash-formatted YAML file" is, because if you do you are not adventurous.
|
27
61
|
|
28
|
-
|
62
|
+
Here the rundown of the config parameters:
|
29
63
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
64
|
+
MAX_FAILED_LOGIN_ATTEMPTS - after how many login attempts the user will be trottled
|
65
|
+
THROTTLE_FOR - Trottle length in seconds
|
66
|
+
ALLOW_DELEGATION - if set to true, the user will be able to redirect his OpenID
|
67
|
+
SESSION_LIFETIME - in seconds - how long does a session remain valid
|
68
|
+
PARTIAL_SSL - see above
|
69
|
+
HTTP_PORT - if partial SSL is used, the port on which the standard version runs
|
70
|
+
SSL_PORT - if partial SSL is used, the port on which the secure version runs
|
34
71
|
|
35
|
-
==Profiles
|
72
|
+
== Profiles
|
36
73
|
|
37
74
|
Pasaporte allows the user to have a simple passport page, where some info can be placed
|
38
|
-
for people who follow the OpenID profile URL. Sharing the information
|
75
|
+
for people who follow the OpenID profile URL. Sharing the information is entirelly optional.
|
39
76
|
|
40
|
-
==The all-id
|
77
|
+
== The all-id
|
41
78
|
|
42
79
|
The login that you use is ultimately the nickname that comes in the URL. For that reason
|
43
80
|
no other login name can be entered in the form. However the user still can verify that
|
@@ -52,20 +89,20 @@ It's important to understand that a Profile record is a helper for metadata and
|
|
52
89
|
something authoritative - it's the auth routine that takes the actual decision about the
|
53
90
|
user's state.
|
54
91
|
|
55
|
-
==Persistence
|
92
|
+
== Persistence
|
56
93
|
|
57
94
|
We store some data that the user might find useful to store and maybe display on his user
|
58
|
-
page. No
|
95
|
+
page. No sessions of the exchange are kept
|
59
96
|
except of the standard OpenID shared secrets (there are not linked to user records in any
|
60
97
|
way).
|
61
98
|
|
62
|
-
==SREG data sharing
|
99
|
+
== SREG data sharing
|
63
100
|
|
64
101
|
There is currently no provision for fetching SREG data (like email, date of birth and such)
|
65
102
|
from the autorizing routine. We might consider this in the future, for now the user has to
|
66
103
|
fill it in himself.
|
67
104
|
|
68
|
-
==Sharding
|
105
|
+
== Sharding
|
69
106
|
|
70
107
|
The users in Pasaporte are segregated by the domain name of Pasaporte server. That is, if
|
71
108
|
you have two domains pointed at +one+ Pasaporte, you will not have name clashes between
|
data/Rakefile
CHANGED
@@ -4,18 +4,11 @@ require 'hoe'
|
|
4
4
|
require File.dirname(__FILE__) + '/lib/pasaporte'
|
5
5
|
$KCODE = 'u'
|
6
6
|
|
7
|
-
class KolkHoe < Hoe
|
8
|
-
def define_tasks
|
9
|
-
extra_deps.reject! {|e| e[0] == 'hoe' }
|
10
|
-
super
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
7
|
# Disable spurious warnings when running tests, ActiveMagic cannot stand -w
|
15
8
|
Hoe::RUBY_FLAGS.replace ENV['RUBY_FLAGS'] || "-I#{%w(lib test).join(File::PATH_SEPARATOR)}" +
|
16
9
|
(Hoe::RUBY_DEBUG ? " #{RUBY_DEBUG}" : '')
|
17
10
|
|
18
|
-
|
11
|
+
psp = Hoe.new('Pasaporte', Pasaporte::VERSION) do |p|
|
19
12
|
p.name = "pasaporte"
|
20
13
|
p.author = "Julik Tarkhanov"
|
21
14
|
p.description = "An OpenID server with a colored bar on top"
|
@@ -25,9 +18,11 @@ KolkHoe.new('Pasaporte', Pasaporte::VERSION) do |p|
|
|
25
18
|
p.rdoc_pattern = /README.txt|CHANGELOG.txt|lib/
|
26
19
|
p.test_globs = 'test/test_*.rb'
|
27
20
|
p.need_zip = true
|
28
|
-
p.extra_deps = ['activerecord', 'camping', ['ruby-openid', '>=2.1.0'], 'flexmock']
|
21
|
+
p.extra_deps = ['activerecord', ['camping' '>=1.5.180'], ['ruby-openid', '>=2.1.0'], 'flexmock']
|
29
22
|
end
|
30
23
|
|
24
|
+
psp.spec.rdoc_options << "--charset" << "utf-8"
|
25
|
+
|
31
26
|
desc "Generate the proper list of country codes from the ISO list"
|
32
27
|
task :build_country_list do
|
33
28
|
ISO_LIST = 'http://www.iso.org/iso/iso3166_en_code_lists.txt'
|
data/bin/pasaporte-fcgi.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
#! /usr/bin/env ruby
|
2
2
|
require 'rubygems'
|
3
3
|
require 'camping'
|
4
4
|
require 'camping/fastcgi'
|
@@ -12,6 +12,10 @@ Camping::Models::Base.establish_connection(
|
|
12
12
|
Pasaporte.create
|
13
13
|
Pasaporte::LOGGER = Logger.new(ENV['HOME'] + "/pasaporte.log")
|
14
14
|
|
15
|
+
ENV.keys.grep(/^PASAPORTE_/).each do | envar |
|
16
|
+
Pasaporte.const_set(envar.gsub(/^PASAPORTE_/, ''), ENV[envar])
|
17
|
+
end
|
18
|
+
|
15
19
|
serv = Camping::FastCGI.new
|
16
20
|
serv.mount('/', Pasaporte)
|
17
21
|
serv.start
|
data/lib/pasaporte.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
gem 'ruby-openid', '>=2.1.0'
|
2
|
+
gem 'ruby-openid', '>=2.1.0' # Not less!
|
3
3
|
|
4
4
|
$: << File.dirname(__FILE__)
|
5
5
|
|
@@ -8,16 +8,24 @@ $: << File.dirname(__FILE__)
|
|
8
8
|
camping/session
|
9
9
|
openid
|
10
10
|
openid/extensions/sreg
|
11
|
-
|
11
|
+
).each {|r| require r }
|
12
|
+
|
13
|
+
Camping.goes :Pasaporte
|
14
|
+
|
15
|
+
%w(
|
12
16
|
pasaporte/julik_state
|
13
17
|
pasaporte/markaby_ext
|
18
|
+
pasaporte/hacks
|
19
|
+
pasaporte/token_box
|
14
20
|
).each {|r| require r }
|
15
21
|
|
16
|
-
Camping.goes :Pasaporte
|
17
22
|
|
23
|
+
Markaby::Builder.set(:indent, 2)
|
18
24
|
Markaby::Builder.set(:output_xml_instruction, false)
|
19
25
|
|
20
26
|
module Pasaporte
|
27
|
+
VERSION = '0.0.3'
|
28
|
+
|
21
29
|
module Auth; end # Acts as a container for auth classes
|
22
30
|
|
23
31
|
MAX_FAILED_LOGIN_ATTEMPTS = 3
|
@@ -25,8 +33,12 @@ module Pasaporte
|
|
25
33
|
DEFAULT_COUNTRY = 'nl'
|
26
34
|
DEFAULT_TZ = 'Europe/Amsterdam'
|
27
35
|
ALLOW_DELEGATION = true
|
28
|
-
|
29
|
-
|
36
|
+
|
37
|
+
|
38
|
+
SESSION_LIFETIME = 12.hours
|
39
|
+
PARTIAL_SSL = false
|
40
|
+
HTTP_PORT = 80
|
41
|
+
SSL_PORT = 443
|
30
42
|
|
31
43
|
LOGGER = Logger.new(STDERR) #:nodoc:
|
32
44
|
PATH = File.expand_path(__FILE__) #:nodoc:
|
@@ -82,18 +94,13 @@ module Pasaporte
|
|
82
94
|
end
|
83
95
|
end
|
84
96
|
|
85
|
-
module LoggerHijack
|
86
|
-
#def service(*the_rest)
|
87
|
-
# _hijacked, OpenID::Util.logger = OpenID::Util.logger, LOGGER
|
88
|
-
# returning(super(*the_rest)) { OpenID::Util.logger = _hijacked }
|
89
|
-
#end
|
90
|
-
end
|
91
|
-
|
92
97
|
# Or a semblance thereof
|
93
98
|
module Secure
|
94
99
|
class PleaseLogin < RuntimeError; end
|
95
100
|
class Throttled < RuntimeError; end
|
96
101
|
class FullStop < RuntimeError; end
|
102
|
+
class RedirectToSSL < RuntimeError; end
|
103
|
+
class RedirectToPlain < RuntimeError; end
|
97
104
|
|
98
105
|
module CheckMethods
|
99
106
|
def _redir_to_login_page!(persistables)
|
@@ -105,7 +112,7 @@ module Pasaporte
|
|
105
112
|
end
|
106
113
|
|
107
114
|
# Allow the user to log in. Suspend any variables passed in the session.
|
108
|
-
def require_login(persistables = {})
|
115
|
+
def require_login!(persistables = {})
|
109
116
|
deny_throttled!
|
110
117
|
raise "No nickname" unless @nickname
|
111
118
|
_redir_to_login_page!(persistables) unless is_logged_in?
|
@@ -117,33 +124,66 @@ module Pasaporte
|
|
117
124
|
def deny_throttled!
|
118
125
|
raise Throttled if Models::Throttle.throttled?(env)
|
119
126
|
end
|
120
|
-
|
127
|
+
|
128
|
+
# When partial SSL is enabled we use this method to redirect
|
129
|
+
def require_ssl!
|
130
|
+
raise RedirectToSSL if PARTIAL_SSL && !@env.HTTPS
|
131
|
+
end
|
132
|
+
|
133
|
+
# When partial SSL is enabled we use this method to force a plain page
|
134
|
+
def require_plain!
|
135
|
+
raise RedirectToPlain if PARTIAL_SSL && @env.HTTPS
|
136
|
+
end
|
137
|
+
|
138
|
+
def validate_token!
|
139
|
+
#from = URI.parse(@env.HTTP_REFERER).path
|
140
|
+
#LOGGER.debug "Validating form token"
|
141
|
+
#token_box.validate!(from, @input.tok)
|
142
|
+
end
|
143
|
+
|
121
144
|
def profile_by_nickname(n)
|
122
145
|
::Pasaporte::Models::Profile.find_or_create_by_nickname_and_domain_name(n, my_domain)
|
123
146
|
end
|
124
147
|
end
|
125
148
|
|
126
|
-
def
|
127
|
-
|
128
|
-
# JulikState.expire_old(SESSION_LIFETIME)
|
129
|
-
# Camping::Models::Session.delete_all("created_at < '%s'" % day_zero)
|
149
|
+
def token_box
|
150
|
+
@state.token_box ||= TokenBox.new
|
130
151
|
end
|
131
152
|
|
132
153
|
def service(*a)
|
133
154
|
begin
|
134
|
-
expire_old_sessions!
|
135
155
|
@ctr = self.class.to_s.split('::').pop
|
136
156
|
super(*a)
|
137
157
|
rescue FullStop
|
138
158
|
return self
|
139
159
|
rescue PleaseLogin
|
140
|
-
LOGGER.info "#{env['REMOTE_ADDR']} - Redirecting to signon
|
160
|
+
LOGGER.info "#{env['REMOTE_ADDR']} - Redirecting to signon"
|
141
161
|
redirect R(Pasaporte::Controllers::Signon, @nickname)
|
142
162
|
return self
|
143
163
|
rescue Throttled
|
144
164
|
LOGGER.info "#{env['REMOTE_ADDR']} - Throttled user tried again"
|
145
165
|
redirect R(Pasaporte::Controllers::ThrottledPage)
|
146
166
|
return self
|
167
|
+
rescue TokenBox::Invalid => i
|
168
|
+
LOGGER.warn "Form token has been compromised on #{@env.REQUEST_URI} - #{i}"
|
169
|
+
LOGGER.warn @state.token_box.inspect
|
170
|
+
redirect R(Pasaporte::Controllers::FormExpired)
|
171
|
+
rescue RedirectToSSL
|
172
|
+
LOGGER.info "Forcing redirect to SSL page"
|
173
|
+
the_uri = URI.parse(@env.REQUEST_URI)
|
174
|
+
the_uri.host = @env.SERVER_NAME
|
175
|
+
the_uri.scheme = 'https'
|
176
|
+
the_uri.port = SSL_PORT unless SSL_PORT.to_i == 443
|
177
|
+
redirect the_uri.to_s
|
178
|
+
return self
|
179
|
+
rescue RedirectToPlain
|
180
|
+
LOGGER.info "Forcing redirect to plain (non-SSL) page"
|
181
|
+
the_uri = URI.parse(@env.REQUEST_URI)
|
182
|
+
the_uri.host = @env.SERVER_NAME
|
183
|
+
the_uri.scheme = 'http'
|
184
|
+
the_uri.port = HTTP_PORT unless HTTP_PORT.to_i == 80
|
185
|
+
redirect the_uri.to_s
|
186
|
+
return self
|
147
187
|
end
|
148
188
|
self
|
149
189
|
end
|
@@ -153,272 +193,19 @@ module Pasaporte
|
|
153
193
|
module CookiePreservingRedirect
|
154
194
|
def redirect(*args)
|
155
195
|
@headers['Set-Cookie'] = @cookies.map { |k,v| "#{k}=#{C.escape(v)}; path=#{self/"/"}" if v != @k[k] } - [nil]
|
196
|
+
force_session_save!
|
156
197
|
super(*args)
|
157
198
|
end
|
158
199
|
end
|
159
200
|
|
160
|
-
# The order here is important. Camping::Session has to come LAST (
|
201
|
+
# The order here is important. Camping::Session has to come LAST (outermost)
|
161
202
|
# otherwise you risk losing the session if one of the services upstream
|
162
203
|
# redirects.
|
163
|
-
[CampingFlash,
|
164
|
-
|
165
|
-
|
166
|
-
module Models
|
167
|
-
MAX = :limit # Thank you rails core, it was MAX before
|
168
|
-
class CreatePasaporte < V 1.0
|
169
|
-
def self.up
|
170
|
-
create_table :pasaporte_profiles, :force => true do |t|
|
171
|
-
# http://openid.net/specs/openid-simple-registration-extension-1_0.html
|
172
|
-
t.column :nickname, :string, MAX => 20
|
173
|
-
t.column :email, :string, MAX => 70
|
174
|
-
t.column :fullname, :string, MAX => 50
|
175
|
-
t.column :dob, :date, :null => true
|
176
|
-
t.column :gender, :string, MAX => 1
|
177
|
-
t.column :postcode, :string, MAX => 10
|
178
|
-
t.column :country, :string, MAX => 2
|
179
|
-
t.column :language, :string, MAX => 5
|
180
|
-
t.column :timezone, :string, MAX => 50
|
181
|
-
|
182
|
-
# And our extensions
|
183
|
-
# is the profile shared (visible to others)
|
184
|
-
t.column :shared, :boolean, :default => false
|
185
|
-
|
186
|
-
# his bio
|
187
|
-
t.column :info, :text
|
188
|
-
|
189
|
-
# when he last used Pasaporte
|
190
|
-
t.column :last_login, :datetime
|
191
|
-
|
192
|
-
# the encryption part that we generate for every user, the other is the pass
|
193
|
-
# the total encryption key for private data will be stored in the session only when
|
194
|
-
# the user is logged in
|
195
|
-
t.column :secret_salt, :integer
|
196
|
-
|
197
|
-
# Good servers delegate
|
198
|
-
t.column :openid_server, :string
|
199
|
-
t.column :openid_delegate, :string
|
200
|
-
|
201
|
-
# We shard by domain
|
202
|
-
t.column :domain_name, :string, :null => false, :default => 'localhost'
|
203
|
-
|
204
|
-
# Keep a close watch on those who
|
205
|
-
t.column :throttle_count, :integer, :default => 0
|
206
|
-
t.column :suspicious, :boolean, :default => false
|
207
|
-
end
|
208
|
-
|
209
|
-
add_index(:pasaporte_profiles, [:nickname, :domain_name], :unique)
|
210
|
-
|
211
|
-
create_table :pasaporte_settings do |t|
|
212
|
-
t.column :setting, :string
|
213
|
-
t.column :value, :binary
|
214
|
-
end
|
215
|
-
|
216
|
-
create_table :pasaporte_associations do |t|
|
217
|
-
# server_url is blob, because URLs could be longer
|
218
|
-
# than db can handle as a string
|
219
|
-
t.column :server_url, :binary
|
220
|
-
t.column :handle, :string
|
221
|
-
t.column :secret, :binary
|
222
|
-
t.column :issued, :integer
|
223
|
-
t.column :lifetime, :integer
|
224
|
-
t.column :assoc_type, :string
|
225
|
-
end
|
226
|
-
|
227
|
-
create_table :pasaporte_nonces do |t|
|
228
|
-
t.column :nonce, :string
|
229
|
-
t.column :created, :integer
|
230
|
-
end
|
204
|
+
[CampingFlash, Secure, JulikState, CookiePreservingRedirect].map{|m| include m }
|
231
205
|
|
232
|
-
create_table :pasaporte_throttles do |t|
|
233
|
-
t.column :created_at, :datetime
|
234
|
-
t.column :client_fingerprint, :string, MAX => 40
|
235
|
-
end
|
236
|
-
end
|
237
206
|
|
238
|
-
|
239
|
-
|
240
|
-
drop_table :pasaporte_settings
|
241
|
-
drop_table :pasaporte_associations
|
242
|
-
drop_table :pasaporte_nonces
|
243
|
-
drop_table :pasaporte_throttles
|
244
|
-
end
|
245
|
-
end
|
246
|
-
class AddAprovals < V(1.1)
|
247
|
-
def self.up
|
248
|
-
create_table :pasaporte_approvals do | t |
|
249
|
-
t.column :profile_id, :integer, :null => false
|
250
|
-
t.column :trust_root, :string, :null => false
|
251
|
-
end
|
252
|
-
add_index(:pasaporte_approvals, [:profile_id, :trust_root], :unique)
|
253
|
-
end
|
254
|
-
|
255
|
-
def self.down
|
256
|
-
drop_table :pasaporte_approvals
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
class MigrateOpenidTables < V(1.2)
|
261
|
-
def self.up
|
262
|
-
drop_table :pasaporte_settings
|
263
|
-
drop_table :pasaporte_nonces
|
264
|
-
create_table :pasaporte_nonces, :force => true do |t|
|
265
|
-
t.column :server_url, :string, :null => false
|
266
|
-
t.column :timestamp, :integer, :null => false
|
267
|
-
t.column :salt, :string, :null => false
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
def self.down
|
272
|
-
drop_table :pasaporte_nonces
|
273
|
-
create_table :pasaporte_nonces, :force => true do |t|
|
274
|
-
t.column "nonce", :string
|
275
|
-
t.column "created", :integer
|
276
|
-
end
|
277
|
-
|
278
|
-
create_table :pasaporte_settings, :force => true do |t|
|
279
|
-
t.column "setting", :string
|
280
|
-
t.column "value", :binary
|
281
|
-
end
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
class ShardOpenidTables < V(1.3)
|
286
|
-
def self.up
|
287
|
-
add_column :pasaporte_associations, :pasaporte_domain, :string, :null => false, :default => 'localhost'
|
288
|
-
add_column :pasaporte_nonces, :pasaporte_domain, :string, :null => false, :default => 'localhost'
|
289
|
-
end
|
290
|
-
|
291
|
-
def self.down
|
292
|
-
remove_column :pasaporte_nonces, :pasaporte_domain
|
293
|
-
remove_column :pasaporte_associations, :pasaporte_domain
|
294
|
-
end
|
295
|
-
end
|
296
|
-
|
297
|
-
# Minimal info we store about people. It's the container for the sreg data
|
298
|
-
# in the first place.
|
299
|
-
class Profile < Base
|
300
|
-
before_create { |p| p.secret_salt = rand(Time.now) }
|
301
|
-
before_save :validate_delegate_uris
|
302
|
-
validates_presence_of :nickname
|
303
|
-
validates_presence_of :domain_name
|
304
|
-
validates_uniqueness_of :nickname, :scope => :domain_name
|
305
|
-
attr_protected :domain_name, :nickname
|
306
|
-
has_many :approvals, :dependent => :delete_all
|
307
|
-
|
308
|
-
any_url_present = lambda do |r|
|
309
|
-
!r.openid_server.blank? || !r.openid_server.blank?
|
310
|
-
end
|
311
|
-
%w(openid_server openid_delegate).map do | c |
|
312
|
-
validates_presence_of c, :if => any_url_present
|
313
|
-
end
|
314
|
-
|
315
|
-
# Convert the profile to sreg according to the spec (YYYY-MM-DD for dob and such)
|
316
|
-
def to_sreg_fields(fields_to_extract = nil)
|
317
|
-
fields_to_extract ||= %w( nickname email fullname dob gender postcode country language timezone )
|
318
|
-
fields_to_extract.inject({}) do | out, field |
|
319
|
-
v = self[field]
|
320
|
-
v.blank? ? out : (out[field.to_s] = v.to_s; out)
|
321
|
-
end
|
322
|
-
end
|
323
|
-
|
324
|
-
# We have to override that because we want our protected attributes
|
325
|
-
def self.find_or_create_by_nickname_and_domain_name(nick, domain)
|
326
|
-
returning(super(nick, domain)) do | me |
|
327
|
-
((me.nickname, me.domain_name = nick, domain) && me.save) if me.new_record?
|
328
|
-
end
|
329
|
-
end
|
330
|
-
class << self
|
331
|
-
alias_method :find_or_create_by_domain_name_and_nickname,
|
332
|
-
:find_or_create_by_nickname_and_domain_name
|
333
|
-
end
|
334
|
-
|
335
|
-
def generate_sess_key
|
336
|
-
self.secret_salt ||= rand(Time.now)
|
337
|
-
s = [nickname, secret_salt, Time.now.year, Time.now.month].join('|')
|
338
|
-
OpenSSL::Digest::SHA1.new(s).hexdigest.to_s
|
339
|
-
end
|
340
|
-
|
341
|
-
# Check if this profile wants us to delegate his openid to a different identity provider.
|
342
|
-
# If both delegate and server are filled in properly this will return true
|
343
|
-
def delegates_openid?
|
344
|
-
ALLOW_DELEGATION && (!openid_server.blank? && !openid_delegate.blank?)
|
345
|
-
end
|
346
|
-
|
347
|
-
def delegates_openid=(nv)
|
348
|
-
(self.openid_server, self.openid_delegate = nil, nil) if [false, '0', 0, 'no'].include?(nv)
|
349
|
-
end
|
350
|
-
alias_method :delegates_openid, :delegates_openid? # for checkboxes
|
351
|
-
|
352
|
-
def to_s; nickname; end
|
353
|
-
|
354
|
-
private
|
355
|
-
def validate_delegate_uris
|
356
|
-
if ([self.openid_server, self.openid_delegate].select{|i| i.blank?}).length == 1
|
357
|
-
errors.add(:delegate_server, "If you use delegation you have to specify both addresses")
|
358
|
-
false
|
359
|
-
end
|
360
|
-
|
361
|
-
%w(openid_server openid_delegate).map do | attr |
|
362
|
-
return if self[attr].blank?
|
363
|
-
begin
|
364
|
-
self[attr] = OpenID::URINorm.urinorm(self[attr])
|
365
|
-
rescue Exception => e
|
366
|
-
errors.add(attr, e.message)
|
367
|
-
end
|
368
|
-
end
|
369
|
-
end
|
370
|
-
end
|
371
|
-
|
372
|
-
# A token that the user has approved a site (a site's trust root) as legal
|
373
|
-
# recipient of his information
|
374
|
-
class Approval < Base
|
375
|
-
belongs_to :profile
|
376
|
-
validates_presence_of :profile_id, :trust_root
|
377
|
-
validates_uniqueness_of :trust_root, :scope => :profile_id
|
378
|
-
def to_s; trust_root; end
|
379
|
-
end
|
380
|
-
|
381
|
-
# Openid setting
|
382
|
-
class Setting < Base; end
|
383
|
-
|
384
|
-
# Openid nonces
|
385
|
-
class Nonce < Base; end
|
386
|
-
|
387
|
-
# Openid assocs
|
388
|
-
class Association < Base
|
389
|
-
def from_record
|
390
|
-
OpenID::Association.new(handle, secret, issued, lifetime, assoc_type)
|
391
|
-
end
|
392
|
-
|
393
|
-
def expired?
|
394
|
-
Time.now.to_i > (issued + lifetime)
|
395
|
-
end
|
396
|
-
end
|
397
|
-
|
398
|
-
# Set throttles
|
399
|
-
class Throttle < Base
|
400
|
-
|
401
|
-
# Set a throttle with the environment of the request
|
402
|
-
def self.set!(e)
|
403
|
-
create(:client_fingerprint => env_hash(e))
|
404
|
-
end
|
405
|
-
|
406
|
-
# Check if an environment is throttled
|
407
|
-
def self.throttled?(e)
|
408
|
-
prune!
|
409
|
-
count(:conditions => ["client_fingerprint = ? AND created_at > ?", env_hash(e), cutoff]) > 0
|
410
|
-
end
|
411
|
-
|
412
|
-
private
|
413
|
-
def self.prune!; delete_all "created_at < '#{cutoff.to_s(:db)}'"; end
|
414
|
-
def self.cutoff; Time.now - THROTTLE_FOR; end
|
415
|
-
def self.env_hash(e)
|
416
|
-
OpenSSL::Digest::SHA1.new([e['REMOTE_ADDR'], e['HTTP_USER_AGENT']].map(&:to_s).join('|')).to_s
|
417
|
-
end
|
418
|
-
end
|
419
|
-
end
|
420
|
-
|
421
|
-
require File.dirname(__FILE__) + '/pasaporte/pasaporte_store'
|
207
|
+
require 'pasaporte/models'
|
208
|
+
require 'pasaporte/pasaporte_store'
|
422
209
|
|
423
210
|
module Controllers
|
424
211
|
|
@@ -437,6 +224,12 @@ module Pasaporte
|
|
437
224
|
post_with_nick(*extras)
|
438
225
|
end
|
439
226
|
|
227
|
+
def head(*extras)
|
228
|
+
raise "Nickname is required for this action" unless (@nickname = extras.shift)
|
229
|
+
raise "#{self.class} does not respond to head_with_nick" unless respond_to?(:head_with_nick)
|
230
|
+
head_with_nick(*extras)
|
231
|
+
end
|
232
|
+
|
440
233
|
# So that we can define put_with_nick and Camping sees it as #put being available
|
441
234
|
def respond_to?(m, *whatever)
|
442
235
|
super(m.to_sym) || super("#{m}_with_nick".to_sym)
|
@@ -457,29 +250,34 @@ module Pasaporte
|
|
457
250
|
include OpenID::Server
|
458
251
|
|
459
252
|
class Err < RuntimeError; end #:nodoc
|
460
|
-
class NeedsApproval <
|
253
|
+
class NeedsApproval < Err; end #:nodoc
|
461
254
|
class Denied < Err; end #:nodoc
|
462
|
-
class NoOpenidRequest <
|
255
|
+
class NoOpenidRequest < Err; end #:nodoc
|
256
|
+
class SwitchUser < Secure::PleaseLogin; end #:nodoc
|
463
257
|
|
464
258
|
def get_with_nick
|
259
|
+
require_plain!
|
465
260
|
begin
|
466
261
|
@oid_request = openid_request_from_input_or_session
|
467
262
|
|
468
|
-
LOGGER.info "
|
263
|
+
LOGGER.info "OpenID: user #{@nickname} must not be throttled"
|
469
264
|
deny_throttled!
|
470
265
|
|
471
|
-
LOGGER.info "
|
266
|
+
LOGGER.info "OpenID: nick must match the identity URL"
|
472
267
|
check_nickname_matches_identity_url
|
268
|
+
|
269
|
+
LOGGER.info "OpenID: identity must reside on our server"
|
270
|
+
check_identity_lives_here
|
473
271
|
|
474
|
-
LOGGER.info "
|
272
|
+
LOGGER.info "OpenID: user must be logged in"
|
475
273
|
check_logged_in
|
476
274
|
|
477
275
|
@profile = profile_by_nickname(@nickname)
|
478
276
|
|
479
|
-
LOGGER.info "
|
277
|
+
LOGGER.info "OpenID: trust root is on the approvals list"
|
480
278
|
check_if_previously_approved
|
481
279
|
|
482
|
-
LOGGER.info "
|
280
|
+
LOGGER.info "OpenID: OpenID verified, redirecting"
|
483
281
|
|
484
282
|
succesful_resp = @oid_request.answer(true)
|
485
283
|
add_sreg(@oid_request, succesful_resp)
|
@@ -487,28 +285,36 @@ module Pasaporte
|
|
487
285
|
rescue NoOpenidRequest
|
488
286
|
return 'This is an OpenID server endpoint.'
|
489
287
|
rescue ProtocolError => e
|
490
|
-
LOGGER.error "
|
288
|
+
LOGGER.error "OpenID: Cannot decode the OpenID request - #{e.message}"
|
491
289
|
return "Something went wrong processing your request"
|
290
|
+
rescue SwitchUser => e
|
291
|
+
# Force a session save, remove the current user from the session and throw
|
292
|
+
# to the login page for the user to switch to
|
293
|
+
@state.nickname = nil
|
294
|
+
force_session_save!
|
295
|
+
LOGGER.warn "OpenID: suspend - need to switch user first"
|
296
|
+
@oid_request.immediate ? ask_user_to_approve : (raise e)
|
492
297
|
rescue PleaseLogin => e
|
493
298
|
# There is a subtlety here. If the user had NO session before entering
|
494
299
|
# this, he will get a new SID upon arriving at the signon page and thus
|
495
300
|
# will loose his openid request
|
496
301
|
force_session_save!
|
497
|
-
LOGGER.warn "
|
302
|
+
LOGGER.warn "OpenID: suspend - the user needs to login first, saving session"
|
498
303
|
@oid_request.immediate ? ask_user_to_approve : (raise e)
|
499
304
|
rescue NeedsApproval
|
500
|
-
LOGGER.warn "
|
305
|
+
LOGGER.warn "OpenID: suspend - the URL needs approval first"
|
501
306
|
ask_user_to_approve
|
502
307
|
rescue Denied => d
|
503
|
-
LOGGER.warn "
|
308
|
+
LOGGER.warn "OpenID: deny OpenID to #{@nickname} - #{d.message}"
|
504
309
|
send_openid_response(@oid_request.answer(false))
|
505
310
|
rescue Secure::Throttled => e
|
506
|
-
LOGGER.warn "
|
311
|
+
LOGGER.warn "OpenID: deny OpenID to #{@nickname} - user is throttled"
|
507
312
|
send_openid_response(@oid_request.answer(false))
|
508
313
|
end
|
509
314
|
end
|
510
315
|
|
511
316
|
def post_with_nick
|
317
|
+
require_plain!
|
512
318
|
req = openid_server.decode_request(input)
|
513
319
|
raise ProtocolError, "The decoded request was nil" if req.nil?
|
514
320
|
# Check for dumb mode HIER!
|
@@ -523,7 +329,7 @@ module Pasaporte
|
|
523
329
|
if input.keys.grep(/openid/).any?
|
524
330
|
@state.delete(:pending_openid)
|
525
331
|
r = openid_server.decode_request(input)
|
526
|
-
LOGGER.info "Starting a
|
332
|
+
LOGGER.info "Starting a session #{r.trust_root} -> #{r.identity}"
|
527
333
|
@state.pending_openid = r
|
528
334
|
elsif @state.pending_openid
|
529
335
|
LOGGER.info "Resuming an OpenID session with #{@state.pending_openid.trust_root}"
|
@@ -534,19 +340,28 @@ module Pasaporte
|
|
534
340
|
end
|
535
341
|
|
536
342
|
def check_nickname_matches_identity_url
|
537
|
-
nick_from_uri = @oid_request.identity.to_s.split(/\//)
|
343
|
+
nick_from_uri = @oid_request.identity.to_s.split(/\//).pop
|
538
344
|
if (nick_from_uri != @nickname)
|
539
345
|
raise Denied, "The identity '#{@oid_request.claimed_id}' does not mach the URL realm"
|
540
346
|
end
|
541
|
-
|
347
|
+
|
542
348
|
if (@state.nickname && (nick_from_uri != @state.nickname))
|
543
|
-
raise
|
349
|
+
raise SwitchUser, "The identity '#{@oid_request.claimed_id}' is not the one of the current user"
|
544
350
|
end
|
545
351
|
end
|
546
|
-
|
352
|
+
|
353
|
+
# TODO: we need to give the user a chance to say "I want these URLs to be legit too"
|
354
|
+
def check_identity_lives_here
|
355
|
+
the_id = URI.parse(@oid_request.identity)
|
356
|
+
# HTTP_HOST check
|
357
|
+
if (the_id.host != @env['HTTP_HOST'])
|
358
|
+
raise Denied, "The identity '#{@oid_request.claimed_id}' is not in this server(#{@env['HTTP_HOST']}"
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
547
362
|
def check_logged_in
|
548
363
|
message = "Before authorizing '%s' you will need to login" % input["openid.trust_root"]
|
549
|
-
require_login(:pending_openid => @oid_request, :msg => message)
|
364
|
+
require_login!(:pending_openid => @oid_request, :msg => message)
|
550
365
|
end
|
551
366
|
|
552
367
|
def ask_user_to_approve
|
@@ -576,29 +391,28 @@ module Pasaporte
|
|
576
391
|
|
577
392
|
# Return the yadis autodiscovery XML for the user
|
578
393
|
class Yadis < personal(:yadis)
|
579
|
-
YADIS_TPL = %{
|
580
|
-
<xrds:XRDS xmlns:xrds="xri://$xrds"
|
581
|
-
xmlns="xri://$xrd*($v*2.0)"
|
582
|
-
xmlns:openid="http://openid.net/xmlns/1.0">
|
394
|
+
YADIS_TPL = %{<?xml version="1.0" encoding="UTF-8"?>
|
395
|
+
<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)">
|
583
396
|
<XRD>
|
584
|
-
<Service
|
397
|
+
<Service>
|
585
398
|
<Type>http://openid.net/signon/1.0</Type>
|
586
399
|
<URI>%s</URI>
|
587
|
-
<openid:Delegate>%s</openid:Delegate>
|
588
400
|
</Service>
|
589
401
|
</XRD>
|
590
402
|
</xrds:XRDS>
|
591
403
|
}
|
592
404
|
|
593
405
|
def get_with_nick
|
594
|
-
@headers["Content-
|
406
|
+
@headers["Content-Type"] = "application/xrds+xml"
|
407
|
+
LOGGER.info "YADIS requested for #{@nickname}"
|
595
408
|
@skip_layout = true
|
596
|
-
|
409
|
+
# We only use the server for now
|
410
|
+
YADIS_TPL % get_endpoints[0]
|
597
411
|
end
|
598
412
|
|
599
413
|
private
|
600
414
|
def get_endpoints
|
601
|
-
defaults = [_our_endpoint_uri,
|
415
|
+
defaults = [_our_endpoint_uri, _our_identity_url]
|
602
416
|
@profile = Profile.find_by_nickname_and_domain_name(@nickname, my_domain)
|
603
417
|
return defaults unless @profile && @profile.delegates_openid?
|
604
418
|
[@profile.openid_server, @profile.openid_delegate]
|
@@ -607,7 +421,7 @@ module Pasaporte
|
|
607
421
|
|
608
422
|
class ApprovalsPage < personal(:approvals)
|
609
423
|
def get_with_nick
|
610
|
-
require_login
|
424
|
+
require_login!
|
611
425
|
@approvals = @profile.approvals
|
612
426
|
if @approvals.empty?
|
613
427
|
@msg = 'You currently do not have any associations with other sites through us'
|
@@ -620,7 +434,7 @@ module Pasaporte
|
|
620
434
|
|
621
435
|
class DeleteApproval < personal('disapprove/(\d+)')
|
622
436
|
def get_with_nick(appr_id)
|
623
|
-
require_login
|
437
|
+
require_login!
|
624
438
|
ap = @profile.approvals.find(appr_id); ap.destroy
|
625
439
|
show_message "The site #{ap} has been removed from your approvals list"
|
626
440
|
redirect R(ApprovalsPage, @nickname)
|
@@ -633,18 +447,27 @@ module Pasaporte
|
|
633
447
|
include Secure::CheckMethods
|
634
448
|
|
635
449
|
def get(nick=nil)
|
636
|
-
LOGGER.info "Entered signon
|
450
|
+
LOGGER.info "Entered signon, #{@env.HTTPS ? :HTTPS : :HTTP_plain }"
|
637
451
|
deny_throttled!
|
638
452
|
return redirect(DashPage, @state.nickname) if @state.nickname
|
639
453
|
if nick && @state.pending_openid
|
640
|
-
humane =
|
454
|
+
humane = begin
|
455
|
+
URI.parse(@state.pending_openid.trust_root).host
|
456
|
+
rescue URI::InvalidURIError
|
457
|
+
LOGGER.error "Failed to parse #{@state.pending_openid.trust_root}"
|
458
|
+
@state.pending_openid.trust_root
|
459
|
+
end
|
641
460
|
show_message "Before authorizing with <b>#{humane}</b> you will need to login"
|
642
461
|
end
|
462
|
+
|
463
|
+
require_ssl!
|
464
|
+
|
643
465
|
@nickname = nick;
|
644
466
|
render :signon_form
|
645
467
|
end
|
646
468
|
|
647
469
|
def post(n=nil)
|
470
|
+
|
648
471
|
begin
|
649
472
|
deny_throttled!
|
650
473
|
rescue Pasaporte::Secure::Throttled => th
|
@@ -654,11 +477,19 @@ module Pasaporte
|
|
654
477
|
end
|
655
478
|
raise th
|
656
479
|
end
|
480
|
+
|
481
|
+
require_ssl!
|
482
|
+
|
657
483
|
@nickname = @input.login || n || (raise "No nickname to authenticate")
|
484
|
+
|
658
485
|
# The throttling logic must be moved into throttles apparently
|
486
|
+
|
659
487
|
# Start counting
|
660
488
|
@state.failed_logins ||= 0
|
661
489
|
|
490
|
+
# Validate token
|
491
|
+
validate_token!
|
492
|
+
|
662
493
|
# If the user reaches the failed login limit we ban him for a while and
|
663
494
|
# tell the OpenID requesting party to go away
|
664
495
|
if Pasaporte::AUTH.call(@nickname, input.pass, my_domain)
|
@@ -726,8 +557,7 @@ module Pasaporte
|
|
726
557
|
class Signout < personal(:signout)
|
727
558
|
def get_with_nick
|
728
559
|
(redirect R(Signon, @nickname); return) unless is_logged_in?
|
729
|
-
|
730
|
-
@state = Camping::H.new
|
560
|
+
reset_session!
|
731
561
|
@state.msg = "Thanks for using the service and goodbye"
|
732
562
|
redirect R(Signon, @nickname)
|
733
563
|
end
|
@@ -737,13 +567,16 @@ module Pasaporte
|
|
737
567
|
# when associating for the first time
|
738
568
|
class Decide < personal(:decide)
|
739
569
|
def get_with_nick
|
740
|
-
|
570
|
+
require_ssl!
|
571
|
+
require_login!
|
572
|
+
|
741
573
|
@oid_request = @state.pending_openid
|
742
574
|
render :decide
|
743
575
|
end
|
744
576
|
|
745
577
|
def post_with_nick
|
746
|
-
|
578
|
+
require_ssl!
|
579
|
+
require_login!
|
747
580
|
if !@state.pending_openid
|
748
581
|
@report = "There is no OpenID request to approve anymore. Looks like it went out already."
|
749
582
|
render :bailout
|
@@ -760,12 +593,16 @@ module Pasaporte
|
|
760
593
|
# Allows the user to modify the settings
|
761
594
|
class EditProfile < personal(:edit)
|
762
595
|
def get_with_nick
|
763
|
-
require_login
|
596
|
+
require_login!
|
597
|
+
require_ssl!
|
764
598
|
render :profile_form
|
765
599
|
end
|
766
600
|
|
767
601
|
def post_with_nick
|
768
|
-
require_login
|
602
|
+
require_login!
|
603
|
+
require_ssl!
|
604
|
+
validate_token!
|
605
|
+
|
769
606
|
_collapse_checkbox_input(input)
|
770
607
|
|
771
608
|
if @profile.update_attributes(input.profile)
|
@@ -821,13 +658,32 @@ module Pasaporte
|
|
821
658
|
render :bailout
|
822
659
|
end
|
823
660
|
end
|
661
|
+
|
662
|
+
class FormExpired < R('/form-expired')
|
663
|
+
def get
|
664
|
+
@report = "The form has expired unfortunately"
|
665
|
+
render :bailout
|
666
|
+
end
|
667
|
+
end
|
824
668
|
|
825
669
|
# Just show a public profile page. Before the user logs in for the first time
|
826
670
|
# it works like a dummy page. After the user has been succesfully authenticated he can
|
827
671
|
# show his personal info on this page (this is his identity URL).
|
828
672
|
class ProfilePage < personal
|
673
|
+
|
674
|
+
def head(nick)
|
675
|
+
get(nick)
|
676
|
+
return
|
677
|
+
end
|
678
|
+
|
829
679
|
def get(nick)
|
830
680
|
@nickname = nick
|
681
|
+
|
682
|
+
# Redirect the OpenID requesting party to the usual HTTP so that
|
683
|
+
# the OpenID procedure takes place without SSL, if partial SSL is turned on
|
684
|
+
require_plain! if PARTIAL_SSL
|
685
|
+
|
686
|
+
LOGGER.info "Profile page GET for #{nick}, sending YADIS header"
|
831
687
|
@headers['X-XRDS-Location'] = _our_identity_url + '/yadis'
|
832
688
|
@title = "#{@nickname}'s profile"
|
833
689
|
@profile = Profile.find_by_nickname_and_domain_name(@nickname, my_domain)
|
@@ -837,7 +693,7 @@ module Pasaporte
|
|
837
693
|
end
|
838
694
|
|
839
695
|
class DashPage < personal(:prefs)
|
840
|
-
def get_with_nick; require_login
|
696
|
+
def get_with_nick; require_login!; render :dash; end
|
841
697
|
end
|
842
698
|
end
|
843
699
|
|
@@ -849,7 +705,8 @@ module Pasaporte
|
|
849
705
|
|
850
706
|
# Return a RELATIVELY reliable domain key
|
851
707
|
def my_domain
|
852
|
-
env["SERVER_NAME"].gsub(/^www\./i, '')
|
708
|
+
server = env["SERVER_NAME"].gsub(/^www\./i, '')
|
709
|
+
(server.mb_chars rescue server.chars).downcase.to_s
|
853
710
|
end
|
854
711
|
|
855
712
|
# Camping processes double values (hidden field with 0 and checkbox with 1) as an array.
|
@@ -859,15 +716,21 @@ module Pasaporte
|
|
859
716
|
(v == ["0", "1"]) ? (ha[k] = "1") : (v.is_a?(Hash) ? _collapse_checkbox_input(v) : true)
|
860
717
|
end
|
861
718
|
end
|
862
|
-
|
719
|
+
|
720
|
+
def _csrf_token
|
721
|
+
#input :name => :tok, :type => :hidden, :value => token_box.procure!(@env.REQUEST_URI)
|
722
|
+
#LOGGER.warn "After token procurement #{token_box.inspect}"
|
723
|
+
end
|
724
|
+
|
863
725
|
def openid_server
|
864
726
|
@store ||= PasaporteStore.new
|
865
727
|
|
866
728
|
# Associations etc are sharded per domain on which Pasaporte sits
|
867
729
|
@store.pasaporte_domain = @env['SERVER_NAME']
|
868
730
|
|
869
|
-
# we also need to provide endopint URL - this is where Pasaporte is mounted
|
870
|
-
|
731
|
+
# we also need to provide endopint URL - this is where Pasaporte is mounted.
|
732
|
+
# Op-endpoint is the endpoint used by the server
|
733
|
+
@server ||= OpenID::Server::Server.new(@store, _our_endpoint_uri)
|
871
734
|
@server
|
872
735
|
end
|
873
736
|
|
@@ -960,22 +823,25 @@ module Pasaporte
|
|
960
823
|
self << "Your password:"
|
961
824
|
input.pass! :name => "pass", :type => "password"
|
962
825
|
end
|
826
|
+
_csrf_token
|
963
827
|
input :type => :submit, :value => 'Log me in'
|
964
828
|
end
|
965
829
|
end
|
966
830
|
|
967
831
|
def layout
|
968
832
|
@headers['Cache-Control'] = 'no-cache; must-revalidate'
|
833
|
+
@headers['Content-Type'] ||= 'text/html'
|
834
|
+
|
969
835
|
if @skip_layout
|
970
836
|
self << yield; return
|
971
837
|
end
|
972
838
|
|
973
|
-
@headers['Content-type'] = 'text/html'
|
974
839
|
xhtml_transitional do
|
975
840
|
head do
|
976
|
-
meta
|
977
|
-
link :rel => "openid.server", :href => _openid_server_uri
|
841
|
+
self << '<meta http-equiv="X-XRDS-Location" content="%s/yadis" />' % _our_identity_url
|
842
|
+
link :rel => "openid.server", :href => _openid_server_uri
|
978
843
|
link :rel => "openid.delegate", :href => _openid_delegate_uri
|
844
|
+
|
979
845
|
link :rel => "stylesheet", :href => _s("pasaporte.css")
|
980
846
|
script :type=>'text/javascript', :src => _s("pasaporte.js")
|
981
847
|
title(@title || ('%s : pasaporte' % env['SERVER_NAME']))
|
@@ -1006,14 +872,14 @@ module Pasaporte
|
|
1006
872
|
R(Assets, file)
|
1007
873
|
end
|
1008
874
|
|
1009
|
-
# Render either our
|
875
|
+
# Render either our endpoint URL or the URL of the delegate
|
1010
876
|
def _openid_server_uri
|
1011
877
|
(@profile && @profile.delegates_openid?) ? @profile.openid_server : _our_endpoint_uri
|
1012
878
|
end
|
1013
879
|
|
1014
|
-
# Render either our
|
880
|
+
# Render either our identity URL or the URL of the delegate
|
1015
881
|
def _openid_delegate_uri
|
1016
|
-
(@profile && @profile.delegates_openid?) ? @profile.openid_delegate :
|
882
|
+
(@profile && @profile.delegates_openid?) ? @profile.openid_delegate : _our_identity_url
|
1017
883
|
end
|
1018
884
|
|
1019
885
|
# Canonicalized URL of our endpoint
|
@@ -1050,12 +916,13 @@ module Pasaporte
|
|
1050
916
|
end
|
1051
917
|
|
1052
918
|
form :method => :post do
|
919
|
+
_csrf_token
|
1053
920
|
input :name => :pleasedo, :type => :submit, :value => " Yes, do allow "
|
1054
921
|
self << ' '
|
1055
922
|
input :name => :nope, :type => :submit, :value => " No, they are villains! "
|
1056
923
|
end
|
1057
924
|
end
|
1058
|
-
|
925
|
+
|
1059
926
|
def _toolbar
|
1060
927
|
# my profile button
|
1061
928
|
# log me out button
|
@@ -1103,7 +970,7 @@ module Pasaporte
|
|
1103
970
|
form(:method => :post) do
|
1104
971
|
|
1105
972
|
h2 "Your profile"
|
1106
|
-
|
973
|
+
_csrf_token
|
1107
974
|
label.cblabel :for => :share_info do
|
1108
975
|
_cbox :profile, :shared, :id => :share_info
|
1109
976
|
self << '  Share your info on your OpenID page'
|
@@ -1206,9 +1073,10 @@ module Pasaporte
|
|
1206
1073
|
def self.create
|
1207
1074
|
JulikState.create_schema
|
1208
1075
|
self::Models.create_schema
|
1209
|
-
LOGGER.warn "Deleting
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1076
|
+
self::LOGGER.warn "Deleting sessions, assocs and nonces"
|
1077
|
+
[self::Models::Throttle, self::Models::Nonce,
|
1078
|
+
self::Models::Association, JulikState::State].each do | m |
|
1079
|
+
m.delete_all
|
1080
|
+
end
|
1213
1081
|
end
|
1214
1082
|
end
|