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