aker 3.0.0.pre

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.
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