aker 3.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data/CHANGELOG.md +210 -0
  2. data/README.md +282 -0
  3. data/assets/aker/form/login.css +73 -0
  4. data/assets/aker/form/login.html.erb +44 -0
  5. data/lib/aker/authorities/automatic_access.rb +36 -0
  6. data/lib/aker/authorities/composite.rb +301 -0
  7. data/lib/aker/authorities/static.rb +283 -0
  8. data/lib/aker/authorities/support/find_sole_user.rb +24 -0
  9. data/lib/aker/authorities/support.rb +9 -0
  10. data/lib/aker/authorities.rb +46 -0
  11. data/lib/aker/cas/authority.rb +79 -0
  12. data/lib/aker/cas/configuration_helper.rb +85 -0
  13. data/lib/aker/cas/middleware/logout_responder.rb +49 -0
  14. data/lib/aker/cas/middleware/ticket_remover.rb +35 -0
  15. data/lib/aker/cas/middleware.rb +6 -0
  16. data/lib/aker/cas/proxy_mode.rb +108 -0
  17. data/lib/aker/cas/rack_proxy_callback.rb +188 -0
  18. data/lib/aker/cas/service_mode.rb +88 -0
  19. data/lib/aker/cas/service_url.rb +62 -0
  20. data/lib/aker/cas/user_ext.rb +64 -0
  21. data/lib/aker/cas.rb +31 -0
  22. data/lib/aker/central_parameters.rb +101 -0
  23. data/lib/aker/configuration.rb +534 -0
  24. data/lib/aker/deprecation.rb +105 -0
  25. data/lib/aker/form/custom_views_mode.rb +80 -0
  26. data/lib/aker/form/login_form_asset_provider.rb +56 -0
  27. data/lib/aker/form/middleware/custom_view_login_responder.rb +19 -0
  28. data/lib/aker/form/middleware/login_renderer.rb +72 -0
  29. data/lib/aker/form/middleware/login_responder.rb +71 -0
  30. data/lib/aker/form/middleware/logout_responder.rb +26 -0
  31. data/lib/aker/form/middleware.rb +10 -0
  32. data/lib/aker/form/mode.rb +118 -0
  33. data/lib/aker/form.rb +26 -0
  34. data/lib/aker/group.rb +67 -0
  35. data/lib/aker/group_membership.rb +162 -0
  36. data/lib/aker/ldap/authority.rb +392 -0
  37. data/lib/aker/ldap/user_ext.rb +19 -0
  38. data/lib/aker/ldap.rb +22 -0
  39. data/lib/aker/modes/base.rb +85 -0
  40. data/lib/aker/modes/http_basic.rb +100 -0
  41. data/lib/aker/modes/support/attempted_path.rb +22 -0
  42. data/lib/aker/modes/support/rfc_2617.rb +32 -0
  43. data/lib/aker/modes/support.rb +12 -0
  44. data/lib/aker/modes.rb +48 -0
  45. data/lib/aker/rack/authenticate.rb +37 -0
  46. data/lib/aker/rack/configuration_helper.rb +18 -0
  47. data/lib/aker/rack/default_logout_responder.rb +36 -0
  48. data/lib/aker/rack/environment_helper.rb +34 -0
  49. data/lib/aker/rack/facade.rb +102 -0
  50. data/lib/aker/rack/failure.rb +69 -0
  51. data/lib/aker/rack/logout.rb +63 -0
  52. data/lib/aker/rack/request_ext.rb +19 -0
  53. data/lib/aker/rack/session_timer.rb +95 -0
  54. data/lib/aker/rack/setup.rb +77 -0
  55. data/lib/aker/rack.rb +107 -0
  56. data/lib/aker/test/helpers.rb +22 -0
  57. data/lib/aker/test.rb +8 -0
  58. data/lib/aker/user.rb +231 -0
  59. data/lib/aker/version.rb +3 -0
  60. data/lib/aker.rb +51 -0
  61. data/spec/aker/aker-sample.yml +11 -0
  62. data/spec/aker/authorities/automatic_access_spec.rb +52 -0
  63. data/spec/aker/authorities/composite_spec.rb +488 -0
  64. data/spec/aker/authorities/nu-schema.jar +0 -0
  65. data/spec/aker/authorities/static_spec.rb +455 -0
  66. data/spec/aker/authorities/support/find_sole_user_spec.rb +33 -0
  67. data/spec/aker/authorities_spec.rb +16 -0
  68. data/spec/aker/cas/authority_spec.rb +106 -0
  69. data/spec/aker/cas/configuration_helper_spec.rb +92 -0
  70. data/spec/aker/cas/middleware/logout_responder_spec.rb +47 -0
  71. data/spec/aker/cas/middleware/ticket_remover_spec.rb +49 -0
  72. data/spec/aker/cas/proxy_mode_spec.rb +185 -0
  73. data/spec/aker/cas/rack_proxy_callback_spec.rb +190 -0
  74. data/spec/aker/cas/service_mode_spec.rb +122 -0
  75. data/spec/aker/cas/service_url_spec.rb +114 -0
  76. data/spec/aker/cas/user_ext_spec.rb +27 -0
  77. data/spec/aker/cas_spec.rb +19 -0
  78. data/spec/aker/central_parameters_spec.rb +44 -0
  79. data/spec/aker/configuration_spec.rb +465 -0
  80. data/spec/aker/deprecation_spec.rb +115 -0
  81. data/spec/aker/form/a_form_mode.rb +129 -0
  82. data/spec/aker/form/custom_views_mode_spec.rb +34 -0
  83. data/spec/aker/form/login_form_asset_provider_spec.rb +80 -0
  84. data/spec/aker/form/middleware/a_form_login_responder.rb +89 -0
  85. data/spec/aker/form/middleware/custom_view_login_responder_spec.rb +47 -0
  86. data/spec/aker/form/middleware/login_renderer_spec.rb +56 -0
  87. data/spec/aker/form/middleware/login_responder_spec.rb +34 -0
  88. data/spec/aker/form/middleware/logout_responder_spec.rb +55 -0
  89. data/spec/aker/form/mode_spec.rb +15 -0
  90. data/spec/aker/form_spec.rb +11 -0
  91. data/spec/aker/group_membership_spec.rb +208 -0
  92. data/spec/aker/group_spec.rb +66 -0
  93. data/spec/aker/ldap/authority_spec.rb +414 -0
  94. data/spec/aker/ldap/ldap-users.ldif +197 -0
  95. data/spec/aker/ldap_spec.rb +11 -0
  96. data/spec/aker/modes/a_aker_mode.rb +41 -0
  97. data/spec/aker/modes/http_basic_spec.rb +127 -0
  98. data/spec/aker/modes/support/attempted_path_spec.rb +32 -0
  99. data/spec/aker/modes_spec.rb +11 -0
  100. data/spec/aker/rack/authenticate_spec.rb +78 -0
  101. data/spec/aker/rack/default_logout_responder_spec.rb +67 -0
  102. data/spec/aker/rack/facade_spec.rb +154 -0
  103. data/spec/aker/rack/failure_spec.rb +151 -0
  104. data/spec/aker/rack/logout_spec.rb +63 -0
  105. data/spec/aker/rack/request_ext_spec.rb +29 -0
  106. data/spec/aker/rack/session_timer_spec.rb +134 -0
  107. data/spec/aker/rack/setup_spec.rb +87 -0
  108. data/spec/aker/rack_spec.rb +216 -0
  109. data/spec/aker/test/helpers_spec.rb +44 -0
  110. data/spec/aker/user_spec.rb +362 -0
  111. data/spec/aker_spec.rb +80 -0
  112. data/spec/deprecation_helper.rb +58 -0
  113. data/spec/java_helper.rb +5 -0
  114. data/spec/logger_helper.rb +17 -0
  115. data/spec/matchers.rb +31 -0
  116. data/spec/mock_builder.rb +25 -0
  117. data/spec/spec_helper.rb +52 -0
  118. metadata +265 -0
