pasaporte 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ # Hacks that are needed to make everything work swell
2
+ # Start an XML tag. We override to get "<stupidbrowserfriendly />" fwd slash
3
+ class Builder::XmlMarkup
4
+ def _start_tag(sym, attrs, end_too=false)
5
+ @target << "<#{sym}"
6
+ _insert_attributes(attrs)
7
+ @target << " /" if end_too #HIER!!
8
+ @target << ">"
9
+ end
10
+ end
@@ -22,21 +22,25 @@ module JulikState
22
22
  def _appn; self.class.to_s.split(/::/).shift; end
23
23
 
24
24
  def force_session_save!
25
- @cookies.jsid ||= _sid
26
25
  res = @js_rec.update_attributes :blob => @state, :sid => @cookies.jsid
27
26
  raise "Cannot save session" unless res
28
27
  end
29
28
 
30
- def service(*a)
31
-
32
- fresh = Camping::H[{}]
33
- @js_rec = State.find_by_sid_and_app(@cookies.jsid, _appn) || State.new(:app => _appn, :blob => fresh,
34
- :sid => (@cookies.jsid ||= _sid))
35
-
29
+ def reset_session!
30
+ @cookies.jsid = _sid
31
+ @js_rec.destroy unless @js_rec.new_record?
32
+ initialize_session!
33
+ end
34
+
35
+ def initialize_session!(with = Camping::H[{}])
36
+ @js_rec = State.find_by_sid_and_app(@cookies.jsid, _appn) || State.new(
37
+ :app => _appn, :blob => with, :sid => (@cookies.jsid = _sid))
36
38
  @state = @js_rec.blob.dup
39
+ end
40
+
41
+ def service(*a)
42
+ initialize_session!
37
43
  @msg = @state.delete(:msg)
38
- returning(super(*a)) do
39
- force_session_save! if (@state != @js_rec.blob)
40
- end
44
+ returning(super(*a)) { force_session_save! }
41
45
  end
