aker 3.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +210 -0
- data/README.md +282 -0
- data/assets/aker/form/login.css +73 -0
- data/assets/aker/form/login.html.erb +44 -0
- data/lib/aker/authorities/automatic_access.rb +36 -0
- data/lib/aker/authorities/composite.rb +301 -0
- data/lib/aker/authorities/static.rb +283 -0
- data/lib/aker/authorities/support/find_sole_user.rb +24 -0
- data/lib/aker/authorities/support.rb +9 -0
- data/lib/aker/authorities.rb +46 -0
- data/lib/aker/cas/authority.rb +79 -0
- data/lib/aker/cas/configuration_helper.rb +85 -0
- data/lib/aker/cas/middleware/logout_responder.rb +49 -0
- data/lib/aker/cas/middleware/ticket_remover.rb +35 -0
- data/lib/aker/cas/middleware.rb +6 -0
- data/lib/aker/cas/proxy_mode.rb +108 -0
- data/lib/aker/cas/rack_proxy_callback.rb +188 -0
- data/lib/aker/cas/service_mode.rb +88 -0
- data/lib/aker/cas/service_url.rb +62 -0
- data/lib/aker/cas/user_ext.rb +64 -0
- data/lib/aker/cas.rb +31 -0
- data/lib/aker/central_parameters.rb +101 -0
- data/lib/aker/configuration.rb +534 -0
- data/lib/aker/deprecation.rb +105 -0
- data/lib/aker/form/custom_views_mode.rb +80 -0
- data/lib/aker/form/login_form_asset_provider.rb +56 -0
- data/lib/aker/form/middleware/custom_view_login_responder.rb +19 -0
- data/lib/aker/form/middleware/login_renderer.rb +72 -0
- data/lib/aker/form/middleware/login_responder.rb +71 -0
- data/lib/aker/form/middleware/logout_responder.rb +26 -0
- data/lib/aker/form/middleware.rb +10 -0
- data/lib/aker/form/mode.rb +118 -0
- data/lib/aker/form.rb +26 -0
- data/lib/aker/group.rb +67 -0
- data/lib/aker/group_membership.rb +162 -0
- data/lib/aker/ldap/authority.rb +392 -0
- data/lib/aker/ldap/user_ext.rb +19 -0
- data/lib/aker/ldap.rb +22 -0
- data/lib/aker/modes/base.rb +85 -0
- data/lib/aker/modes/http_basic.rb +100 -0
- data/lib/aker/modes/support/attempted_path.rb +22 -0
- data/lib/aker/modes/support/rfc_2617.rb +32 -0
- data/lib/aker/modes/support.rb +12 -0
- data/lib/aker/modes.rb +48 -0
- data/lib/aker/rack/authenticate.rb +37 -0
- data/lib/aker/rack/configuration_helper.rb +18 -0
- data/lib/aker/rack/default_logout_responder.rb +36 -0
- data/lib/aker/rack/environment_helper.rb +34 -0
- data/lib/aker/rack/facade.rb +102 -0
- data/lib/aker/rack/failure.rb +69 -0
- data/lib/aker/rack/logout.rb +63 -0
- data/lib/aker/rack/request_ext.rb +19 -0
- data/lib/aker/rack/session_timer.rb +95 -0
- data/lib/aker/rack/setup.rb +77 -0
- data/lib/aker/rack.rb +107 -0
- data/lib/aker/test/helpers.rb +22 -0
- data/lib/aker/test.rb +8 -0
- data/lib/aker/user.rb +231 -0
- data/lib/aker/version.rb +3 -0
- data/lib/aker.rb +51 -0
- data/spec/aker/aker-sample.yml +11 -0
- data/spec/aker/authorities/automatic_access_spec.rb +52 -0
- data/spec/aker/authorities/composite_spec.rb +488 -0
- data/spec/aker/authorities/nu-schema.jar +0 -0
- data/spec/aker/authorities/static_spec.rb +455 -0
- data/spec/aker/authorities/support/find_sole_user_spec.rb +33 -0
- data/spec/aker/authorities_spec.rb +16 -0
- data/spec/aker/cas/authority_spec.rb +106 -0
- data/spec/aker/cas/configuration_helper_spec.rb +92 -0
- data/spec/aker/cas/middleware/logout_responder_spec.rb +47 -0
- data/spec/aker/cas/middleware/ticket_remover_spec.rb +49 -0
- data/spec/aker/cas/proxy_mode_spec.rb +185 -0
- data/spec/aker/cas/rack_proxy_callback_spec.rb +190 -0
- data/spec/aker/cas/service_mode_spec.rb +122 -0
- data/spec/aker/cas/service_url_spec.rb +114 -0
- data/spec/aker/cas/user_ext_spec.rb +27 -0
- data/spec/aker/cas_spec.rb +19 -0
- data/spec/aker/central_parameters_spec.rb +44 -0
- data/spec/aker/configuration_spec.rb +465 -0
- data/spec/aker/deprecation_spec.rb +115 -0
- data/spec/aker/form/a_form_mode.rb +129 -0
- data/spec/aker/form/custom_views_mode_spec.rb +34 -0
- data/spec/aker/form/login_form_asset_provider_spec.rb +80 -0
- data/spec/aker/form/middleware/a_form_login_responder.rb +89 -0
- data/spec/aker/form/middleware/custom_view_login_responder_spec.rb +47 -0
- data/spec/aker/form/middleware/login_renderer_spec.rb +56 -0
- data/spec/aker/form/middleware/login_responder_spec.rb +34 -0
- data/spec/aker/form/middleware/logout_responder_spec.rb +55 -0
- data/spec/aker/form/mode_spec.rb +15 -0
- data/spec/aker/form_spec.rb +11 -0
- data/spec/aker/group_membership_spec.rb +208 -0
- data/spec/aker/group_spec.rb +66 -0
- data/spec/aker/ldap/authority_spec.rb +414 -0
- data/spec/aker/ldap/ldap-users.ldif +197 -0
- data/spec/aker/ldap_spec.rb +11 -0
- data/spec/aker/modes/a_aker_mode.rb +41 -0
- data/spec/aker/modes/http_basic_spec.rb +127 -0
- data/spec/aker/modes/support/attempted_path_spec.rb +32 -0
- data/spec/aker/modes_spec.rb +11 -0
- data/spec/aker/rack/authenticate_spec.rb +78 -0
- data/spec/aker/rack/default_logout_responder_spec.rb +67 -0
- data/spec/aker/rack/facade_spec.rb +154 -0
- data/spec/aker/rack/failure_spec.rb +151 -0
- data/spec/aker/rack/logout_spec.rb +63 -0
- data/spec/aker/rack/request_ext_spec.rb +29 -0
- data/spec/aker/rack/session_timer_spec.rb +134 -0
- data/spec/aker/rack/setup_spec.rb +87 -0
- data/spec/aker/rack_spec.rb +216 -0
- data/spec/aker/test/helpers_spec.rb +44 -0
- data/spec/aker/user_spec.rb +362 -0
- data/spec/aker_spec.rb +80 -0
- data/spec/deprecation_helper.rb +58 -0
- data/spec/java_helper.rb +5 -0
- data/spec/logger_helper.rb +17 -0
- data/spec/matchers.rb +31 -0
- data/spec/mock_builder.rb +25 -0
- data/spec/spec_helper.rb +52 -0
- 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
|