@@ -0,0 +1,392 @@
1
+ require 'net/ldap'
2
+ require 'aker/ldap'
3
+
4
+ module Aker::Ldap
5
+ ##
6
+ # A generic authority for performing authentication and user lookup
7
+ # via an LDAP server. It authenticates username/password
8
+ # combinations and fills in demographic information from the LDAP
9
+ # record. It also implements {#find_users find_users} to provide
10
+ # searches separately from authentication.
11
+ #
12
+ # This authority supports multiple instances if you need to combine
13
+ # the results from multiple LDAP servers in a single Aker
14
+ # configuration. Setting up multiple instances either requires
15
+ # directly constructing the instances (i.e., not using the `:ldap`
16
+ # alias and having Aker construct them for you) or writing
17
+ # separately constructable subclasses. The former makes sense for
18
+ # one-off configurations while the latter is better for reuse. See
19
+ # also {Configuration::Slice} for setting default parameters in
20
+ # extensions and {Configuration#alias_authority} for giving pithy
21
+ # names to authority subclasses.
22
+ #
23
+ # @example Configuring a single LDAP authority
24
+ # Aker.configure {
25
+ # ldap_parameters :server => "ldap.example.org"
26
+ # authority :ldap
27
+ # }
28
+ #
29
+ # @example Configuring multiple LDAP authorities via manual construction
30
+ # Aker.configure {
31
+ # hr_parameters :server => "hr.example.com", :port => 5003
32
+ # dept_parameters :server => "ldap.mydept.example.com"
33
+ #
34
+ # hr_ldap = Aker::Ldap::Authority.new(this, :hr)
35
+ # dept_ldap = Aker::Ldap::Authority.new(this, :dept)
36
+ #
37
+ # authorities hr_ldap, dept_ldap
38
+ # }
39
+ #
40
+ # @example Defining multiple LDAP authorities via subclassing
41
+ # # Not pictured: using a default slice and aliasing to make
42
+ # # these authorities look like built-in authorities.
43
+ #
44
+ # class HrLdap < Aker::Ldap::Authority
45
+ # def initialize(config); super config, :hr; end
46
+ # end
47
+ # class DeptLdap < Aker::Ldap::Authority
48
+ # def initialize(config); super config, :dept; end
49
+ # end
50
+ #
51
+ # Aker.configure {
52
+ # hr_parameters :server => "hr.example.com", :port => 5003
53
+ # dept_parameters :server => "ldap.mydept.example.com"
54
+ # authorities HrLdap, DeptLdap
55
+ # }
56
+ #
57
+ # @since 2.2.0
58
+ # @author Rhett Sutphin
59
+ class Authority
60
+ ##
61
+ # @see #attribute_map
62
+ DEFAULT_ATTRIBUTE_MAP = {
63
+ :uid => :username,
64
+ :sn => :last_name,
65
+ :givenname => :first_name,
66
+ :title => :title,
67
+ :mail => :email,
68
+ :telephonenumber => :business_phone,
69
+ :facsimiletelephonenumber => :fax,
70
+ }.freeze
71
+
72
+ ##
73
+ # Create a new instance.
74
+ #
75
+ # @param [Configuration, Hash] config the configuration for
76
+ # this instance. If a hash, the parameters are extracted
77
+ # directly. If a {Configuration}, the parameters are
78
+ # extracted using {Configuration#parameters_for
79
+ # parameters_for(name)} (where `name` is the name parameter to
80
+ # this constructor; default is `:ldap`).
81
+ #
82
+ # @option config [String] :server The hostname for the LDAP server
83
+ # (required)
84
+ #
85
+ # @option config [Integer] :port (636) The port to use to connect to the
86
+ # LDAP server
87
+ #
88
+ # @option config [Boolean] :use_tls (true) Whether the LDAP server
89
+ # uses TLS. Note that if you set this to false, you'll probably
90
+ # need to change the port as well.
91
+ #
92
+ # @option config [String] :user A username to use to bind to
93
+ # the server before searching or authenticating (optional)
94
+ #
95
+ # @option config [String] :password The password that goes with
96
+ # *:user* (optional; required if *:user* is specified)
97
+ #
98
+ # @option config [Hash<Symbol, Symbol>] :attribute_map Extensions
99
+ # and overrides for the LDAP-to-Aker user attribute
100
+ # mapping. See {#attribute_map} for details.
101
+ #
102
+ # @option config [Hash<Symbol, #call>] :attribute_processors See
103
+ # {#attribute_processors} for details.
104
+ #
105
+ # @option config [Hash<Symbol, Symbol>] :criteria_map See
106
+ # {#criteria_map} for details.
107
+ #
108
+ # @param [Symbol] name the name for this authority. If you need to
109
+ # have multiple LDAP authorities in the same configuration,
110
+ # distinguish them by name.
111
+ def initialize(config, name=:ldap)
112
+ @config =
113
+ case config
114
+ when Aker::Configuration
115
+ config.parameters_for(name)
116
+ else
117
+ config
118
+ end
119
+ validate_config!
120
+ end
121
+
122
+ def config
123
+ @config
124
+ end
125
+ protected :config
126
+
127
+ ##
128
+ # The configured server's hostname or other address.
129
+ # @return [String]
130
+ def server
131
+ @config[:server]
132
+ end
133
+
134
+ ##
135
+ # The port to use when connecting to the server.
136
+ # Defaults to 636.
137
+ # @return [Integer]
138
+ def port
139
+ @config[:port] || 636
140
+ end
141
+
142
+ ##
143
+ # The user to bind as before searching or authenticating.
144
+ # @return [String,nil]
145
+ def user
146
+ @config[:user]
147
+ end
148
+
149
+ ##
150
+ # The password to use with {#user}.
151
+ # @return [String,nil]
152
+ def password
153
+ @config[:password]
154
+ end
155
+
156
+ ##
157
+ # Whether to use TLS when communicating with the server. TLS is enabled by
158
+ # default.
159
+ # @return [Boolean]
160
+ def use_tls
161
+ @config[:use_tls].nil? ? true : @config[:use_tls]
162
+ end
163
+
164
+ ##
165
+ # The mapping between attributes from the LDAP server and
166
+ # {Aker::User} attributes. This mapping is used in two ways:
167
+ #
168
+ # * When returning users from the LDAP server, the first value for any
169
+ # mapped attribute is used as the value for that attribute in the
170
+ # user object.
171
+ # * Aker user attributes in this map will be translated into LDAP
172
+ # attributes when doing a criteria query with {#find_users}.
173
+ #
174
+ # There is a default mapping which will be reasonable for many
175
+ # cases. To extend it, provide the `:attribute_map` parameter when
176
+ # constructing this authority.
177
+ #
178
+ # If this mapping is not reversible (i.e., each value is unique),
179
+ # then the behavior of this authority is not defined. Each value
180
+ # in this map must be a writable attribute on Aker::User.
181
+ #
182
+ # @example
183
+ # ldap.attribute_map # => { :givenname => :first_name }
184
+ # ldap.find_user('jmt123')
185
+ # # => The givenName attribute in the LDAP record will be
186
+ # # mapped to Aker::User#first_name in the returned user
187
+ # ldap.find_users(:first_name => 'Jo')
188
+ # # => The LDAP server will be queried using givenName=Jo.
189
+ #
190
+ # @return [Hash<Symbol, Symbol>] the mapping from LDAP attribute
191
+ # to Aker user attribute.
192
+ def attribute_map
193
+ @attribute_map ||= DEFAULT_ATTRIBUTE_MAP.merge(@config[:attribute_map] || {})
194
+ end
195
+
196
+ ##
197
+ # @return [Hash<Symbol, Symbol>] The reverse of {#attribute_map}.
198
+ def reverse_attribute_map
199
+ @reverse_attribute_map ||= attribute_map.inject({}) { |h, (k, v)| h[v] = k; h }
200
+ end
201
+ protected :reverse_attribute_map
202
+
203
+ ##
204
+ # A set of named procs which will be applied while creating a
205
+ # {Aker::User} from an LDAP entry. The values in the map should
206
+ # be procs. Each proc should accept three arguments:
207
+ #
208
+ # * The user being created from the LDAP entry.
209
+ # * The full ldap entry. This is a hash-like object that allows
210
+ # you to retrieve LDAP attributes using their names in lower
211
+ # case. Values in the entry may be arrays or scalars. They may
212
+ # not be serializable, so before copying a value out you should
213
+ # be sure to dup it.
214
+ # * A proc that allows you to safely extract a single value from
215
+ # the entry. The values returned from this proc are safe to set
216
+ # directly in the user.
217
+ #
218
+ # If there is an entry in this mapping whose key is the same as a
219
+ # key in {#attribute_map}, the processor will be used instead of
220
+ # the simple mapping implied by `attribute_map`.
221
+ #
222
+ # @example An example processor
223
+ # lambda { |user, entry, s|
224
+ # user.identifiers[:ssn] = s[:ssn]
225
+ # }
226
+ #
227
+ # @return [Hash<Symbol, #call>]
228
+ def attribute_processors
229
+ @attribute_processors ||= attribute_map_processors.merge(@config[:attribute_processors] || {})
230
+ end
231
+
232
+ def attribute_map_processors
233
+ Hash[attribute_map.collect { |ldap, aker|
234
+ [ldap, lambda { |user, entry, s| user.send("#{aker}=", s[ldap]) }]
235
+ }]
236
+ end
237
+ private :attribute_map_processors
238
+
239
+ ##
240
+ # A mapping between attributes from the LDAP server and criteria
241
+ # keys used in {#find_users}. This mapping will be used when
242
+ # translating criteria hashes into LDAP queries. It is similar to
243
+ # {#attribute_map} in that way, but there are two differences:
244
+ #
245
+ # * The mapping is [criteria key] => [LDAP attribute]. This is the
246
+ # reverse of `attribute_map`.
247
+ # * The "criteria" in `attribute_map` have to be {Aker::User}
248
+ # attribute names. This map does not have that restriction.
249
+ #
250
+ # If a criterion appears both in this map and `attribute_map`, the
251
+ # mapping in this map is used.
252
+ #
253
+ # @return [Hash<Symbol,Symbol>]
254
+ def criteria_map
255
+ @config[:criteria_map] || {}
256
+ end
257
+
258
+ ##
259
+ # @private (only exposed for testing)
260
+ # @return [Hash]
261
+ def ldap_parameters
262
+ {
263
+ :host => server, :port => port,
264
+ :auth => if user
265
+ { :method => :simple, :username => user, :password => password }
266
+ else
267
+ { :method => :anonymous }
268
+ end,
269
+ :encryption => (:simple_tls if use_tls)
270
+ }
271
+ end
272
+
273
+ ##
274
+ # Verifies a username and password using the configured NU LDAP
275
+ # server. Only supports the `:user` credential kind. There must
276
+ # be exactly two credentials.
277
+ #
278
+ # @return [User, nil, :unsupported] a complete user record
279
+ # if the credentials are valid, `nil` if they aren't valid, and
280
+ # `:unsupported` if the first parameter is anything other than
281
+ # `:user`
282
+ def valid_credentials?(kind, *credentials)
283
+ return :unsupported unless kind == :user
284
+
285
+ username, password = credentials
286
+ return nil unless password && !password.strip.empty?
287
+
288
+ with_ldap do |ldap|
289
+ result = find_by_criteria(ldap, :username => username)
290
+ if result.size == 1
291
+ return ldap.authentic?(one_value(result[0], :dn), password) ? create_user(result[0]) : nil
292
+ else
293
+ return nil
294
+ end
295
+ end
296
+ end
297
+
298
+ ##
299
+ # Searches for and returns users matching the given criteria. If
300
+ # the criteria is a `String`, it is treated as a username. If it
301
+ # is a `Hash`, the keys are interpreted as {Aker::User} attribute
302
+ # names. Those attributes which are directly mappable to LDAP
303
+ # attributes will be used to build a filtered LDAP query. If the
304
+ # `Hash` contains no keys which are mappable to LDAP attribute
305
+ # names, no query will be performed and an empty array will be
306
+ # returned.
307
+ #
308
+ # @see Composite#find_users
309
+ # @return [Array<User>]
310
+ def find_users(*criteria)
311
+ with_ldap do |ldap|
312
+ result = find_by_criteria(ldap, *criteria)
313
+ return result.collect { |r| create_user(r) }
314
+ end
315
+ end
316
+ include ::Aker::Authorities::Support::FindSoleUser
317
+
318
+ protected
319
+
320
+ def create_user(ldap_entry)
321
+ Aker::User.new(one_value(ldap_entry, :uid)).tap do |u|
322
+ s = lambda { |k| one_value(ldap_entry, k) }
323
+ attribute_processors.reject { |k, v| k == :username }.each do |k, processor|
324
+ processor.call(u, ldap_entry, s)
325
+ end
326
+
327
+ u.extend UserExt
328
+ u.ldap_attributes = ldap_entry
329
+ end
330
+ end
331
+
332
+ def create_criteria_filter(criterion)
333
+ case criterion
334
+ when Hash
335
+ criterion.collect { |aker_attribute, value|
336
+ create_single_filter(aker_attribute.to_sym, value)
337
+ }.compact.inject { |all, filter|
338
+ all & filter
339
+ }
340
+ else
341
+ create_criteria_filter(:username => criterion.to_s)
342
+ end
343
+ end
344
+
345
+ def with_ldap
346
+ # Net::LDAP.open leaks connections in 0.0.4, so do this instead
347
+ ldap = Net::LDAP.new(ldap_parameters)
348
+ yield ldap
349
+ end
350
+
351
+ def find_by_criteria(ldap, *criteria)
352
+ filter = criteria.collect { |c| create_criteria_filter(c) }.inject { |a, f| a | f }
353
+ return [] unless filter
354
+ base = "dc=northwestern,dc=edu"
355
+ ldap.search(:filter => filter, :base => base)
356
+ end
357
+
358
+ def one_value(ldap_entry, key)
359
+ item = [*ldap_entry[key]].first
360
+ item.nil? ? nil : item.dup
361
+ end
362
+
363
+ def validate_config!
364
+ self.server or raise "The server parameter is required for ldap."
365
+ if self.user.nil? ^ self.password.nil?
366
+ raise "For ldap, both user and password are required if one is set."
367
+ end
368
+ end
369
+
370
+ def create_single_filter(aker_attribute, value)
371
+ ldap_attribute = criteria_map[aker_attribute] || reverse_attribute_map[aker_attribute]
372
+ if !value.nil? && ldap_attribute
373
+ Net::LDAP::Filter.eq(ldap_attribute.to_s, value)
374
+ end
375
+ end
376
+ end
377
+ end
378
+
379
+ ##
380
+ # Aker-specific extensions to net/ldap.
381
+ class Net::LDAP
382
+ ##
383
+ # Sugar for using LDAP bind to verify a password.
384
+ #
385
+ # @param dn [String] a full distinguished name
386
+ # @param password [String] a password to check
387
+ # @return [Boolean]
388
+ def authentic?(dn, password)
389
+ self.authenticate(dn, password)
390
+ self.bind
391
+ end
392
+ end
@@ -0,0 +1,19 @@
1
+ require 'aker/ldap'
2
+
3
+ module Aker::Ldap
4
+ ##
5
+ # Extensions to {Aker::User} for users that were found in
6
+ # an LDAP server.
7
+ #
8
+ # @see Aker::Ldap::Authority
9
+ module UserExt
10
+ ##
11
+ # A hash of all the attributes in the user's ldap
12
+ # record. The keys are downcased versions of the
13
+ # (case-insensitive) LDAP keys. Values are arrays of strings
14
+ # (since LDAP allows multiple instances of the same key).
15
+ #
16
+ # @return [Hash<Symbol, Array<String>>]
17
+ attr_accessor :ldap_attributes
18
+ end
19
+ end
data/lib/aker/ldap.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'aker'
2
+
3
+ module Aker
4
+ ##
5
+ # Namespace for LDAP-related functionality in Aker.
6
+ module Ldap
7
+ autoload :Authority, 'aker/ldap/authority'
8
+ autoload :UserExt, 'aker/ldap/user_ext'
9
+
10
+ ##
11
+ # @private
12
+ class Slice < Aker::Configuration::Slice
13
+ def initialize
14
+ super do
15
+ alias_authority :ldap, Authority
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ Aker::Configuration.add_default_slice(Aker::Ldap::Slice.new)
@@ -0,0 +1,85 @@
1
+ require 'aker'
2
+ require 'warden'
3
+
4
+ module Aker
5
+ module Modes
6
+ ##
7
+ # Base class for all authentication modes.
8
+ #
9
+ # An _authentication mode_ is an an object that implements an
10
+ # authentication protocol. Modes may be _interactive_, meaning that they
11
+ # require input from a human, and/or _non-interactive_, meaning that they
12
+ # can be used without user intervention.
13
+ #
14
+ # For mode implementors: While it is not strictly necessary to implement
15
+ # aker modes as subclasses of `Aker::Modes::Base`, it is recommended that
16
+ # you do so.
17
+ #
18
+ # @author David Yip
19
+ class Base < Warden::Strategies::Base
20
+ include Aker::Rack::EnvironmentHelper
21
+
22
+ ##
23
+ # Exposes the configuration this mode should use.
24
+ #
25
+ # @return [Aker::Configuration]
26
+ def configuration
27
+ super(env)
28
+ end
29
+
30
+ ##
31
+ # Exposes the authority this mode will use to validate
32
+ # credentials. Internally it is extracted from the
33
+ # `aker.authority` Rack environment variable.
34
+ #
35
+ # @return [Object]
36
+ def authority
37
+ super(env)
38
+ end
39
+
40
+ ##
41
+ # Whether or not the current request is interactive.
42
+ #
43
+ # @return [Boolean]
44
+ def interactive?
45
+ super(env)
46
+ end
47
+
48
+ ##
49
+ # Used by Warden to determine whether or not it should store user
50
+ # information in the session. In Aker, this is computed as the result
51
+ # of {#interactive?}.
52
+ #
53
+ # N.B. The `!!` is present because Warden requires that this method return
54
+ # `false` (not `false` or `nil`) for session serialization to be disabled.
55
+ #
56
+ # @see
57
+ # http://rubydoc.info/gems/warden/1.0.3/Warden/Strategies/Base#store%3F-instance_method
58
+ # Warden::Strategies::Base#store documentation
59
+ # @see
60
+ # https://github.com/hassox/warden/blob/v1.0.3/lib/warden/proxy.rb#L158
61
+ # Warden's expectations for this method
62
+ #
63
+ # @return [Boolean]
64
+ def store?
65
+ !!interactive?
66
+ end
67
+
68
+ ##
69
+ # Authenticates a user.
70
+ #
71
+ # {#authenticate!} expects `kind` and `credentials` to be
72
+ # defined. See subclasses for examples.
73
+ #
74
+ # If authentication is successful, then success! (from
75
+ # `Warden::Strategies::Base`) is called with a {User} object.
76
+ # If authentication fails, then nothing is done.
77
+ #
78
+ # @return [void]
79
+ def authenticate!
80
+ user = authority.valid_credentials?(kind, *credentials)
81
+ success!(user) if user
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,100 @@
1
+ require 'aker'
2
+ require 'base64'
3
+
4
+ module Aker
5
+ module Modes
6
+ ##
7
+ # A non-interactive and interactive mode that provides HTTP Basic
8
+ # authentication.
9
+ #
10
+ # This mode operates non-interactively when an Authorization header with a
11
+ # Basic challenge is present. It operates interactively when it is
12
+ # configured as an interactive authentication mode.
13
+ #
14
+ # @see http://www.ietf.org/rfc/rfc2617.txt
15
+ # RFC 2617
16
+ # @author David Yip
17
+ class HttpBasic < Aker::Modes::Base
18
+ include Support::Rfc2617
19
+
20
+ ##
21
+ # Recognizes valid Basic challenges.
22
+ #
23
+ # An HTTP Basic challenge is the word "Basic", followed by one space,
24
+ # followed by a Base64-encoded string.
25
+ #
26
+ # @see http://www.ietf.org/rfc/rfc2045.txt
27
+ # RFC 2045, section 6.8
28
+ BasicPattern = %r{^Basic ((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?)$}
29
+
30
+ ##
31
+ # A key that refers to this mode; used for configuration convenience.
32
+ #
33
+ # @return [Symbol]
34
+ def self.key
35
+ :http_basic
36
+ end
37
+
38
+ ##
39
+ # The type of credentials supplied by this mode.
40
+ #
41
+ # @return [Symbol]
42
+ def kind
43
+ :user
44
+ end
45
+
46
+ # Decodes and extracts a (username, password) pair from an Authorization
47
+ # header.
48
+ #
49
+ # This method checks if the format of the Authorization header is a valid
50
+ # response to a Basic challenge. If it is, then a username (and possibly
51
+ # a password) are returned. If it is not, then an empty array is
52
+ # returned.
53
+ #
54
+ # @return [Array<String>] username and password, username, or an empty
55
+ # array
56
+ #
57
+ # @see BasicPattern
58
+ # @see http://www.ietf.org/rfc/rfc2617.txt
59
+ # RFC 2617, section 2
60
+ def credentials
61
+ key = 'HTTP_AUTHORIZATION'
62
+ matches = env[key].match(BasicPattern) if env.has_key?(key)
63
+
64
+ if matches && matches[1]
65
+ Base64.decode64(matches[1]).split(':', 2)
66
+ else
67
+ []
68
+ end
69
+ end
70
+
71
+ ##
72
+ # Returns true if a valid Basic challenge is present, false otherwise.
73
+ def valid?
74
+ credentials.length == 2
75
+ end
76
+
77
+ ##
78
+ # Builds a Rack response with status 401 that indicates a need for
79
+ # authentication.
80
+ #
81
+ # With Web browsers, this will cause a username/password dialog to
82
+ # appear.
83
+ #
84
+ # @return [Rack::Response]
85
+ def on_ui_failure
86
+ ::Rack::Response.new([], 401, {'WWW-Authenticate' => challenge})
87
+ end
88
+
89
+ ##
90
+ # Used to build a WWW-Authenticate header that will be returned to a
91
+ # client when authentication is required.
92
+ #
93
+ # @see HttpMode#challenge
94
+ # @return [String]
95
+ def scheme
96
+ "Basic"
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,22 @@
1
+ require 'aker/modes/support'
2
+
3
+ module Aker::Modes::Support
4
+ ##
5
+ # If a user fails authentication, the URL that user was trying to access is
6
+ # stored in the `:attempted_path` key in the `warden.options` environment
7
+ # variable.
8
+ #
9
+ # AttemptedPath provides code to extract the attempted path that can be
10
+ # shared amongst objects that need this information.
11
+ module AttemptedPath
12
+ ##
13
+ # Returns the path that a user was trying to access.
14
+ #
15
+ # @return [String, nil] a String if a path exists, nil otherwise
16
+ def attempted_path
17
+ if env['warden.options']
18
+ env['warden.options'][:attempted_path]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,32 @@
1
+ require 'aker/modes/support'
2
+
3
+ module Aker
4
+ module Modes
5
+ module Support
6
+ ##
7
+ # A mixin providing common methods for modes which implement
8
+ # authentication according to RFC 2616 and RFC 2617.
9
+ module Rfc2617
10
+ ##
11
+ # Builds the content of the WWW-Authenticate challenge header for
12
+ # a particular mode. Requires that the target mode implement
13
+ # `#scheme`.
14
+ #
15
+ # @return [String] the challenge
16
+ def challenge
17
+ "#{scheme} realm=\"#{realm}\""
18
+ end
19
+
20
+ ##
21
+ # Determines the value to use for the required "realm" challenge
22
+ # parameter. If set, the {Aker::Configuration#portal portal} is
23
+ # used. Otherwise "Aker" is used.
24
+ #
25
+ # @return [String]
26
+ def realm
27
+ (configuration.portal? ? configuration.portal : 'Aker').to_s
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ require 'aker/modes'
2
+
3
+ module Aker
4
+ module Modes
5
+ ##
6
+ # Library code shared by modes and their middleware lives here.
7
+ module Support
8
+ autoload :AttemptedPath, 'aker/modes/support/attempted_path'
9
+ autoload :Rfc2617, 'aker/modes/support/rfc_2617'
10
+ end
11
+ end
12
+ end