42
46
  end
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDZDCCAkygAwIBAgIBADANBgkqhkiG9w0BAQUFADAQMQ4wDAYDVQQDDAVQVGVz
3
+ dDAeFw0wODExMDYwMTIyMzlaFw0xMzExMDUwMTIyMzlaMBAxDjAMBgNVBAMMBVBU
4
+ ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvsRwETWeR2VEhRev
5
+ kd7UQNFYKAd0b219/Lbt0QWFeRPki6pxRDBWlq5ZvUTfJV6LlV3NDHRL4qSC4NDp
6
+ ks3EuyE15Y0pGXMcQQ7/2GeOAHIP94Civ3swa5kfWhvzQZlw+wJWDuEEc83RXgJS
7
+ yNm3wjO0FhoxPgYuSPSxsKCftq7ZgQjqDw1aCk+hpAwZblohyj+neRdfbdIBcYvK
8
+ IzxwaR1dgcSvR1YD4shc7Hh3240VrqcVNA1XV/hQFVH3bkI4f4cfMz6Xq7wD3NAp
9
+ Y45tA/n+k5oHy5rMTYPgW8eTrCJ8clvVYCtyD9gfs9KmuVLRyRnVQeOslN3fwB3X
10
+ UB2IIQIDAQABo4HIMIHFMA8GA1UdEwEB/wQFMAMBAf8wMQYJYIZIAYb4QgENBCQW
11
+ IlJ1YnkvT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFGqv
12
+ 1S7TNlJ/5Eps7UtIPC9k4+moMA4GA1UdDwEB/wQEAwIBBjAWBgNVHSUBAf8EDDAK
13
+ BggrBgEFBQcDATA4BgNVHSMEMTAvgBRqr9Uu0zZSf+RKbO1LSDwvZOPpqKEUpBIw
14
+ EDEOMAwGA1UEAwwFUFRlc3SCAQAwDQYJKoZIhvcNAQEFBQADggEBAIqa1uDqrRSk
15
+ jPLN6XBta6KjrSJ3gd/BSgVj4VTCRPP3AwLzTavSSUVS+U6Be4Xd9PC096gf0irH
16
+ iqViMJrytBW1alyTrjyhMVLsUPbQOrHOAbJvSthDsCx+pM5g9VibUnFC5V13fkxf
17
+ Byu5H/SJA2qlrYOHgrb1/6mOqn9upP27q1l+gpCFZKQutvFlwPkdIO5z28tM5bnZ
18
+ ZZTll4+19BquA+qAfr2hb5p9ksANTd5Qh1sDs+aOiPseLz0C7IZMycBewnFWc3B0
19
+ icStB2AaSeLm5bAGBQ2VjrrOD4kPSyX3xY+Rs3bi2oGY1hhzyT/NljVDCeyNTeDN
20
+ tdMa50hTagM=
21
+ -----END CERTIFICATE-----
@@ -0,0 +1,32 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICxzCCAa+gAwIBAgIBATANBgkqhkiG9w0BAQUFADAQMQ4wDAYDVQQDDAVQVGVz
3
+ dDAeFw0wODExMDYwMTIyNDBaFw0wOTExMDYwMTIyNDBaMCExEjAQBgNVBAMMCWxv
4
+ Y2FsaG9zdDELMAkGA1UECwwCQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB
5
+ AMtCjxQbJwktQFLJ+NOzUvsX1yEWPU7QdD0dmdDTflxyymUpbXyFKIdR5XPvVT7w
6
+ CxjHQXCTNle16DYrQbjKNVlK6voTC6NkZEUfPg/nTxwxb1ZUbdfvCEiEG2Tz75SQ
7
+ E9VPpUiB4Yk+NuEurs5gn2K/m9kUF5Pazb26x4sIY/itAgMBAAGjgZ4wgZswDAYD
8
+ VR0TAQH/BAIwADAxBglghkgBhvhCAQ0EJBYiUnVieS9PcGVuU1NMIEdlbmVyYXRl
9
+ ZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUyF7HBbbl960mFxCkaFO4c+szNOkwCwYD
10
+ VR0PBAQDAgOoMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBcGA1UdEQEB/wQNMAuCCWxv
11
+ Y2FsaG9zdDANBgkqhkiG9w0BAQUFAAOCAQEAiqBI77ifmbNnaOCY5hZH6V9XkrvH
12
+ tzEeDrgLLCGXCWgIKWkjeNZQDCfRbyu7rPxUav87+WQWSndcSnFsWVOcxYDmPq2N
13
+ aXHfzIU51sEY57Ie9/pzZgsxs6Hhrky81NyzepG9B76ZzJOrYToJxywQmh3NThD9
14
+ tfywNXGnoiO55DZJfg7w4cOH4VnxkpqLMT8AxOpSlXCD4p57AutXSGLUOfzurU/Q
15
+ npqoQcp9m/eYUKIDzS9TyA6scQ8akKCuMuMuX4HQOJl17y6d5+CrNhfvq1nYkj0M
16
+ LWBzK5Uqf/O/R/bbMKMlgduuxSB2jfBtgl5C+hhQfDKtqf5F5S4csebPwA==
17
+ -----END CERTIFICATE-----
18
+ -----BEGIN RSA PRIVATE KEY-----
19
+ MIICXQIBAAKBgQDLQo8UGycJLUBSyfjTs1L7F9chFj1O0HQ9HZnQ035ccsplKW18
20
+ hSiHUeVz71U+8AsYx0FwkzZXteg2K0G4yjVZSur6EwujZGRFHz4P508cMW9WVG3X
21
+ 7whIhBtk8++UkBPVT6VIgeGJPjbhLq7OYJ9iv5vZFBeT2s29useLCGP4rQIDAQAB
22
+ AoGAP2sk+UD/jP1xdGNQH71zxqRJmyk1N8ISgn8Z3u4eHvox7B5g6tkhLBeBYArs
23
+ rhZ3X+PLpzRHYFaBfWVBvEZbHlH5vIFEB7hVtomr5yhv8oVi+IY6ynAp6qFh47JY
24
+ QcqNm+PKDVOAIPyUafMjFCAJljngIup3f0r/Tuj/9jhaVIECQQD9xkj52bG2GelP
25
+ Jtqa6mmI7LaI5xX3Bi3M3AswFlj3ICI5ZSTiLI5/5PjzBAbu9F1TwQLqH6g3lkrK
26
+ KREGlmnTAkEAzQre1RT8ixFIlyvUfi6tLtn8oARYd42WXibZT7e6j4t0xaZ8MV3J
27
+ gfVH413EmcJhHw4oKNYlS+XMLO1o4FgDfwJBAKnPHZO5/HUan4hsOkkA4/9QTdAL
28
+ uSHjS5BSCVZzDbLHGL+JE4YYRH4F7CNIpY8Nisl5VIbvCfOwKHlfw1nCGisCQCJF
29
+ cN1YtqVf7CwoTUoR7yxnjwwH7el9puZxw9zJLsuTWZ83poZx0J6CKtPb9mJk1Orl
30
+ 6Nx6fp1i+W+A9wiYbW0CQQC92IXy4rgz5gaoXCGCUZ3uQVyysvuUcb0LBPVezNhB
31
+ aOsMqthVtaddDMm6rshkFN+8GXrottZWJgStRxFLv9mk
32
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,27 @@
1
+ # This is a sample config for running Pasaporte in Partial SSL mode.
2
+ # It runs on non-privileged ports.
3
+ server.bind = "0.0.0.0"
4
+ server.port = 9050
5
+ server.pid-file = CWD + "/lighttpd.pid"
6
+ server.modules = ( "mod_fastcgi", "mod_redirect" )
7
+ index-file.names = ("index.html")
8
+ server.document-root = CWD
9
+
10
+ fastcgi.server = ( "" => (
11
+ "localhost" => (
12
+ "socket" => "/tmp/camping-pasaporte.socket",
13
+ "bin-path" => CWD +"/../../../bin/pasaporte-fcgi.rb",
14
+ "bin-environment" => ("FORCE_ROOT" => "1",
15
+ "PASAPORTE_PARTIAL_SSL" => "1",
16
+ "PASAPORTE_SSL_PORT" => "9051",
17
+ "PASAPORTE_HTTP_PORT" => "9050"
18
+ ),
19
+ "allow-x-send-file" => "enable",
20
+ "check-local" => "disable",
21
+ "max-procs" => 1 ) ) )
22
+
23
+ $SERVER["socket"] == ":9051" {
24
+ ssl.engine = "enable"
25
+ ssl.ca-file = CWD + "/cacert.pem"
26
+ ssl.pemfile = CWD + "/cert_localhost_combined.pem"
27
+ }
@@ -0,0 +1,254 @@
1
+ module Pasaporte::Models
2
+ MAX = :limit # Thank you rails core, it was MAX before
3
+ class CreatePasaporte < V 1.0
4
+ def self.up
5
+ create_table :pasaporte_profiles, :force => true do |t|
6
+ # http://openid.net/specs/openid-simple-registration-extension-1_0.html
7
+ t.column :nickname, :string, MAX => 20
8
+ t.column :email, :string, MAX => 70
9
+ t.column :fullname, :string, MAX => 50
10
+ t.column :dob, :date, :null => true
11
+ t.column :gender, :string, MAX => 1
12
+ t.column :postcode, :string, MAX => 10
13
+ t.column :country, :string, MAX => 2
14
+ t.column :language, :string, MAX => 5
15
+ t.column :timezone, :string, MAX => 50
16
+
17
+ # And our extensions
18
+ # is the profile shared (visible to others)
19
+ t.column :shared, :boolean, :default => false
20
+
21
+ # his bio
22
+ t.column :info, :text
23
+
24
+ # when he last used Pasaporte
25
+ t.column :last_login, :datetime
26
+
27
+ # the encryption part that we generate for every user, the other is the pass
28
+ # the total encryption key for private data will be stored in the session only when
29
+ # the user is logged in
30
+ t.column :secret_salt, :integer
31
+
32
+ # Good servers delegate
33
+ t.column :openid_server, :string
34
+ t.column :openid_delegate, :string
35
+
36
+ # We shard by domain
37
+ t.column :domain_name, :string, :null => false, :default => 'localhost'
38
+
39
+ # Keep a close watch on those who
40
+ t.column :throttle_count, :integer, :default => 0
41
+ t.column :suspicious, :boolean, :default => false
42
+ end
43
+
44
+ add_index(:pasaporte_profiles, [:nickname, :domain_name], :unique)
45
+
46
+ create_table :pasaporte_settings do |t|
47
+ t.column :setting, :string
48
+ t.column :value, :binary
49
+ end
50
+
51
+ create_table :pasaporte_associations do |t|
52
+ # server_url is blob, because URLs could be longer
53
+ # than db can handle as a string
54
+ t.column :server_url, :binary
55
+ t.column :handle, :string
56
+ t.column :secret, :binary
57
+ t.column :issued, :integer
58
+ t.column :lifetime, :integer
59
+ t.column :assoc_type, :string
60
+ end
61
+
62
+ create_table :pasaporte_nonces do |t|
63
+ t.column :nonce, :string
64
+ t.column :created, :integer
65
+ end
66
+
67
+ create_table :pasaporte_throttles do |t|
68
+ t.column :created_at, :datetime
69
+ t.column :client_fingerprint, :string, MAX => 40
70
+ end
71
+ end
72
+
73
+ def self.down
74
+ drop_table :pasaporte_profiles
75
+ drop_table :pasaporte_settings
76
+ drop_table :pasaporte_associations
77
+ drop_table :pasaporte_nonces
78
+ drop_table :pasaporte_throttles
79
+ end
80
+ end
81
+ class AddAprovals < V(1.1)
82
+ def self.up
83
+ create_table :pasaporte_approvals do | t |
84
+ t.column :profile_id, :integer, :null => false
85
+ t.column :trust_root, :string, :null => false
86
+ end
87
+ add_index(:pasaporte_approvals, [:profile_id, :trust_root], :unique)
88
+ end
89
+
90
+ def self.down
91
+ drop_table :pasaporte_approvals
92
+ end
93
+ end
94
+
95
+ class MigrateOpenidTables < V(1.2)
96
+ def self.up
97
+ drop_table :pasaporte_settings
98
+ drop_table :pasaporte_nonces
99
+ create_table :pasaporte_nonces, :force => true do |t|
100
+ t.column :server_url, :string, :null => false
101
+ t.column :timestamp, :integer, :null => false
102
+ t.column :salt, :string, :null => false
103
+ end
104
+ end
105
+
106
+ def self.down
107
+ drop_table :pasaporte_nonces
108
+ create_table :pasaporte_nonces, :force => true do |t|
109
+ t.column "nonce", :string
110
+ t.column "created", :integer
111
+ end
112
+
113
+ create_table :pasaporte_settings, :force => true do |t|
114
+ t.column "setting", :string
115
+ t.column "value", :binary
116
+ end
117
+ end
118
+ end
119
+
120
+ class ShardOpenidTables < V(1.3)
121
+ def self.up
122
+ add_column :pasaporte_associations, :pasaporte_domain, :string, :null => false, :default => 'localhost'
123
+ add_column :pasaporte_nonces, :pasaporte_domain, :string, :null => false, :default => 'localhost'
124
+ end
125
+
126
+ def self.down
127
+ remove_column :pasaporte_nonces, :pasaporte_domain
128
+ remove_column :pasaporte_associations, :pasaporte_domain
129
+ end
130
+ end
131
+
132
+ # Minimal info we store about people. It's the container for the sreg data
133
+ # in the first place.
134
+ class Profile < Base
135
+ before_create { |p| p.secret_salt = rand(Time.now) }
136
+ before_save :validate_delegate_uris
137
+ validates_presence_of :nickname
138
+ validates_presence_of :domain_name
139
+ validates_uniqueness_of :nickname, :scope => :domain_name
140
+ attr_protected :domain_name, :nickname
141
+ has_many :approvals, :dependent => :delete_all
142
+
143
+ any_url_present = lambda do |r|
144
+ !r.openid_server.blank? || !r.openid_server.blank?
145
+ end
146
+ %w(openid_server openid_delegate).map do | c |
147
+ validates_presence_of c, :if => any_url_present
148
+ end
149
+
150
+ # Convert the profile to sreg according to the spec (YYYY-MM-DD for dob and such)
151
+ def to_sreg_fields(fields_to_extract = nil)
152
+ fields_to_extract ||= %w( nickname email fullname dob gender postcode country language timezone )
153
+ fields_to_extract.inject({}) do | out, field |
154
+ v = self[field]
155
+ v.blank? ? out : (out[field.to_s] = v.to_s; out)
156
+ end
157
+ end
158
+
159
+ # We have to override that because we want our protected attributes
160
+ def self.find_or_create_by_nickname_and_domain_name(nick, domain)
161
+ returning(super(nick, domain)) do | me |
162
+ ((me.nickname, me.domain_name = nick, domain) && me.save) if me.new_record?
163
+ end
164
+ end
165
+ class << self
166
+ alias_method :find_or_create_by_domain_name_and_nickname,
167
+ :find_or_create_by_nickname_and_domain_name
168
+ end
169
+
170
+ def generate_sess_key
171
+ self.secret_salt ||= rand(Time.now)
172
+ s = [nickname, secret_salt, Time.now.year, Time.now.month].join('|')
173
+ OpenSSL::Digest::SHA1.new(s).hexdigest.to_s
174
+ end
175
+
176
+ # Check if this profile wants us to delegate his openid to a different identity provider.
177
+ # If both delegate and server are filled in properly this will return true
178
+ def delegates_openid?
179
+ Pasaporte::ALLOW_DELEGATION && (!openid_server.blank? && !openid_delegate.blank?)
180
+ end
181
+
182
+ def delegates_openid=(nv)
183
+ (self.openid_server, self.openid_delegate = nil, nil) if [false, '0', 0, 'no'].include?(nv)
184
+ end
185
+ alias_method :delegates_openid, :delegates_openid? # for checkboxes
186
+
187
+ def to_s; nickname; end
188
+
189
+ private
190
+ def validate_delegate_uris
191
+ if ([self.openid_server, self.openid_delegate].select{|i| i.blank?}).length == 1
192
+ errors.add(:delegate_server, "If you use delegation you have to specify both addresses")
193
+ false
194
+ end
195
+
196
+ %w(openid_server openid_delegate).map do | attr |
197
+ return if self[attr].blank?
198
+ begin
199
+ self[attr] = OpenID::URINorm.urinorm(self[attr])
200
+ rescue Exception => e
201
+ errors.add(attr, e.message)
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ # A token that the user has approved a site (a site's trust root) as legal
208
+ # recipient of his information
209
+ class Approval < Base
210
+ belongs_to :profile
211
+ validates_presence_of :profile_id, :trust_root
212
+ validates_uniqueness_of :trust_root, :scope => :profile_id
213
+ def to_s; trust_root; end
214
+ end
215
+
216
+ # Openid setting
217
+ class Setting < Base; end
218
+
219
+ # Openid nonces
220
+ class Nonce < Base; end
221
+
222
+ # Openid assocs
223
+ class Association < Base
224
+ def from_record
225
+ OpenID::Association.new(handle, secret, issued, lifetime, assoc_type)
226
+ end
227
+
228
+ def expired?
229
+ Time.now.to_i > (issued + lifetime)
230
+ end
231
+ end
232
+
233
+ # Set throttles
234
+ class Throttle < Base
235
+
236
+ # Set a throttle with the environment of the request
237
+ def self.set!(e)
238
+ create(:client_fingerprint => env_hash(e))
239
+ end
240
+
241
+ # Check if an environment is throttled
242
+ def self.throttled?(e)
243
+ prune!
244
+ count(:conditions => ["client_fingerprint = ? AND created_at > ?", env_hash(e), cutoff]) > 0
245
+ end
246
+
247
+ private
248
+ def self.prune!; delete_all "created_at < '#{cutoff.to_s(:db)}'"; end
249
+ def self.cutoff; Time.now - Pasaporte::THROTTLE_FOR; end
250
+ def self.env_hash(e)
251
+ OpenSSL::Digest::SHA1.new([e['REMOTE_ADDR'], e['HTTP_USER_AGENT']].map(&:to_s).join('|')).to_s
252
+ end
253
+ end
254
+ end # Pasaporte::Models
@@ -0,0 +1,43 @@
1
+ # A simple but effective CSRF protector
2
+ class TokenBox
3
+ class Invalid < RuntimeError; end
4
+ MAX_TOKENS, TOKEN_SIZE = 2, 64
5
+ CHARS = [*'A'..'Z'] + [*'0'..'9'] + [*'a'..'z']
6
+ WINDOW = 10.minutes # Gone in 60 seconds
7
+
8
+ class Token
9
+ attr_reader :token
10
+ alias_method :to_s, :token
11
+
12
+ def initialize(lifetime)
13
+ @will_expire = Time.now.utc + lifetime
14
+ @token = (0...TOKEN_SIZE).inject("") { |ret,_| ret << CHARS[rand(CHARS.length)] }
15
+ end
16
+
17
+ def expired?
18
+ @will_expire < Time.now.utc
19
+ end
20
+
21
+ def inspect
22
+ "#{@token}:#{'exp' if expired?}"
23
+ end
24
+ end
25
+
26
+ # Procure a CSRF token for a specific request URI
27
+ def procure!(request, lifetime = nil)
28
+ returning(Token.new(lifetime || WINDOW)) do | t |
29
+ @heap ||= {}
30
+ @heap[request] ||= []
31
+ @heap[request].shift if @heap[request].length >= MAX_TOKENS
32
+ @heap[request] << t
33
+ end.to_s
34
+ end
35
+
36
+ # Validate the token for a specific request URI
37
+ def validate!(request, token)
38
+ raise Invalid.new("no heap part") unless (@heap && @heap[request])
39
+ @heap[request].reject!{|t| t.expired? }
40
+ raise Invalid.new("no token found in heap") unless @heap[request].find{|e| e.to_s == token}
41
+ @heap[request].reject!{|e| e.to_s == token }
42
+ end
43
+ end
@@ -8,10 +8,13 @@ require File.dirname(__FILE__) + '/mosquito'
8
8
  require 'flexmock'
9
9
  require 'flexmock/test_unit'
10
10
 
11
+ Markaby::Builder.set(:indent, nil)
12
+
11
13
  # for assert_select and friends
12
14
  require 'action_controller'
13
15
  require 'action_controller/assertions'
14
16
 
17
+
15
18
  class Pasaporte::Controllers::ServerError
16
19
  def get(*all)
17
20
  raise all.pop
@@ -63,4 +66,7 @@ class Pasaporte::WebTest < Camping::WebTest
63
66
  @html_document = HTML::Document.new(@response.body.to_s) unless @response.headers["Location"]
64
67
  end
65
68
  end
66
- end
69
+ end
70
+
71
+ # Requires Pasaporte:WebTest to be defined already
72
+ require File.dirname(__FILE__) + '/testable_openid_fetcher'