strelka 0.0.1.pre148 → 0.0.1.pre177
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/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,115 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# vim: set nosta noet ts=4 sw=4:
|
3
|
+
|
4
|
+
require 'securerandom'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'sequel'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
require 'strelka' unless defined?( Strelka )
|
10
|
+
require 'strelka/app' unless defined?( Strelka::App )
|
11
|
+
require 'strelka/session/default'
|
12
|
+
|
13
|
+
# Database session class -- this class offers persistent session data using
|
14
|
+
# any database that Sequel supports. It defaults to non-persistent, in memory Sqlite.
|
15
|
+
#
|
16
|
+
class Strelka::Session::Db < Strelka::Session::Default
|
17
|
+
include Strelka::Loggable
|
18
|
+
extend Forwardable
|
19
|
+
|
20
|
+
# Class-instance variables
|
21
|
+
@table_name = :sessions
|
22
|
+
@db = nil
|
23
|
+
@dataset = nil
|
24
|
+
@cookie_options = {
|
25
|
+
:name => 'strelka-session'
|
26
|
+
}
|
27
|
+
|
28
|
+
class << self
|
29
|
+
# The Sequel dataset connection
|
30
|
+
attr_reader :db
|
31
|
+
|
32
|
+
# The Sequel dataset for the sessions table
|
33
|
+
attr_reader :dataset
|
34
|
+
|
35
|
+
# The configured session cookie parameters
|
36
|
+
attr_accessor :cookie_options
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
########################################################################
|
41
|
+
### C L A S S M E T H O D S
|
42
|
+
########################################################################
|
43
|
+
|
44
|
+
### Configure the session class with the given +options+, which should be a
|
45
|
+
### Hash or an object that has a Hash-like interface.
|
46
|
+
###
|
47
|
+
### Valid options:
|
48
|
+
### cookie -> A hash that contains valid Strelka::Cookie options
|
49
|
+
### connect -> The Sequel connection string
|
50
|
+
### table_name -> The name of the sessions table
|
51
|
+
def self::configure( options={} )
|
52
|
+
options ||= {}
|
53
|
+
|
54
|
+
self.cookie_options.merge!( options[:cookie] ) if options[:cookie]
|
55
|
+
@table_name = options[:table_name] || :sessions
|
56
|
+
|
57
|
+
@db = options[ :connect ].nil? ?
|
58
|
+
Mongrel2::Config.in_memory_db :
|
59
|
+
Sequel.connect( options[:connect] )
|
60
|
+
@db.logger = Strelka.logger
|
61
|
+
|
62
|
+
self.initialize_sessions_table
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
### Create the initial DB sessions schema if needed, setting the +sessions+
|
67
|
+
### attribute to a Sequel dataset on the configured DB table.
|
68
|
+
###
|
69
|
+
def self::initialize_sessions_table
|
70
|
+
if self.db.table_exists?( @table_name )
|
71
|
+
Strelka.log.debug "Using existing sessions table for %p" % [ db ]
|
72
|
+
|
73
|
+
else
|
74
|
+
Strelka.log.debug "Creating new sessions table for %p" % [ db ]
|
75
|
+
self.db.create_table( @table_name ) do
|
76
|
+
text :session_id, :index => true
|
77
|
+
text :session
|
78
|
+
timestamp :created
|
79
|
+
|
80
|
+
primary_key :session_id
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
@dataset = self.db[ @table_name ]
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
### Load a session instance from storage using the given +session_id+.
|
89
|
+
def self::load( session_id )
|
90
|
+
session_row = self.dataset.filter( :session_id => session_id ).first
|
91
|
+
session = session_row.nil? ? {} : YAML.load( session_row[:session] )
|
92
|
+
return new( session_id, session )
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
### Save the given +data+ associated with the +session_id+ to the DB.
|
97
|
+
def self::save_session_data( session_id, data )
|
98
|
+
self.db.transaction do
|
99
|
+
self.delete_session_data( session_id.to_s )
|
100
|
+
self.dataset.insert(
|
101
|
+
:session_id => session_id,
|
102
|
+
:session => data.to_yaml,
|
103
|
+
:created => Time.now.utc.to_s
|
104
|
+
)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
### Delete the data associated with the given +session_id+ from the DB.
|
110
|
+
def self::delete_session_data( session_id )
|
111
|
+
self.dataset.filter( :session_id => session_id ).delete
|
112
|
+
end
|
113
|
+
|
114
|
+
end # class Strelka::Session::Db
|
115
|
+
|
@@ -6,6 +6,7 @@ require 'forwardable'
|
|
6
6
|
|
7
7
|
require 'strelka' unless defined?( Strelka )
|
8
8
|
require 'strelka/app' unless defined?( Strelka::App )
|
9
|
+
require 'strelka/cookie'
|
9
10
|
require 'strelka/session'
|
10
11
|
require 'strelka/mixins'
|
11
12
|
|
@@ -14,53 +15,40 @@ require 'strelka/mixins'
|
|
14
15
|
# == Hash Interface
|
15
16
|
#
|
16
17
|
# The following methods are delegated to the inner Hash via the #namespaced_hash method:
|
17
|
-
#
|
18
|
-
# #compare_by_identity?, #default, # #default=, #default_proc, #default_proc=,
|
19
|
-
# #delete, #delete_if, #each, #each_key, #each_pair, #each_value,
|
20
|
-
# #empty?, #eql?, #fetch, #flatten, #has_key?, #has_value?, #hash, #include?,
|
21
|
-
# #index, #inspect, #invert, #keep_if, #key, #key?, #keys, #length, #member?,
|
22
|
-
# #merge, #merge!, #pretty_print, #pretty_print_cycle, #rassoc, #rehash, #reject,
|
23
|
-
# #reject!, #replace, #select, #select!, #shift, #size, #sql_expr, #sql_negate,
|
24
|
-
# #sql_or, #store, #to_a, #to_hash, #to_s, #update, #value?, #values, #values_at,
|
25
|
-
# #|, #~
|
18
|
+
# #[], #[]=, #delete, #key?
|
26
19
|
class Strelka::Session::Default < Strelka::Session
|
27
20
|
extend Forwardable,
|
28
21
|
Strelka::Delegation
|
29
|
-
include
|
30
|
-
Strelka::Loggable,
|
22
|
+
include Strelka::Loggable,
|
31
23
|
Strelka::DataUtilities
|
32
24
|
|
33
|
-
|
34
|
-
# The default name of the cookie that stores the session ID
|
35
|
-
DEFAULT_COOKIE_NAME = 'strelka-sessionid'
|
36
|
-
|
37
25
|
# Class-instance variables
|
38
26
|
@sessions = {}
|
39
|
-
@
|
27
|
+
@cookie_options = {
|
28
|
+
:name => 'strelka-session'
|
29
|
+
}
|
40
30
|
|
41
31
|
class << self
|
42
32
|
# Persist sessions in memory
|
43
33
|
attr_reader :sessions
|
44
34
|
|
45
|
-
# The
|
46
|
-
attr_accessor :
|
35
|
+
# The configured session cookie parameters
|
36
|
+
attr_accessor :cookie_options
|
47
37
|
end
|
48
38
|
|
49
39
|
|
50
40
|
### Configure the session class with the given +options+, which should be a
|
51
|
-
### Hash or an object that has a Hash-like interface. Sets
|
52
|
-
### the session
|
53
|
-
### is set.
|
41
|
+
### Hash or an object that has a Hash-like interface. Sets cookie options
|
42
|
+
### for the session if the +:cookie+ key is set.
|
54
43
|
def self::configure( options )
|
55
44
|
if options
|
56
|
-
self.
|
45
|
+
self.cookie_options.merge!( options[:cookie] ) if options[:cookie]
|
57
46
|
end
|
58
47
|
end
|
59
48
|
|
60
49
|
|
61
50
|
### Load a session instance from storage using the given +session_id+ and return
|
62
|
-
### it. Returns +nil+ if no session could be loaded.
|
63
|
-
### this, as the default just returns +nil+.
|
51
|
+
### it. Returns +nil+ if no session could be loaded.
|
64
52
|
def self::load_session_data( session_id )
|
65
53
|
return Strelka::DataUtilities.deep_copy( self.sessions[session_id] )
|
66
54
|
end
|
@@ -79,16 +67,13 @@ class Strelka::Session::Default < Strelka::Session
|
|
79
67
|
|
80
68
|
|
81
69
|
### Fetch the session ID from the given +request+, or create a new one if the
|
82
|
-
### request doesn't have the necessary attributes.
|
83
|
-
### as the default implementation just returns +nil+.
|
70
|
+
### request doesn't have the necessary attributes.
|
84
71
|
def self::get_session_id( request=nil )
|
85
72
|
id = nil
|
86
73
|
|
87
74
|
# Fetch and untaint the existing ID if it exists and looks valid
|
88
|
-
if request && (cookie = request.cookies[ self.
|
89
|
-
Strelka.log.debug "Cookie value: %p" % [ cookie.value ]
|
75
|
+
if request && (cookie = request.cookies[ self.cookie_options[:name] ])
|
90
76
|
id = $1.untaint if cookie.value =~ /^([[:xdigit:]]+)$/i
|
91
|
-
Strelka.log.debug "Hmm? %p" % [ $1 ]
|
92
77
|
end
|
93
78
|
|
94
79
|
return id || SecureRandom.random_bytes.unpack( 'H*' ).join
|
@@ -99,8 +84,7 @@ class Strelka::Session::Default < Strelka::Session
|
|
99
84
|
### I N S T A N C E M E T H O D S
|
100
85
|
#################################################################
|
101
86
|
|
102
|
-
### Create a new
|
103
|
-
### values.
|
87
|
+
### Create a new session store using the given +hash+ for initial values.
|
104
88
|
def initialize( session_id=nil, initial_values={} )
|
105
89
|
session_id ||= self.class.get_session_id
|
106
90
|
|
@@ -134,7 +118,7 @@ class Strelka::Session::Default < Strelka::Session
|
|
134
118
|
attr_reader :namespace
|
135
119
|
|
136
120
|
# Delegate Hash methods to whichever Hash is the current namespace
|
137
|
-
def_method_delegators :namespaced_hash,
|
121
|
+
def_method_delegators :namespaced_hash, :[], :[]=, :delete, :key?
|
138
122
|
|
139
123
|
|
140
124
|
### Set the current namespace for session values to +namespace+. Setting
|
@@ -150,7 +134,28 @@ class Strelka::Session::Default < Strelka::Session
|
|
150
134
|
self.log.debug "Saving session %s" % [ self.session_id ]
|
151
135
|
self.class.save_session_data( self.session_id, @hash )
|
152
136
|
self.log.debug "Adding session cookie to the request."
|
153
|
-
|
137
|
+
|
138
|
+
session_cookie = Strelka::Cookie.new(
|
139
|
+
self.class.cookie_options[ :name ],
|
140
|
+
self.session_id,
|
141
|
+
self.class.cookie_options
|
142
|
+
)
|
143
|
+
|
144
|
+
response.cookies << session_cookie
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
### Return the Hash that corresponds with the current namespace's storage. If no
|
149
|
+
### namespace is currently set, returns the entire session store as a Hash of Hashes
|
150
|
+
### keyed by namespaces as Symbols.
|
151
|
+
def namespaced_hash
|
152
|
+
if @namespace
|
153
|
+
self.log.debug "Returning namespaced hash: %p" % [ @namespace ]
|
154
|
+
return @hash[ @namespace ]
|
155
|
+
else
|
156
|
+
self.log.debug "Returning toplevel namespace"
|
157
|
+
return @hash
|
158
|
+
end
|
154
159
|
end
|
155
160
|
|
156
161
|
|
@@ -169,10 +174,8 @@ class Strelka::Session::Default < Strelka::Session
|
|
169
174
|
|
170
175
|
method_body = nil
|
171
176
|
if assignment
|
172
|
-
self.log.debug "Makin' a setter for %p" % [ key ]
|
173
177
|
method_body = self.make_setter( key )
|
174
178
|
else
|
175
|
-
self.log.debug "Makin' a getter for %p" % [ key ]
|
176
179
|
method_body = self.make_getter( key )
|
177
180
|
end
|
178
181
|
|
@@ -192,18 +195,4 @@ class Strelka::Session::Default < Strelka::Session
|
|
192
195
|
return Proc.new { self.namespaced_hash[key.to_sym] }
|
193
196
|
end
|
194
197
|
|
195
|
-
|
196
|
-
### Return the Hash that corresponds with the current namespace's storage. If no
|
197
|
-
### namespace is currently set, returns the entire session store as a Hash of Hashes
|
198
|
-
### keyed by namespaces as Symbols.
|
199
|
-
def namespaced_hash
|
200
|
-
if @namespace
|
201
|
-
self.log.debug "Returning namespaced hash: %p" % [ @namespace ]
|
202
|
-
return @hash[ @namespace ]
|
203
|
-
else
|
204
|
-
self.log.debug "Returning toplevel namespace"
|
205
|
-
return @hash
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
198
|
end # class Strelka::Session::Default
|
data/lib/strelka/session.rb
CHANGED
data/lib/strelka.rb
CHANGED
@@ -12,13 +12,16 @@ require 'configurability/config'
|
|
12
12
|
#
|
13
13
|
# * Michael Granger <ged@FaerieMUD.org>
|
14
14
|
#
|
15
|
+
# :title: Strelka Web Application Framework
|
16
|
+
# :main: README.rdoc
|
17
|
+
#
|
15
18
|
module Strelka
|
16
19
|
|
17
20
|
# Library version constant
|
18
21
|
VERSION = '0.0.1'
|
19
22
|
|
20
23
|
# Version-control revision constant
|
21
|
-
REVISION = %q$Revision:
|
24
|
+
REVISION = %q$Revision: 28465ed81190 $
|
22
25
|
|
23
26
|
|
24
27
|
require 'strelka/logging'
|
data/spec/lib/helpers.rb
CHANGED
@@ -113,9 +113,9 @@ module Strelka::SpecHelpers
|
|
113
113
|
end
|
114
114
|
|
115
115
|
### Set up a Mongrel2 configuration database according to the specified +dbspec+.
|
116
|
-
|
117
|
-
|
118
|
-
|
116
|
+
### Set up a Mongrel2 configuration database in memory.
|
117
|
+
def setup_config_db
|
118
|
+
Mongrel2::Config.db ||= Mongrel2::Config.in_memory_db
|
119
119
|
Mongrel2::Config.init_database
|
120
120
|
Mongrel2::Config.db.tables.collect {|t| Mongrel2::Config.db[t] }.each( &:truncate )
|
121
121
|
end
|
@@ -126,6 +126,11 @@ module Strelka::SpecHelpers
|
|
126
126
|
return {:action => name.to_sym}
|
127
127
|
end
|
128
128
|
|
129
|
+
|
130
|
+
#
|
131
|
+
# Matchers
|
132
|
+
#
|
133
|
+
|
129
134
|
# Route matcher
|
130
135
|
RSpec::Matchers.define( :match_route ) do |routename|
|
131
136
|
match do |route|
|
@@ -0,0 +1,367 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
BEGIN {
|
4
|
+
require 'pathname'
|
5
|
+
basedir = Pathname.new( __FILE__ ).dirname.parent.parent.parent
|
6
|
+
$LOAD_PATH.unshift( basedir ) unless $LOAD_PATH.include?( basedir )
|
7
|
+
}
|
8
|
+
|
9
|
+
require 'rspec'
|
10
|
+
require 'rspec/mocks'
|
11
|
+
|
12
|
+
require 'spec/lib/helpers'
|
13
|
+
|
14
|
+
require 'strelka'
|
15
|
+
require 'strelka/app/plugins'
|
16
|
+
require 'strelka/app/auth'
|
17
|
+
require 'strelka/authprovider'
|
18
|
+
|
19
|
+
require 'strelka/behavior/plugin'
|
20
|
+
|
21
|
+
|
22
|
+
#####################################################################
|
23
|
+
### C O N T E X T S
|
24
|
+
#####################################################################
|
25
|
+
|
26
|
+
describe Strelka::App::Auth do
|
27
|
+
|
28
|
+
before( :all ) do
|
29
|
+
setup_logging( :fatal )
|
30
|
+
@request_factory = Mongrel2::RequestFactory.new( route: '/api/v1' )
|
31
|
+
end
|
32
|
+
|
33
|
+
after( :all ) do
|
34
|
+
reset_logging()
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
it_should_behave_like( "A Strelka::App Plugin" )
|
39
|
+
|
40
|
+
|
41
|
+
it "gives including apps a default authprovider" do
|
42
|
+
app = Class.new( Strelka::App ) do
|
43
|
+
plugins :auth
|
44
|
+
end
|
45
|
+
|
46
|
+
app.auth_provider.should be_a( Class )
|
47
|
+
app.auth_provider.should < Strelka::AuthProvider
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
it "adds the Auth mixin to the request class" do
|
52
|
+
app = Class.new( Strelka::App ) do
|
53
|
+
plugins :auth
|
54
|
+
end
|
55
|
+
|
56
|
+
@request_factory.get( '/api/v1/verify' ).should respond_to( :authenticated? )
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
describe "an including App" do
|
61
|
+
|
62
|
+
before( :each ) do
|
63
|
+
@app = Class.new( Strelka::App ) do
|
64
|
+
plugins :auth
|
65
|
+
|
66
|
+
# Stand in for a real AuthProvider
|
67
|
+
@auth_provider = RSpec::Mocks::Mock
|
68
|
+
|
69
|
+
def initialize( appid='auth-test', sspec=TEST_SEND_SPEC, rspec=TEST_RECV_SPEC )
|
70
|
+
super
|
71
|
+
end
|
72
|
+
|
73
|
+
def handle_request( req )
|
74
|
+
super do
|
75
|
+
res = req.response
|
76
|
+
res.status = HTTP::OK
|
77
|
+
res.content_type = 'text/plain'
|
78
|
+
res.puts "Ran successfully."
|
79
|
+
|
80
|
+
res
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
it "applies auth to every request by default" do
|
88
|
+
app = @app.new
|
89
|
+
req = @request_factory.get( '/api/v1' )
|
90
|
+
|
91
|
+
app.auth_provider.should_receive( :authenticate ).and_return( 'anonymous' )
|
92
|
+
app.auth_provider.should_receive( :authorize ).and_return( true )
|
93
|
+
|
94
|
+
res = app.handle( req )
|
95
|
+
|
96
|
+
res.status.should == HTTP::OK
|
97
|
+
end
|
98
|
+
|
99
|
+
it "doesn't have any auth criteria by default" do
|
100
|
+
@app.should_not have_auth_criteria()
|
101
|
+
end
|
102
|
+
|
103
|
+
it "sets the authenticated_user attribute of the request to the credentials of the authenticating user" do
|
104
|
+
app = @app.new
|
105
|
+
req = @request_factory.get( '/api/v1' )
|
106
|
+
|
107
|
+
app.auth_provider.should_receive( :authenticate ).and_return( 'anonymous' )
|
108
|
+
app.auth_provider.should_receive( :authorize ).and_return( true )
|
109
|
+
|
110
|
+
app.handle( req )
|
111
|
+
req.authenticated_user.should == 'anonymous'
|
112
|
+
end
|
113
|
+
|
114
|
+
context "that has negative auth criteria" do
|
115
|
+
|
116
|
+
before( :each ) do
|
117
|
+
@app.no_auth_for( '/login' )
|
118
|
+
end
|
119
|
+
|
120
|
+
it "knows that it has auth criteria" do
|
121
|
+
@app.should have_auth_criteria()
|
122
|
+
end
|
123
|
+
|
124
|
+
it "doesn't pass a request that matches through auth" do
|
125
|
+
req = @request_factory.get( '/api/v1/login' )
|
126
|
+
|
127
|
+
app = @app.new
|
128
|
+
app.auth_provider.should_not_receive( :authenticate )
|
129
|
+
app.auth_provider.should_not_receive( :authorize )
|
130
|
+
|
131
|
+
app.handle( req )
|
132
|
+
end
|
133
|
+
|
134
|
+
it "passes a request that doesn't match through auth" do
|
135
|
+
req = @request_factory.get( '/api/v1/console' )
|
136
|
+
|
137
|
+
app = @app.new
|
138
|
+
app.auth_provider.should_receive( :authenticate ).and_return( 'anonymous' )
|
139
|
+
app.auth_provider.should_receive( :authorize ).and_return( true )
|
140
|
+
|
141
|
+
app.handle( req )
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
context "that has a negative auth criteria block" do
|
147
|
+
|
148
|
+
before( :each ) do
|
149
|
+
@app.no_auth_for do |req|
|
150
|
+
req.notes[:skip_auth]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
it "knows that it has auth criteria" do
|
155
|
+
@app.should have_auth_criteria()
|
156
|
+
end
|
157
|
+
|
158
|
+
it "doesn't pass a request for which the block returns true through auth" do
|
159
|
+
req = @request_factory.get( '/api/v1/login' )
|
160
|
+
req.notes[:skip_auth] = true
|
161
|
+
|
162
|
+
app = @app.new
|
163
|
+
app.auth_provider.should_not_receive( :authenticate )
|
164
|
+
app.auth_provider.should_not_receive( :authorize )
|
165
|
+
|
166
|
+
app.handle( req )
|
167
|
+
end
|
168
|
+
|
169
|
+
it "passes a request for which the block returns false through auth" do
|
170
|
+
req = @request_factory.get( '/api/v1/login' )
|
171
|
+
req.notes[:skip_auth] = false
|
172
|
+
|
173
|
+
app = @app.new
|
174
|
+
app.auth_provider.should_receive( :authenticate ).and_return( 'anonymous' )
|
175
|
+
app.auth_provider.should_receive( :authorize ).and_return( true )
|
176
|
+
|
177
|
+
app.handle( req )
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
context "that has a negative auth criteria with both a pattern and a block" do
|
184
|
+
|
185
|
+
before( :each ) do
|
186
|
+
@app.no_auth_for( %r{^/login/(?<username>\w+)} ) do |req, match|
|
187
|
+
match[:username] != 'validuser'
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
it "knows that it has auth criteria" do
|
192
|
+
@app.should have_auth_criteria()
|
193
|
+
end
|
194
|
+
|
195
|
+
it "doesn't pass a request through auth if the path matches and the block returns true" do
|
196
|
+
req = @request_factory.get( '/api/v1/login/lyssa' )
|
197
|
+
|
198
|
+
app = @app.new
|
199
|
+
app.auth_provider.should_not_receive( :authenticate )
|
200
|
+
app.auth_provider.should_not_receive( :authorize )
|
201
|
+
|
202
|
+
app.handle( req )
|
203
|
+
end
|
204
|
+
|
205
|
+
it "passes a request through auth if the path doesn't match" do
|
206
|
+
req = @request_factory.get( '/api/v1/console' )
|
207
|
+
|
208
|
+
app = @app.new
|
209
|
+
app.auth_provider.should_receive( :authenticate ).and_return( 'anonymous' )
|
210
|
+
app.auth_provider.should_receive( :authorize ).and_return( true )
|
211
|
+
|
212
|
+
app.handle( req )
|
213
|
+
end
|
214
|
+
|
215
|
+
it "passes a request through auth if the path matches, but the the block returns false" do
|
216
|
+
req = @request_factory.get( '/api/v1/login/validuser' )
|
217
|
+
|
218
|
+
app = @app.new
|
219
|
+
app.auth_provider.should_receive( :authenticate ).and_return( 'anonymous' )
|
220
|
+
app.auth_provider.should_receive( :authorize ).and_return( true )
|
221
|
+
|
222
|
+
app.handle( req )
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
context "that has positive auth criteria" do
|
229
|
+
|
230
|
+
before( :each ) do
|
231
|
+
@app.require_auth_for( '/login' )
|
232
|
+
end
|
233
|
+
|
234
|
+
it "knows that it has auth criteria" do
|
235
|
+
@app.should have_auth_criteria()
|
236
|
+
end
|
237
|
+
|
238
|
+
it "passes requests that match through auth" do
|
239
|
+
req = @request_factory.get( '/api/v1/login' )
|
240
|
+
|
241
|
+
app = @app.new
|
242
|
+
app.auth_provider.should_receive( :authenticate ).and_return( 'anonymous' )
|
243
|
+
app.auth_provider.should_receive( :authorize ).and_return( true )
|
244
|
+
|
245
|
+
app.handle( req )
|
246
|
+
end
|
247
|
+
|
248
|
+
it "doesn't pass requests that don't match through auth" do
|
249
|
+
req = @request_factory.get( '/api/v1/console' )
|
250
|
+
|
251
|
+
app = @app.new
|
252
|
+
app.auth_provider.should_not_receive( :authenticate )
|
253
|
+
app.auth_provider.should_not_receive( :authorize )
|
254
|
+
|
255
|
+
app.handle( req )
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
|
260
|
+
context "that has a positive auth criteria block" do
|
261
|
+
|
262
|
+
before( :each ) do
|
263
|
+
@app.require_auth_for do |req|
|
264
|
+
req.notes[:require_auth]
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
it "knows that it has auth criteria" do
|
269
|
+
@app.should have_auth_criteria()
|
270
|
+
end
|
271
|
+
|
272
|
+
it "passes requests for which the block returns true through auth" do
|
273
|
+
req = @request_factory.get( '/api/v1/login' )
|
274
|
+
req.notes[:require_auth] = true
|
275
|
+
|
276
|
+
app = @app.new
|
277
|
+
app.auth_provider.should_receive( :authenticate ).and_return( 'anonymous' )
|
278
|
+
app.auth_provider.should_receive( :authorize ).and_return( true )
|
279
|
+
|
280
|
+
app.handle( req )
|
281
|
+
end
|
282
|
+
|
283
|
+
it "doesn't pass requests for which the block returns false through auth" do
|
284
|
+
req = @request_factory.get( '/api/v1/console' )
|
285
|
+
req.notes[:require_auth] = false
|
286
|
+
|
287
|
+
app = @app.new
|
288
|
+
app.auth_provider.should_not_receive( :authenticate )
|
289
|
+
app.auth_provider.should_not_receive( :authorize )
|
290
|
+
|
291
|
+
app.handle( req )
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
|
296
|
+
context "that has a positive auth criteria with both a pattern and a block" do
|
297
|
+
|
298
|
+
before( :each ) do
|
299
|
+
@app.require_auth_for( %r{^/login/(?<username>\w+)} ) do |req, match|
|
300
|
+
match[:username] != 'guest'
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
it "knows that it has auth criteria" do
|
305
|
+
@app.should have_auth_criteria()
|
306
|
+
end
|
307
|
+
|
308
|
+
it "passes a request through auth if the path matches and the block returns true" do
|
309
|
+
req = @request_factory.get( '/api/v1/login/lyssa' )
|
310
|
+
|
311
|
+
app = @app.new
|
312
|
+
app.auth_provider.should_receive( :authenticate ).and_return( 'lyssa' )
|
313
|
+
app.auth_provider.should_receive( :authorize ).and_return( true )
|
314
|
+
|
315
|
+
app.handle( req )
|
316
|
+
end
|
317
|
+
|
318
|
+
it "doesn't pass a request through auth if the path doesn't match" do
|
319
|
+
req = @request_factory.get( '/api/v1/console' )
|
320
|
+
|
321
|
+
app = @app.new
|
322
|
+
app.auth_provider.should_not_receive( :authenticate )
|
323
|
+
app.auth_provider.should_not_receive( :authorize )
|
324
|
+
|
325
|
+
app.handle( req )
|
326
|
+
end
|
327
|
+
|
328
|
+
it "doesn't pass a request through auth if the path matches, but the the block returns false" do
|
329
|
+
req = @request_factory.get( '/api/v1/login/guest' )
|
330
|
+
|
331
|
+
app = @app.new
|
332
|
+
app.auth_provider.should_not_receive( :authenticate )
|
333
|
+
app.auth_provider.should_not_receive( :authorize )
|
334
|
+
|
335
|
+
app.handle( req )
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|
339
|
+
|
340
|
+
|
341
|
+
it "can register an authorization callback with a block" do
|
342
|
+
@app.authz_callback { :authz }
|
343
|
+
@app.authz_callback.should be_a( Proc )
|
344
|
+
end
|
345
|
+
|
346
|
+
it "can register an authorization callback with a callable object" do
|
347
|
+
callback = Proc.new { :authz }
|
348
|
+
@app.authz_callback( callback )
|
349
|
+
@app.authz_callback.should == callback
|
350
|
+
end
|
351
|
+
|
352
|
+
|
353
|
+
context "that has an authz callback" do
|
354
|
+
|
355
|
+
before( :each ) do
|
356
|
+
@app.authz_callback { }
|
357
|
+
end
|
358
|
+
|
359
|
+
it "yields authorization to the callback if authentication succeeds"
|
360
|
+
it "responds with a 403 Forbidden response if the block doesn't return true"
|
361
|
+
|
362
|
+
end
|
363
|
+
|
364
|
+
end
|
365
|
+
|
366
|
+
end
|
367
|
+
|