strelka 0.0.1.pre148 → 0.0.1.pre177
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +1294 -0
- data/IDEAS.rdoc +6 -0
- data/Manifest.txt +20 -0
- data/README.rdoc +8 -2
- data/Rakefile +9 -7
- data/examples/auth-demo.rb +32 -0
- data/examples/auth-demo2.rb +37 -0
- data/examples/auth-form.tmpl +10 -0
- data/examples/auth-success.tmpl +3 -0
- data/examples/config.yml +12 -0
- data/examples/examples.css +4 -0
- data/examples/examples.html +31 -0
- data/examples/gen-config.rb +5 -2
- data/examples/layout.tmpl +31 -0
- data/lib/strelka/app/auth.rb +480 -0
- data/lib/strelka/app/sessions.rb +8 -6
- data/lib/strelka/app/templating.rb +78 -17
- data/lib/strelka/app.rb +13 -5
- data/lib/strelka/authprovider/basic.rb +134 -0
- data/lib/strelka/authprovider/hostaccess.rb +91 -0
- data/lib/strelka/authprovider.rb +122 -0
- data/lib/strelka/cookie.rb +1 -1
- data/lib/strelka/cookieset.rb +1 -1
- data/lib/strelka/httprequest/auth.rb +31 -0
- data/lib/strelka/logging.rb +69 -14
- data/lib/strelka/mixins.rb +35 -65
- data/lib/strelka/session/db.rb +115 -0
- data/lib/strelka/session/default.rb +38 -49
- data/lib/strelka/session.rb +1 -1
- data/lib/strelka.rb +4 -1
- data/spec/lib/helpers.rb +8 -3
- data/spec/strelka/app/auth_spec.rb +367 -0
- data/spec/strelka/authprovider/basic_spec.rb +192 -0
- data/spec/strelka/authprovider/hostaccess_spec.rb +70 -0
- data/spec/strelka/authprovider_spec.rb +99 -0
- data/spec/strelka/cookie_spec.rb +1 -1
- data/spec/strelka/httprequest/auth_spec.rb +55 -0
- data/spec/strelka/httprequest/session_spec.rb +63 -3
- data/spec/strelka/session/db_spec.rb +85 -0
- data/spec/strelka/session/default_spec.rb +5 -51
- data.tar.gz.sig +0 -0
- metadata +88 -57
- metadata.gz.sig +0 -0
@@ -0,0 +1,480 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# vim: set nosta noet ts=4 sw=4:
|
3
|
+
#encoding: utf-8
|
4
|
+
|
5
|
+
require 'strelka' unless defined?( Strelka )
|
6
|
+
require 'strelka/app' unless defined?( Strelka::App )
|
7
|
+
|
8
|
+
require 'strelka/httprequest/auth'
|
9
|
+
require 'strelka/authprovider'
|
10
|
+
|
11
|
+
|
12
|
+
# Pluggable authentication and authorization for Strelka applications.
|
13
|
+
#
|
14
|
+
# Enabling the +:auth+ plugin by default causes all requests to your
|
15
|
+
# handler to go through an authentication and authorization provider
|
16
|
+
# first. This provider checks the request for the necessary credentials,
|
17
|
+
# then either forwards it on if sufficient conditions are met, or
|
18
|
+
# responds with the appropriate 4xx status response.
|
19
|
+
#
|
20
|
+
# The conditions are broken down into two stages:
|
21
|
+
#
|
22
|
+
# * Authentication -- the client is who they say they are
|
23
|
+
# * Authorization -- the client is allowed to access the resources in question
|
24
|
+
#
|
25
|
+
# Auth providers are plugins that are named <tt>Strelka::AuthProvider::<MyType></tt>, and
|
26
|
+
# inherit from Strelka::AuthProvider. In order for them to be discoverable, each one
|
27
|
+
# should be in a file named <tt>lib/strelka/authprovider/<mytype>.rb</tt>. They
|
28
|
+
# can implement one or both of the stages; see the API docs for Strelka::AuthProvider
|
29
|
+
# for details on how to write your own plugin.
|
30
|
+
#
|
31
|
+
#
|
32
|
+
# == Applying Authentication
|
33
|
+
#
|
34
|
+
# The default authentication policy is to require authentification from every
|
35
|
+
# request, but sometimes you may wish to narrow the restrictions a bit.
|
36
|
+
#
|
37
|
+
# === Relaxing \Auth for A Few Methods
|
38
|
+
#
|
39
|
+
# Sometimes you want to expose just one or two resources to the world, say in the
|
40
|
+
# case of a REST API that includes the authentication endpoint. Obviously, clients
|
41
|
+
# can't be authenticated until after they send their request that authenticates
|
42
|
+
# them, so you can expose just the +/login+ URI by using the 'no_auth_for' directive:
|
43
|
+
#
|
44
|
+
# class MyService < Strelka::App
|
45
|
+
# plugins :auth
|
46
|
+
# no_auth_for '/login'
|
47
|
+
#
|
48
|
+
# # ...
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# A String or a Regexp argument will be used to match against the request's
|
52
|
+
# {#app_path}[rdoc-ref:Strelka::HTTPRequest#app_path] (the path of the request
|
53
|
+
# URI with the Mongrel2 route omitted), and any requests which match are sent
|
54
|
+
# along as-is.
|
55
|
+
#
|
56
|
+
# If you require some more-complex criteria for determining if the request should
|
57
|
+
# skip the auth plugin, you can provide a block to +no_auth_for+ instead.
|
58
|
+
#
|
59
|
+
# # Allow requests from 'localhost' without auth, but require it from
|
60
|
+
# # everywhere else
|
61
|
+
# no_auth_for do |request|
|
62
|
+
# return 'internal-user' if request.header.x_forwarded_for == '127.0.0.1'
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# If the block returns a true-ish value, it will be used in the place of the
|
66
|
+
# authenticated username and the request will be handed to your app.
|
67
|
+
#
|
68
|
+
# Returning a false-ish value will go ahead with the rest of the auth
|
69
|
+
# processing.
|
70
|
+
#
|
71
|
+
# You can also combine String and Regexp arguments with a block to further refine
|
72
|
+
# the conditions:
|
73
|
+
#
|
74
|
+
# # Allow people to visit the seminar registration view without an account
|
75
|
+
# # if there are still slots open
|
76
|
+
# no_auth_for( '/register' ) do |request|
|
77
|
+
# if Seminars.any? {|seminar| !seminar.full? }
|
78
|
+
# 'register'
|
79
|
+
# else
|
80
|
+
#
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
#
|
85
|
+
# === Relaxing \Auth for All But a Few Methods
|
86
|
+
#
|
87
|
+
# Sometimes, though, you want just the opposite -- a few methods are available
|
88
|
+
# only to a select few, but the majority are unrestricted.
|
89
|
+
#
|
90
|
+
# To do this, use the 'require_auth_for' directive:
|
91
|
+
#
|
92
|
+
# class MyBlog < Strelka::App
|
93
|
+
# plugins :auth
|
94
|
+
# require_auth_for '/admin'
|
95
|
+
#
|
96
|
+
# # ...
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# Note that this inverts the usual behavior of the +:auth+ plugin: resources
|
100
|
+
# will, by default, be unguarded, so be sure you keep this in mind when
|
101
|
+
# using +require_auth_for+.
|
102
|
+
#
|
103
|
+
# Like +no_auth_for+, +require_auth_for+ can also take a block, and
|
104
|
+
# a true-ish return value will cause the request to pass through the
|
105
|
+
# AuthProvider.
|
106
|
+
#
|
107
|
+
# You can't use +no_auth_for+ and +require_auth_for+ in the same application; doing
|
108
|
+
# so will result in a ScriptError being raised when the application is loaded.
|
109
|
+
#
|
110
|
+
#
|
111
|
+
# == Adding Authorization
|
112
|
+
#
|
113
|
+
# Sometimes simple authentication isn't sufficient for accessing some
|
114
|
+
# resources, especially if you have some kind of permissions system that
|
115
|
+
# dictates who can see/use what. That's where the second stage of the
|
116
|
+
# authentification process comes into play: Authorization.
|
117
|
+
#
|
118
|
+
# The AuthProvider you're using may provide some form of general authorization
|
119
|
+
# itself (especially a custom one), but typically authorization is particular to an application and
|
120
|
+
# even particular actions within the application.
|
121
|
+
#
|
122
|
+
# To provide the particulars for your app's authorization, you can declare
|
123
|
+
# a block with the +authz_callback+ directive.
|
124
|
+
#
|
125
|
+
# authz_callback do |request, user, *other_auth_info|
|
126
|
+
# user.can?( request.app_path )
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# The block will be called once authentication has succeeded, and any general authorization has been
|
130
|
+
# checked. It will be called with at least the credentials object returned from the authentication
|
131
|
+
# stage and the request object. Some AuthProviders may opt to return authentication credentials
|
132
|
+
# as a User object of some kind (e.g., a database row, LDAP entry, model object, etc.), but the
|
133
|
+
# simpler ones just return the login of the authenticated +user+. The AuthProvider may also
|
134
|
+
# furnish additional useful arguments such as a database handle, permission objects, etc. to your
|
135
|
+
# authorization block. See the documentation for your chosen AuthProvider for details.
|
136
|
+
#
|
137
|
+
# == Customizing Failure
|
138
|
+
#
|
139
|
+
# As mentioned before, an authentication or authorization failure results in a
|
140
|
+
# 4xx status response. By default Strelka will present this back to the
|
141
|
+
# browser as a simple error response, but oftentimes you will want to customize
|
142
|
+
# it to look a little nicer, or to behave in a more-intuitive way.
|
143
|
+
# The easiest way to do this is to use the {:errors}[rdoc-ref:Strelka::App::Errors]
|
144
|
+
# plugin.
|
145
|
+
#
|
146
|
+
# === Redirecting to a Form
|
147
|
+
#
|
148
|
+
# If you're using form-based session authentication (as opposed to basic
|
149
|
+
# auth, which has its own UI), you can rewrite the response to instruct
|
150
|
+
# the browser to go to a static HTML form instead:
|
151
|
+
#
|
152
|
+
# class FormAuthApp < Strelka::App
|
153
|
+
# plugins :errors, :auth, :sessions
|
154
|
+
# auth_provider :session
|
155
|
+
#
|
156
|
+
# on_status HTTP::AUTH_REQUIRED do |res, status|
|
157
|
+
# formuri = res.request.uri
|
158
|
+
# formuri.path = '/loginform.html'
|
159
|
+
#
|
160
|
+
# res.reset
|
161
|
+
# res.status = HTTP::SEE_OTHER
|
162
|
+
# res.content_type = 'text/plain'
|
163
|
+
# res.puts "This resource requires authentication."
|
164
|
+
# res.header.location = formuri
|
165
|
+
#
|
166
|
+
# return res
|
167
|
+
# end
|
168
|
+
# end
|
169
|
+
#
|
170
|
+
# === Responding With a Form
|
171
|
+
#
|
172
|
+
# With the addition of the {:templating}[rdoc-ref:Strelka::App::Templating] plugin,
|
173
|
+
# you can respond with the form directly instead:
|
174
|
+
#
|
175
|
+
# class TemplateFormAuthApp < Strelka::App
|
176
|
+
# plugins :auth, :errors, :templating
|
177
|
+
# auth_provider :session
|
178
|
+
#
|
179
|
+
# layout 'examples/layout.tmpl'
|
180
|
+
# templates \
|
181
|
+
# form: 'examples/auth-form.tmpl',
|
182
|
+
# success: 'examples/auth-success.tmpl'
|
183
|
+
#
|
184
|
+
# on_status HTTP::AUTH_REQUIRED, :form
|
185
|
+
#
|
186
|
+
# ### Handle any (authenticated) HTTP request
|
187
|
+
# def handle_request( req )
|
188
|
+
# return :success
|
189
|
+
# end
|
190
|
+
#
|
191
|
+
# end
|
192
|
+
#
|
193
|
+
# == Examples
|
194
|
+
#
|
195
|
+
# Here are a few more examples using a few different AuthProviders.
|
196
|
+
#
|
197
|
+
# # Guard every request the app does behind a simple passphrase
|
198
|
+
# class MyGuardedApp < Strelka::App
|
199
|
+
# plugins :auth
|
200
|
+
#
|
201
|
+
# auth_provider :passphrase
|
202
|
+
# end
|
203
|
+
#
|
204
|
+
# # Require LDAP authentication for one route
|
205
|
+
# class MyGuardedApp < Strelka::App
|
206
|
+
# plugins :auth, :routing
|
207
|
+
#
|
208
|
+
# auth_provider :ldap
|
209
|
+
# authz_callback do |user, request, directory|
|
210
|
+
# authgroup = directory.ou( :appperms ).cn( :guarded_app )
|
211
|
+
# authgroup.members.include?( user.dn )
|
212
|
+
# end
|
213
|
+
#
|
214
|
+
# authenticated %r{^/admin}
|
215
|
+
# end
|
216
|
+
#
|
217
|
+
# # Use a user table in a PostgreSQL database for authentication for
|
218
|
+
# # all routes except one
|
219
|
+
# class MyGuardedApp < Strelka::App
|
220
|
+
# plugins :auth
|
221
|
+
#
|
222
|
+
# auth_provider :sequel
|
223
|
+
# authz_callback do |user, request, db|
|
224
|
+
# db[:permissions].filter( :user_id => user[:id] ).
|
225
|
+
# filter( :permname => 'guardedapp' )
|
226
|
+
# end
|
227
|
+
#
|
228
|
+
# unauthenticated %r{^/auth}
|
229
|
+
#
|
230
|
+
# # Only authenticated users can use this
|
231
|
+
# post '/servers' do
|
232
|
+
# # ...
|
233
|
+
# end
|
234
|
+
# end
|
235
|
+
#
|
236
|
+
module Strelka::App::Auth
|
237
|
+
extend Strelka::App::Plugin,
|
238
|
+
Strelka::MethodUtilities,
|
239
|
+
Configurability
|
240
|
+
include Strelka::Loggable,
|
241
|
+
Strelka::Constants
|
242
|
+
|
243
|
+
run_before :routing, :restresources
|
244
|
+
run_after :templating, :errors, :sessions
|
245
|
+
|
246
|
+
|
247
|
+
# The name of the default plugin to use for authentication
|
248
|
+
DEFAULT_AUTH_PROVIDER = :hostaccess
|
249
|
+
|
250
|
+
|
251
|
+
# Class methods to add to app classes that enable Auth
|
252
|
+
module ClassMethods
|
253
|
+
|
254
|
+
@auth_provider = nil
|
255
|
+
@authz_callback = nil
|
256
|
+
@positive_auth_criteria = {}
|
257
|
+
@negative_auth_criteria = {}
|
258
|
+
|
259
|
+
##
|
260
|
+
# Arrays of criteria for applying and skipping auth for a request.
|
261
|
+
attr_reader :positive_auth_criteria, :negative_auth_criteria
|
262
|
+
|
263
|
+
|
264
|
+
### Get/set the authentication type.
|
265
|
+
def auth_provider( type=nil )
|
266
|
+
if type
|
267
|
+
@auth_provider = Strelka::AuthProvider.get_subclass( type )
|
268
|
+
elsif type.nil?
|
269
|
+
@auth_provider ||= Strelka::AuthProvider.get_subclass( DEFAULT_AUTH_PROVIDER )
|
270
|
+
end
|
271
|
+
|
272
|
+
return @auth_provider
|
273
|
+
end
|
274
|
+
|
275
|
+
|
276
|
+
### Register a function to call after the user successfully authenticates to check
|
277
|
+
### for authorization or other criteria. The arguments to the function depend on
|
278
|
+
### which authentication plugin is used. Returning +true+ from this function will
|
279
|
+
### cause authorization to succeed, while returning a false value causes it to fail
|
280
|
+
### with a FORBIDDEN response. If no callback is set, and the provider doesn't
|
281
|
+
### provide authorization
|
282
|
+
def authz_callback( callable=nil, &block )
|
283
|
+
if callable
|
284
|
+
@authz_callback = callable
|
285
|
+
elsif block
|
286
|
+
@authz_callback = block
|
287
|
+
end
|
288
|
+
|
289
|
+
return @authz_callback
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
### Returns +true+ if there are any criteria for determining whether or
|
294
|
+
### not a request needs auth.
|
295
|
+
def has_auth_criteria?
|
296
|
+
return self.has_positive_auth_criteria? || self.has_negative_auth_criteria?
|
297
|
+
end
|
298
|
+
|
299
|
+
|
300
|
+
### Returns +true+ if the app has been set up so that only some methods
|
301
|
+
### require auth.
|
302
|
+
def has_positive_auth_criteria?
|
303
|
+
return !self.positive_auth_criteria.empty?
|
304
|
+
end
|
305
|
+
|
306
|
+
|
307
|
+
### Returns +true+ if the app has been set up so that all methods but
|
308
|
+
### ones that match declared criteria require auth.
|
309
|
+
def has_negative_auth_criteria?
|
310
|
+
return !self.negative_auth_criteria.empty?
|
311
|
+
end
|
312
|
+
|
313
|
+
|
314
|
+
### Constrain auth to apply only to requests which match the given +criteria+,
|
315
|
+
### and/or the given +block+. The +criteria+ are either Strings or Regexps
|
316
|
+
### which are tested against
|
317
|
+
### {the request's #app_path}[rdoc-ref:Strelka::HTTPRequest#app_path]. The block
|
318
|
+
### should return a true-ish value if the request should undergo authentication
|
319
|
+
### and authorization.
|
320
|
+
### *NOTE:* using this declaration inverts the default security policy of
|
321
|
+
### restricting access to all requests.
|
322
|
+
def require_auth_for( *criteria, &block )
|
323
|
+
if self.has_negative_auth_criteria?
|
324
|
+
raise ScriptError,
|
325
|
+
"defining both positive and negative auth criteria is unsupported."
|
326
|
+
end
|
327
|
+
|
328
|
+
criteria << '' if criteria.empty?
|
329
|
+
block ||= Proc.new { true }
|
330
|
+
|
331
|
+
criteria.each do |pattern|
|
332
|
+
self.positive_auth_criteria[ pattern ] = block
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
|
337
|
+
### Contrain auth to apply to all requests *except* those that match the
|
338
|
+
### given +criteria+.
|
339
|
+
def no_auth_for( *criteria, &block )
|
340
|
+
if self.has_positive_auth_criteria?
|
341
|
+
raise ScriptError,
|
342
|
+
"defining both positive and negative auth criteria is unsupported."
|
343
|
+
end
|
344
|
+
|
345
|
+
criteria << '' if criteria.empty?
|
346
|
+
block ||= Proc.new { true }
|
347
|
+
|
348
|
+
criteria.each do |pattern|
|
349
|
+
self.negative_auth_criteria[ pattern ] = block
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
|
354
|
+
end # module ClassMethods
|
355
|
+
|
356
|
+
|
357
|
+
### Extension callback -- extend the HTTPRequest class with Auth
|
358
|
+
### support when this plugin is loaded.
|
359
|
+
def self::included( object )
|
360
|
+
Strelka.log.debug "Extending Request with Auth mixin"
|
361
|
+
Strelka::HTTPRequest.class_eval { include Strelka::HTTPRequest::Auth }
|
362
|
+
super
|
363
|
+
end
|
364
|
+
|
365
|
+
|
366
|
+
### Add an AuthProvider instance to the app.
|
367
|
+
def initialize( * )
|
368
|
+
super
|
369
|
+
@auth_provider = self.class.auth_provider.new( self )
|
370
|
+
end
|
371
|
+
|
372
|
+
|
373
|
+
######
|
374
|
+
public
|
375
|
+
######
|
376
|
+
|
377
|
+
# The instance of (a subclass of) Strelka::AuthProvider that provides authentication
|
378
|
+
# logic for the app.
|
379
|
+
attr_reader :auth_provider
|
380
|
+
|
381
|
+
|
382
|
+
### Check authentication and authorization for requests that need it before
|
383
|
+
### sending them on.
|
384
|
+
def handle_request( request, &block )
|
385
|
+
self.log.debug "AuthProvider: %p" % [ self.auth_provider ]
|
386
|
+
|
387
|
+
self.authenticate_and_authorize( request ) if self.request_should_auth?( request )
|
388
|
+
|
389
|
+
super
|
390
|
+
end
|
391
|
+
|
392
|
+
|
393
|
+
#########
|
394
|
+
protected
|
395
|
+
#########
|
396
|
+
|
397
|
+
### Returns +true+ if the given +request+ requires authentication.
|
398
|
+
def request_should_auth?( request )
|
399
|
+
self.log.debug "Checking to see if Auth(entication/orization) should be applied for %s" %
|
400
|
+
[ request.app_path ]
|
401
|
+
|
402
|
+
# If there are positive criteria, return true if the request matches any of them,
|
403
|
+
# or false if they don't
|
404
|
+
if self.class.has_positive_auth_criteria?
|
405
|
+
criteria = self.class.positive_auth_criteria
|
406
|
+
self.log.debug " checking %d positive auth criteria" % [ criteria.length ]
|
407
|
+
return criteria.any? do |pattern, block|
|
408
|
+
self.log.debug " %p -> %p" % [ pattern, block ]
|
409
|
+
self.request_matches_criteria( request, pattern, &block )
|
410
|
+
end
|
411
|
+
|
412
|
+
# If there are negative criteria, return false if the request matches any of them,
|
413
|
+
# or true if they don't
|
414
|
+
elsif self.class.has_negative_auth_criteria?
|
415
|
+
criteria = self.class.negative_auth_criteria
|
416
|
+
self.log.debug " checking %d negative auth criteria" % [ criteria.length ]
|
417
|
+
return !criteria.any? do |pattern, block|
|
418
|
+
self.log.debug " %p -> %p" % [ pattern, block ]
|
419
|
+
self.request_matches_criteria( request, pattern, &block )
|
420
|
+
end
|
421
|
+
|
422
|
+
else
|
423
|
+
self.log.debug " no auth criteria; default to requiring auth"
|
424
|
+
return true
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
|
429
|
+
### Returns +true+ if there are positive auth criteria and the +request+ matches
|
430
|
+
### at least one of them.
|
431
|
+
def request_matches_criteria( request, pattern )
|
432
|
+
case pattern
|
433
|
+
when Regexp
|
434
|
+
self.log.debug " matching app_path with regexp: %p" % [ pattern ]
|
435
|
+
matchdata = pattern.match( request.app_path ) or return false
|
436
|
+
self.log.debug " calling the block"
|
437
|
+
return yield( request, matchdata )
|
438
|
+
|
439
|
+
when String
|
440
|
+
self.log.debug " matching app_path prefix: %p" % [ pattern ]
|
441
|
+
request.app_path.start_with?( pattern ) or return false
|
442
|
+
self.log.debug " calling the block"
|
443
|
+
return yield( request )
|
444
|
+
|
445
|
+
else
|
446
|
+
raise ScriptError, "don't know how to match a request with a %p" % [ pattern.class ]
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
|
451
|
+
### Process authentication and authorization for the specified +request+.
|
452
|
+
def authenticate_and_authorize( request )
|
453
|
+
credentials = self.provide_authentication( request )
|
454
|
+
request.authenticated_user = credentials
|
455
|
+
self.provide_authorization( credentials, request )
|
456
|
+
end
|
457
|
+
|
458
|
+
|
459
|
+
### If the AuthProvider does authentication, try to extract authenticated credentials
|
460
|
+
### from the +request+ and return them, throwing a :finish with
|
461
|
+
### a properly-constructed 401 (Auth required) response if that fails.
|
462
|
+
def provide_authentication( request )
|
463
|
+
provider = self.auth_provider
|
464
|
+
self.log.info "Authenticating request using provider: %p" % [ provider ]
|
465
|
+
return provider.authenticate( request )
|
466
|
+
end
|
467
|
+
|
468
|
+
|
469
|
+
### Process authorization for the given +credentials+ and +request+.
|
470
|
+
def provide_authorization( credentials, request )
|
471
|
+
provider = self.auth_provider
|
472
|
+
callback = self.class.authz_callback
|
473
|
+
|
474
|
+
self.log.info "Authorizing using credentials: %p, callback: %p" % [ credentials, callback ]
|
475
|
+
provider.authorize( credentials, request, &callback )
|
476
|
+
end
|
477
|
+
|
478
|
+
end # module Strelka::App::Auth
|
479
|
+
|
480
|
+
|
data/lib/strelka/app/sessions.rb
CHANGED
@@ -85,9 +85,7 @@ module Strelka::App::Sessions
|
|
85
85
|
Strelka::Constants
|
86
86
|
|
87
87
|
# Default options to pass to the session object
|
88
|
-
DEFAULT_OPTIONS = {
|
89
|
-
:cookie_name => 'strelka-session',
|
90
|
-
}
|
88
|
+
DEFAULT_OPTIONS = {}
|
91
89
|
|
92
90
|
# Configurability API -- specify which section of the config this class gets
|
93
91
|
config_key :sessions
|
@@ -101,7 +99,7 @@ module Strelka::App::Sessions
|
|
101
99
|
singleton_attr_writer :session_class
|
102
100
|
|
103
101
|
|
104
|
-
# Class methods and instance variables to add to classes with
|
102
|
+
# Class methods and instance variables to add to classes with sessions.
|
105
103
|
module ClassMethods # :nodoc:
|
106
104
|
|
107
105
|
# The namespace of the session that will be exposed to instances of this
|
@@ -135,7 +133,11 @@ module Strelka::App::Sessions
|
|
135
133
|
if config
|
136
134
|
self.session_class = Strelka::Session.get_subclass( config[:session_class] ) if
|
137
135
|
config.key?( :session_class )
|
138
|
-
|
136
|
+
if config[:options]
|
137
|
+
options.merge!( config[:options] ) do |key, oldval, newval|
|
138
|
+
oldval.merge( newval )
|
139
|
+
end
|
140
|
+
end
|
139
141
|
else
|
140
142
|
self.session_class = Strelka::Session.get_subclass( :default )
|
141
143
|
end
|
@@ -150,6 +152,7 @@ module Strelka::App::Sessions
|
|
150
152
|
def self::included( object )
|
151
153
|
Strelka.log.debug "Extending Request with Session mixin"
|
152
154
|
Strelka::HTTPRequest.class_eval { include Strelka::HTTPRequest::Session }
|
155
|
+
Strelka.log.debug "Extending Response with Session mixin"
|
153
156
|
Strelka::HTTPResponse.class_eval { include Strelka::HTTPResponse::Session }
|
154
157
|
super
|
155
158
|
end
|
@@ -169,7 +172,6 @@ module Strelka::App::Sessions
|
|
169
172
|
return super
|
170
173
|
end
|
171
174
|
|
172
|
-
|
173
175
|
end # module Strelka::App::Sessions
|
174
176
|
|
175
177
|
|
@@ -10,7 +10,82 @@ require 'strelka/app' unless defined?( Strelka::App )
|
|
10
10
|
require 'strelka/app/plugins'
|
11
11
|
|
12
12
|
|
13
|
-
#
|
13
|
+
# A templated content-generation plugin for Strelka::Apps. It uses the
|
14
|
+
# Inversion[http://deveiate.org/projects/Inversion] templating system.
|
15
|
+
#
|
16
|
+
# It adds:
|
17
|
+
#
|
18
|
+
# * a preloaded/cached template table
|
19
|
+
# * a mechanism for fetching templates from the table
|
20
|
+
# * a global layout template which is automatically wrapped around responses
|
21
|
+
#
|
22
|
+
# == Usage
|
23
|
+
#
|
24
|
+
# To use it, just load the <tt>:templating</tt> plugin in your app:
|
25
|
+
#
|
26
|
+
# plugins :templating
|
27
|
+
#
|
28
|
+
# and declare one or more templates that your application will use:
|
29
|
+
#
|
30
|
+
# templates :console => 'views/console.tmpl',
|
31
|
+
# :proctable => 'partials/proctable.tmpl'
|
32
|
+
#
|
33
|
+
# Then, inside your app, you can fetch a copy of one or more of the templates and
|
34
|
+
# return it as the reponse:
|
35
|
+
#
|
36
|
+
# def handle_request( req )
|
37
|
+
# super do
|
38
|
+
# res = request.response
|
39
|
+
#
|
40
|
+
# proctable = template :proctable
|
41
|
+
# proctable.processes = ProcessList.fetch
|
42
|
+
#
|
43
|
+
# tmpl = template :console
|
44
|
+
# tmpl.message = "Everything's up."
|
45
|
+
# tmpl.proctable = proctable
|
46
|
+
# res.body = tmpl
|
47
|
+
#
|
48
|
+
# return res
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# You can also just return the template if you don't need to do anything else to the
|
53
|
+
# response.
|
54
|
+
#
|
55
|
+
# When returning a template, either in the body of the response or directly, it will
|
56
|
+
# automatically set a few attributes for commonly-used objects:
|
57
|
+
#
|
58
|
+
# request :: The current Strelka::HTTPRequest
|
59
|
+
# strelka_version :: Strelka.version_string( true )
|
60
|
+
# mongrel2_version :: Mongrel2.version_string( true )
|
61
|
+
# route :: If the :routing plugin is loaded, this will be set to the
|
62
|
+
# 'routing_info' of the chosen route. See
|
63
|
+
# Strelka::Router#add_route for details.
|
64
|
+
#
|
65
|
+
# If your app will *only* be loading and returning a template without doing anything
|
66
|
+
# with it, you can return just its name:
|
67
|
+
#
|
68
|
+
# def handle_request( req )
|
69
|
+
# super { :console }
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# It will be loaded, set as the response body, and the above common objects added to it.
|
73
|
+
#
|
74
|
+
# === Layouts
|
75
|
+
#
|
76
|
+
# Very often, you'll want your app to share a common page layout. To accomplish this, you
|
77
|
+
# can declare a special layout template:
|
78
|
+
#
|
79
|
+
# layout 'layout.tmpl'
|
80
|
+
#
|
81
|
+
# Any template that you return will be set as the 'body' attribute of this layout
|
82
|
+
# template, and the layout rendered as the body of the response.
|
83
|
+
#
|
84
|
+
# Note that if you want any of the "common objects" from above with a layout template,
|
85
|
+
# you must use the <tt><?import ?></tt> directive to import them:
|
86
|
+
#
|
87
|
+
# <?import request, strelka_version, route ?>
|
88
|
+
#
|
14
89
|
module Strelka::App::Templating
|
15
90
|
include Strelka::Constants
|
16
91
|
extend Strelka::App::Plugin
|
@@ -20,7 +95,7 @@ module Strelka::App::Templating
|
|
20
95
|
|
21
96
|
|
22
97
|
# Class methods to add to classes with templating.
|
23
|
-
module ClassMethods
|
98
|
+
module ClassMethods
|
24
99
|
|
25
100
|
# The map of template names to template file paths.
|
26
101
|
@template_map = {}
|
@@ -97,21 +172,7 @@ module Strelka::App::Templating
|
|
97
172
|
|
98
173
|
|
99
174
|
### Intercept responses on the way back out and turn them into a Mongrel2::HTTPResponse
|
100
|
-
### with a String for its entity body.
|
101
|
-
###
|
102
|
-
### 1. A Mongrel2::Response with an Inversion::Template as its body.
|
103
|
-
### 2. An Inversion::Template by itself.
|
104
|
-
### 3. A Symbol that matches one of the keys of the registered templates.
|
105
|
-
###
|
106
|
-
### In all three of these cases, the return value will be a Mongrel2::Response with a
|
107
|
-
### body set to the rendered value of the template in question, and with its status
|
108
|
-
### set to '200 OK' unless it is already set to something else.
|
109
|
-
###
|
110
|
-
### If there is a registered layout template, and any of the three cases is true, the
|
111
|
-
### layout template is loaded, its #body attributes set to the content template,
|
112
|
-
### and its rendered output set as the body of the response instead.
|
113
|
-
###
|
114
|
-
### Every other response is returned without modification.
|
175
|
+
### with a String for its entity body.
|
115
176
|
def handle_request( request, &block )
|
116
177
|
response = super
|
117
178
|
|
data/lib/strelka/app.rb
CHANGED
@@ -239,7 +239,6 @@ class Strelka::App < Mongrel2::Handler
|
|
239
239
|
### for the plugin system. Without being overridden or extended by plugins, this
|
240
240
|
### method just returns the default Mongrel2::HTTPRequest#response.
|
241
241
|
def handle_request( request, &block )
|
242
|
-
self.log.debug "Strelka::App#handle_request"
|
243
242
|
if block
|
244
243
|
return super( request, &block )
|
245
244
|
else
|
@@ -302,15 +301,17 @@ class Strelka::App < Mongrel2::Handler
|
|
302
301
|
### Abort the current execution and return a response with the specified
|
303
302
|
### http_status code immediately. The specified +message+ will be logged,
|
304
303
|
### and will be included in any message that is returned as part of the
|
305
|
-
### response. The +
|
306
|
-
def finish_with( http_status, message,
|
307
|
-
status_info =
|
304
|
+
### response. The +headers+ hash will be used to set response headers.
|
305
|
+
def finish_with( http_status, message, headers={} )
|
306
|
+
status_info = { :status => http_status, :message => message, :headers => headers }
|
308
307
|
throw :finish, status_info
|
309
308
|
end
|
310
309
|
|
311
310
|
|
312
311
|
### Create a response to specified +request+ based on the specified +status_code+
|
313
312
|
### and +message+.
|
313
|
+
### :TODO: Document and test the :content_type status_info field.
|
314
|
+
### :TODO: Implement a way to set headers from the status_info.
|
314
315
|
def prepare_status_response( request, status_info )
|
315
316
|
status_code, message = status_info.values_at( :status, :message )
|
316
317
|
self.log.info "Non-OK response: %d (%s)" % [ status_code, message ]
|
@@ -322,10 +323,17 @@ class Strelka::App < Mongrel2::Handler
|
|
322
323
|
# Some status codes allow explanatory text to be returned; some forbid it. Append the
|
323
324
|
# message for those that allow one.
|
324
325
|
unless request.verb == :HEAD || HTTP::BODILESS_HTTP_RESPONSE_CODES.include?( status_code )
|
325
|
-
response.content_type =
|
326
|
+
response.content_type = 'text/plain'
|
326
327
|
response.puts( message )
|
327
328
|
end
|
328
329
|
|
330
|
+
# Now assign any headers to the response that are part of the status
|
331
|
+
if status_info.key?( :headers )
|
332
|
+
status_info[:headers].each do |hdr, value|
|
333
|
+
response.headers[ hdr ] = value
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
329
337
|
return response
|
330
338
|
end
|
331
339
|
|