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,301 @@
|
|
1
|
+
require 'aker'
|
2
|
+
|
3
|
+
module Aker::Authorities
|
4
|
+
##
|
5
|
+
# This authority provides a uniform entry point for multiple
|
6
|
+
# authorities.
|
7
|
+
#
|
8
|
+
# The documentation on each method describes how the implementation
|
9
|
+
# for a specific authority should work in addition to the way this
|
10
|
+
# authority aggregates results. For any particular authority, all
|
11
|
+
# methods are optional (of course, it would be pointless to
|
12
|
+
# configure in an authority which doesn't implement any of them).
|
13
|
+
class Composite
|
14
|
+
##
|
15
|
+
# The ordered series of authorities whose results this authority
|
16
|
+
# aggregates. The members of this array should implement one or
|
17
|
+
# more of the methods in the authority informal interface (i.e.,
|
18
|
+
# the other methods in this class).
|
19
|
+
#
|
20
|
+
# @return [Array]
|
21
|
+
attr_accessor :authorities
|
22
|
+
|
23
|
+
##
|
24
|
+
# Creates a new instance.
|
25
|
+
#
|
26
|
+
# @param [Aker::Configuration] config the configuration to use
|
27
|
+
# for this authority. The {#authorities} attribute for this
|
28
|
+
# instance will default to {Aker::Configuration#authorities
|
29
|
+
# config.authorities}. Its {Aker::Configuration#portal portal}
|
30
|
+
# (if any) will be used as the {Aker::User#default_portal
|
31
|
+
# default portal} for any authenticated users which don't have
|
32
|
+
# one.
|
33
|
+
def initialize(config)
|
34
|
+
@config = config
|
35
|
+
end
|
36
|
+
|
37
|
+
def authorities
|
38
|
+
@authorities || @config.authorities
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# The main authentication and authorization entry point for an
|
43
|
+
# authority. A concrete authority can return one of four things
|
44
|
+
# from its implementation of this method:
|
45
|
+
#
|
46
|
+
# * A {Aker::User} instance if the credentials are valid. The
|
47
|
+
# instance represents the user that corresponds to these
|
48
|
+
# credentials, with all the attributes and authorization
|
49
|
+
# information that the verifying authority knows about.
|
50
|
+
# * `nil` if the credentials aren't valid according to the
|
51
|
+
# authority.
|
52
|
+
# * `false` if the authority wants to prevent the presenter of
|
53
|
+
# these credentials from authenticating, even if another
|
54
|
+
# authority says they are valid.
|
55
|
+
# * `:unsupported` if the authority can never authenticate
|
56
|
+
# credentials of the provided kind. Semantically this is the
|
57
|
+
# same as `nil`, but it allows `Composite` to provide a useful
|
58
|
+
# debugging message if none of the authorities are capable of
|
59
|
+
# validating a submitted kind.
|
60
|
+
#
|
61
|
+
# The composite implementation provided by this class does the
|
62
|
+
# following:
|
63
|
+
#
|
64
|
+
# * Executes `valid_credentials?` on all the configured authorities
|
65
|
+
# that implement it.
|
66
|
+
# * Returns `false` if any of the authorities return `false`.
|
67
|
+
# * Returns `nil` if none of the authorities returned a user.
|
68
|
+
# * Selects the first user returned by any of the authorities.
|
69
|
+
# * Returns `false` if any of the authorities {#veto? veto} the
|
70
|
+
# user.
|
71
|
+
# * Otherwise returns the user, {#amplify! amplified}.
|
72
|
+
#
|
73
|
+
# On failure, the {#on_authentication_failure} callback is called
|
74
|
+
# on any authority which provides it. Similarly on success, the
|
75
|
+
# {#on_authentication_success} callback is called on all the
|
76
|
+
# authorities which support it.
|
77
|
+
#
|
78
|
+
# @param [Symbol] kind a symbol describing the semantics of the
|
79
|
+
# supplied credentials. Different authorities may support
|
80
|
+
# different kinds (or multiple authorities may support the same
|
81
|
+
# kind).
|
82
|
+
# @param [Array] *credentials the actual credentials. The form of
|
83
|
+
# these is dependent on the kind.
|
84
|
+
#
|
85
|
+
# @return [Aker::User,false,nil] the aggregated result of calling
|
86
|
+
# `valid_credentials?` on all the configured {#authorities}.
|
87
|
+
# If the credentials are valid, the returned user will already
|
88
|
+
# be {#amplify! amplified}.
|
89
|
+
def valid_credentials?(kind, *credentials)
|
90
|
+
results = poll(:valid_credentials?, kind, *credentials)
|
91
|
+
if results.empty?
|
92
|
+
raise "No authentication-providing authority is configured. " <<
|
93
|
+
"At least one authority must implement #valid_credentials?."
|
94
|
+
end
|
95
|
+
user, authenticating_authority = results.detect { |r, authority| r && r != :unsupported }
|
96
|
+
|
97
|
+
unless user
|
98
|
+
msg =
|
99
|
+
if results.collect { |r, auth| r }.uniq == [:unsupported]
|
100
|
+
"no configured authorities support #{kind.inspect} credentials"
|
101
|
+
else
|
102
|
+
"invalid credentials"
|
103
|
+
end
|
104
|
+
on_authentication_failure(nil, kind, credentials, msg)
|
105
|
+
return nil
|
106
|
+
end
|
107
|
+
|
108
|
+
vc_vetoers = results.select { |r, authority| r == false }.collect { |r, authority| authority }
|
109
|
+
unless vc_vetoers.empty?
|
110
|
+
msg = "credentials vetoed by #{vc_vetoers.collect(&:to_s).join(', ')}"
|
111
|
+
on_authentication_failure(user, kind, credentials, msg)
|
112
|
+
return false
|
113
|
+
end
|
114
|
+
|
115
|
+
veto_vetoers = poll(:veto?, user).
|
116
|
+
select { |result, authority| result }.collect { |result, authority| authority }
|
117
|
+
unless veto_vetoers.empty?
|
118
|
+
msg = "user vetoed by #{veto_vetoers.collect(&:to_s).join(', ')}"
|
119
|
+
on_authentication_failure(user, kind, credentials, msg)
|
120
|
+
return false
|
121
|
+
end
|
122
|
+
|
123
|
+
amplify!(user)
|
124
|
+
on_authentication_success(user, kind, credentials, authenticating_authority)
|
125
|
+
user
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# Allows an authority to unconditionally block access for a user.
|
130
|
+
#
|
131
|
+
# If an authority sometimes wishes to prevent authentication for a
|
132
|
+
# user, even if some authority has indicated that he or she has
|
133
|
+
# {#valid_credentials? valid credentials}, it can implement this
|
134
|
+
# method and return true when appropriate.
|
135
|
+
#
|
136
|
+
# The composite behavior aggregates the responses for all
|
137
|
+
# implementing authorities, returning true if any of them return
|
138
|
+
# true.
|
139
|
+
#
|
140
|
+
# @param [Aker::User] user the user who will be declared
|
141
|
+
# authentic unless this method returns true.
|
142
|
+
#
|
143
|
+
# @return [Boolean] true to block access, false otherwise
|
144
|
+
def veto?(user)
|
145
|
+
poll(:veto?, user).detect { |result, authority| result }
|
146
|
+
end
|
147
|
+
|
148
|
+
##
|
149
|
+
# A callback which is invoked when {#valid_credentials?}
|
150
|
+
# is about to return a new user. It has no control over whether
|
151
|
+
# authentication will proceed — it's just a notification.
|
152
|
+
#
|
153
|
+
# The composite behavior is to invoke the callback on all the
|
154
|
+
# authorities which implement it.
|
155
|
+
#
|
156
|
+
# @param [Aker::User] user the user which has been authenticated.
|
157
|
+
# @param [Symbol] kind the kind of credentials (the same value
|
158
|
+
# originally passed to {#valid_credentials?}).
|
159
|
+
# @param [Array] credentials the actual credentials which were
|
160
|
+
# determined to be valid (the same value originally passed to
|
161
|
+
# {#valid_credentials?}).
|
162
|
+
# @param [Object] authenticating_authority the (first) authority
|
163
|
+
# which determined that the credentials were valid.
|
164
|
+
#
|
165
|
+
# @return [void]
|
166
|
+
def on_authentication_success(user, kind, credentials, authenticating_authority)
|
167
|
+
@config.logger.info("User \"#{user.username}\" was successfully authenticated " <<
|
168
|
+
"by #{authenticating_authority.class}.")
|
169
|
+
poll(:on_authentication_success, user, kind, credentials, authenticating_authority)
|
170
|
+
end
|
171
|
+
|
172
|
+
##
|
173
|
+
# A callback which is invoked when {#valid_credentials?} is going
|
174
|
+
# to return `false` or `nil`. It has no control over whether
|
175
|
+
# authentication will proceed — it's just a notification.
|
176
|
+
#
|
177
|
+
# The composite behavior is to invoke the callback on all the
|
178
|
+
# authorities which implement it.
|
179
|
+
#
|
180
|
+
# @param [Aker::User,nil] user the user whose access is being
|
181
|
+
# denied. This may be nil if the authentication failure happens
|
182
|
+
# before the credentials are mapped to a user.
|
183
|
+
# @param [Symbol] kind the kind of credentials (the same value
|
184
|
+
# originally passed to {#valid_credentials?}).
|
185
|
+
# @param [Array] credentials the actual credentials which were
|
186
|
+
# determined to be valid (the same value originally passed to
|
187
|
+
# {#valid_credentials?}).
|
188
|
+
# @param [String] reason the reason why authentication failed,
|
189
|
+
# broadly speaking; e.g., `"invalid credentials"` or `"user vetoed
|
190
|
+
# by MyLockoutAuthority"`.
|
191
|
+
#
|
192
|
+
# @return [void]
|
193
|
+
def on_authentication_failure(user, kind, credentials, reason)
|
194
|
+
@config.logger.info("Authentication attempt#{" by \"#{user.username}\"" if user} " <<
|
195
|
+
"failed: #{reason}.")
|
196
|
+
poll(:on_authentication_failure, user, kind, credentials, reason)
|
197
|
+
end
|
198
|
+
|
199
|
+
##
|
200
|
+
# Fills in any information about the user which the authority
|
201
|
+
# knows but which is not already present in the given object.
|
202
|
+
# In addition to the simple attributes on {Aker::User}, this
|
203
|
+
# method should fill in all available authorization information.
|
204
|
+
#
|
205
|
+
# The composite behavior is to invoke `amplify!` on each of the
|
206
|
+
# configured {#authorities} in series, passing the given user to
|
207
|
+
# each.
|
208
|
+
#
|
209
|
+
# @param [Aker::User] user the user to modify in-place.
|
210
|
+
#
|
211
|
+
# @return [Aker::User] the passed-in user
|
212
|
+
#
|
213
|
+
# @see Aker::User#merge!
|
214
|
+
def amplify!(user)
|
215
|
+
user.default_portal = @config.portal if @config.portal? && !user.default_portal
|
216
|
+
poll(:amplify!, user)
|
217
|
+
user
|
218
|
+
end
|
219
|
+
|
220
|
+
##
|
221
|
+
# Finds users matching the given criteria. Criteria may either be
|
222
|
+
# `String`s or `Hash`es. If it's a single `String`, it is
|
223
|
+
# interpreted as a username and this method will return an array
|
224
|
+
# containing either a single user with that username or an empty
|
225
|
+
# array. If the criteria is a `Hash`, the behavior will be
|
226
|
+
# authority-dependent. However, all the attributes of
|
227
|
+
# {Aker::User} are reserved parameter names — if an
|
228
|
+
# authority interprets the value associated with a {Aker::User}
|
229
|
+
# attribute name, it must be interpreted as an exact-match
|
230
|
+
# criteria for that authority's understanding of that attribute
|
231
|
+
# (whatever it is).
|
232
|
+
#
|
233
|
+
# If more than one criterion is provided, each value is treated
|
234
|
+
# separately according to the description given above for a single
|
235
|
+
# value. The resulting array will contain each matching user
|
236
|
+
# exactly once. It will not be possible to determine from the
|
237
|
+
# results alone which returned user matched which criterion.
|
238
|
+
#
|
239
|
+
# Examples:
|
240
|
+
#
|
241
|
+
# authority.find_users("wakibbe") # => that single user, if
|
242
|
+
# # the username is known
|
243
|
+
# authority.find_users(:first_name => 'Warren')
|
244
|
+
# # => all the users named
|
245
|
+
# # Warren
|
246
|
+
# authority.find_users(
|
247
|
+
# :first_name => 'Warren', :last_name => 'Kibbe')
|
248
|
+
# # => all the users named
|
249
|
+
# # Warren Kibbe
|
250
|
+
# authority.find_users(
|
251
|
+
# { :first_name => 'Warren' }, { :last_name => 'Kibbe' })
|
252
|
+
# # => all the users with
|
253
|
+
# # first name Warren or
|
254
|
+
# # last name Kibbe
|
255
|
+
#
|
256
|
+
# The composite behavior is to invoke `find_users` on all the
|
257
|
+
# authorities which support it and merge the resulting lists. Any
|
258
|
+
# users with the same username are merged using
|
259
|
+
# {Aker::User#merge!}. Finally, all the users are {#amplify!
|
260
|
+
# amplified}.
|
261
|
+
#
|
262
|
+
# This method will always return an array.
|
263
|
+
#
|
264
|
+
# @param [Array<Hash,#to_s>] criteria (see above)
|
265
|
+
#
|
266
|
+
# @return [Array<Aker::User>] the matching users
|
267
|
+
def find_users(*criteria)
|
268
|
+
poll(:find_users, *criteria).
|
269
|
+
collect { |result, authority| result }.
|
270
|
+
compact.
|
271
|
+
inject([]) { |aggregate, users| merge_user_lists!(aggregate, users.compact) }.
|
272
|
+
each { |user| amplify!(user) }
|
273
|
+
end
|
274
|
+
include Support::FindSoleUser
|
275
|
+
|
276
|
+
protected
|
277
|
+
|
278
|
+
def merge_user_lists!(target, new_users)
|
279
|
+
new_users.each do |u|
|
280
|
+
existing = target.find { |t| t.username == u.username }
|
281
|
+
if existing
|
282
|
+
existing.merge!(u)
|
283
|
+
else
|
284
|
+
target << u
|
285
|
+
end
|
286
|
+
end
|
287
|
+
target
|
288
|
+
end
|
289
|
+
|
290
|
+
##
|
291
|
+
# Invokes the specified method with the specified arguments on all
|
292
|
+
# the authorities which will respond to it.
|
293
|
+
def poll(method, *args)
|
294
|
+
authorities.select { |a|
|
295
|
+
a.respond_to?(method)
|
296
|
+
}.collect { |a|
|
297
|
+
[a.send(method, *args), a]
|
298
|
+
}
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
@@ -0,0 +1,283 @@
|
|
1
|
+
require 'aker'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Aker::Authorities
|
5
|
+
##
|
6
|
+
# An authority which is configured entirely in memory. It's not
|
7
|
+
# intended for production, but rather for testing (particularly
|
8
|
+
# integrated testing) and bootstrapping (e.g., for rapidly testing
|
9
|
+
# out aker in an application before setting up the infrastructure
|
10
|
+
# needed for {Ldap} or a custom authority).
|
11
|
+
class Static
|
12
|
+
##
|
13
|
+
# Creates a new instance. Does not use any configuration properties.
|
14
|
+
def initialize(ignored=nil)
|
15
|
+
self.clear
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Creates a new instance from a file. You can use the result of
|
20
|
+
# this method directly in a Aker configuration block. E.g.:
|
21
|
+
#
|
22
|
+
# Aker.configure {
|
23
|
+
# authority Aker::Authorities::Static.from_file(File.expand_path("../static-auth.yml", __FILE__))
|
24
|
+
# }
|
25
|
+
#
|
26
|
+
# @param [String] filename the name of a YAML file containing the
|
27
|
+
# format outlined for {#load!}
|
28
|
+
#
|
29
|
+
# @return [Static] a new instance
|
30
|
+
#
|
31
|
+
# @see #load! the file format
|
32
|
+
def self.from_file(filename)
|
33
|
+
File.open(filename) { |f| self.new.load!(f) }
|
34
|
+
end
|
35
|
+
|
36
|
+
###### AUTHORITY API IMPLEMENTATION
|
37
|
+
|
38
|
+
##
|
39
|
+
# Verifies the credentials against the set provided by calls to
|
40
|
+
# {#valid_credentials!} and {#load!}. Supports all kinds.
|
41
|
+
#
|
42
|
+
# @return [Aker::User, nil]
|
43
|
+
def valid_credentials?(kind, *credentials)
|
44
|
+
found_username =
|
45
|
+
(all_credentials(kind).detect { |c| c[:credentials] == credentials } || {})[:username]
|
46
|
+
@users[found_username]
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Merges in the authorization information in this authority for the
|
51
|
+
# given user.
|
52
|
+
#
|
53
|
+
# @param [Aker::User] user the target user
|
54
|
+
#
|
55
|
+
# @return [Aker::User] the input user, modified
|
56
|
+
def amplify!(user)
|
57
|
+
base = @users[user.username]
|
58
|
+
return user unless base
|
59
|
+
|
60
|
+
user.merge!(base)
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Returns the any users which match the given criteria from the
|
65
|
+
# set that have been loaded with {#load!}, {#valid_credentials!},
|
66
|
+
# and {#user}.
|
67
|
+
#
|
68
|
+
# @param [Array<Hash,#to_s>] criteria as described in
|
69
|
+
# {Composite#find_users}.
|
70
|
+
# @return [Array<Aker::User>]
|
71
|
+
def find_users(*criteria)
|
72
|
+
criteria.collect do |criteria_group|
|
73
|
+
unless Hash === criteria_group
|
74
|
+
criteria_group = { :username => criteria_group.to_s }
|
75
|
+
end
|
76
|
+
props = criteria_group.keys.select { |k|
|
77
|
+
Aker::User.instance_methods.include?(k.to_s) || # for 1.8.7
|
78
|
+
Aker::User.instance_methods.include?(k.to_sym) # for 1.9.1
|
79
|
+
}
|
80
|
+
if props.empty?
|
81
|
+
[]
|
82
|
+
else
|
83
|
+
@users.values.select do |user|
|
84
|
+
props.inject(true) { |result, prop| result && user.send(prop) == criteria_group[prop] }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end.flatten.uniq
|
88
|
+
end
|
89
|
+
|
90
|
+
###### SETUP METHODS
|
91
|
+
|
92
|
+
##
|
93
|
+
# Creates or updates one of the user records in this authority. If
|
94
|
+
# provided a block, the user will be yielded to it. This the
|
95
|
+
# mechanism to use to set attributes, portals, and group
|
96
|
+
# memberships on the users returned by {#valid_credentials?}.
|
97
|
+
# Example:
|
98
|
+
#
|
99
|
+
# auth.user("wakibbe") do |u|
|
100
|
+
# u.first_name = "Warren"
|
101
|
+
# u.portals << :ENU
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# auth.user("wakibbe").first_name # => "Warren"
|
105
|
+
#
|
106
|
+
# @param [String] username the username for the user to create,
|
107
|
+
# update, or just read
|
108
|
+
#
|
109
|
+
# @return [Aker::User] the single user for `username` (possibly
|
110
|
+
# newly created; never nil)
|
111
|
+
#
|
112
|
+
# @see #load!
|
113
|
+
def user(username, &block)
|
114
|
+
u = (@users[username] ||= Aker::User.new(username))
|
115
|
+
u.tap(&block) if block
|
116
|
+
u
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# Associate the given set of credentials of a particular kind with
|
121
|
+
# the specified user. Note that all kinds require a username
|
122
|
+
# (unlike with {#valid_credentials?}). Examples:
|
123
|
+
#
|
124
|
+
# auth.valid_credentials!(:user, "wakibbe", "ekibder")
|
125
|
+
# auth.valid_credentials!(:api_key, "notis-app", "12345-67890")
|
126
|
+
#
|
127
|
+
# @param [Symbol] kind the kind of credentials these are.
|
128
|
+
# Anything is allowed.
|
129
|
+
# @param [String] username the username for the user which is
|
130
|
+
# authenticated by these credentials.
|
131
|
+
# @param [Array<String>,nil] *credentials the credentials
|
132
|
+
# themselves. (Note that you need not repeat the username for
|
133
|
+
# the :user kind.)
|
134
|
+
#
|
135
|
+
# @return [void]
|
136
|
+
def valid_credentials!(kind, username, *credentials)
|
137
|
+
if kind == :user
|
138
|
+
credentials = [username, *credentials]
|
139
|
+
end
|
140
|
+
all_credentials(kind) << { :username => username, :credentials => credentials }
|
141
|
+
@users[username] ||= Aker::User.new(username)
|
142
|
+
end
|
143
|
+
|
144
|
+
##
|
145
|
+
# Loads a YAML doc and uses its contents to initialize the
|
146
|
+
# authority's authentication and authorization data.
|
147
|
+
#
|
148
|
+
# Sample doc:
|
149
|
+
#
|
150
|
+
# users:
|
151
|
+
# wakibbe: # username
|
152
|
+
# password: ekibder # password for :user auth (optional)
|
153
|
+
# first_name: Warren # any attributes from Aker::User may
|
154
|
+
# last_name: Kibbe # be set here
|
155
|
+
# identifiers: # identifiers will be loaded with
|
156
|
+
# employee_id: 4 # symbolized keys
|
157
|
+
# portals: # portal & group auth info (optional)
|
158
|
+
# - SQLSubmit # A groupless portal
|
159
|
+
# - ENU: # A portal with simple groups
|
160
|
+
# - User
|
161
|
+
# - NOTIS: # A portal with affiliated groups
|
162
|
+
# - Manager: [23]
|
163
|
+
# - User # you can mix affiliated and simple
|
164
|
+
#
|
165
|
+
# groups: # groups for hierarchical portals
|
166
|
+
# NOTIS: # (these aren't real NOTIS groups)
|
167
|
+
# - Admin:
|
168
|
+
# - Manager:
|
169
|
+
# - User
|
170
|
+
# - Auditor
|
171
|
+
#
|
172
|
+
# @param [#read] io a readable handle (something that can be passed to
|
173
|
+
# `YAML.load`)
|
174
|
+
#
|
175
|
+
# @return [Static] self
|
176
|
+
def load!(io)
|
177
|
+
doc = YAML.load(io)
|
178
|
+
return self unless doc
|
179
|
+
(doc["groups"] || {}).each do |portal, top_level_groups|
|
180
|
+
@groups[portal.to_sym] = top_level_groups.collect { |group_data| build_group(group_data) }
|
181
|
+
end
|
182
|
+
(doc["users"] || {}).each do |username, config|
|
183
|
+
valid_credentials!(:user, username, config["password"]) if config["password"]
|
184
|
+
user(username) do |u|
|
185
|
+
attr_keys = config.keys
|
186
|
+
(attr_keys - ["password", "portals", "identifiers"]).each do |k|
|
187
|
+
setter = "#{k}="
|
188
|
+
if u.respond_to?(setter)
|
189
|
+
u.send(setter, config[k])
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
(config["portals"] || []).each do |portal_data|
|
194
|
+
portal, group_data =
|
195
|
+
if String === portal_data
|
196
|
+
portal_data
|
197
|
+
else
|
198
|
+
portal_data.to_a.first
|
199
|
+
end
|
200
|
+
|
201
|
+
u.default_portal = portal unless u.default_portal
|
202
|
+
|
203
|
+
u.portals << portal.to_sym
|
204
|
+
if group_data
|
205
|
+
u.group_memberships(portal).concat(load_group_memberships(portal.to_sym, group_data))
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
(config["identifiers"] || {}).each do |ident, value|
|
210
|
+
u.identifiers[ident.to_sym] = value
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
self
|
216
|
+
end
|
217
|
+
|
218
|
+
##
|
219
|
+
# Resets the user and authorization data to the same state it was
|
220
|
+
# in at initialization.
|
221
|
+
#
|
222
|
+
# @return [Static] self
|
223
|
+
def clear
|
224
|
+
@groups = {}
|
225
|
+
@users = {}
|
226
|
+
@credentials = {}
|
227
|
+
self
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
|
232
|
+
def all_credentials(kind)
|
233
|
+
@credentials[kind] ||= []
|
234
|
+
end
|
235
|
+
|
236
|
+
def build_group(group_data)
|
237
|
+
group_name, children =
|
238
|
+
if String === group_data
|
239
|
+
[group_data, []]
|
240
|
+
else
|
241
|
+
group_data.to_a.first
|
242
|
+
end
|
243
|
+
|
244
|
+
group = Aker::Group.new(group_name)
|
245
|
+
children.each do |ch_group_data|
|
246
|
+
group << build_group(ch_group_data)
|
247
|
+
end
|
248
|
+
group
|
249
|
+
end
|
250
|
+
|
251
|
+
##
|
252
|
+
# Transform the group membership info from the static yaml format
|
253
|
+
# into {Aker::GroupMembership} instances.
|
254
|
+
#
|
255
|
+
# @param [Array<String,Hash<String,Array<Fixnum>>>] group_data
|
256
|
+
def load_group_memberships(portal, group_data)
|
257
|
+
group_data.collect do |entry|
|
258
|
+
group, affiliates =
|
259
|
+
if String === entry
|
260
|
+
entry
|
261
|
+
else
|
262
|
+
entry.to_a.first
|
263
|
+
end
|
264
|
+
|
265
|
+
gm = Aker::GroupMembership.new(find_or_create_group(portal, group))
|
266
|
+
if affiliates
|
267
|
+
gm.affiliate_ids = affiliates
|
268
|
+
end
|
269
|
+
gm
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# @return [Aker::Group]
|
274
|
+
def find_or_create_group(portal, group_name)
|
275
|
+
existing = (@groups[portal] ||= []).collect { |top|
|
276
|
+
top.find { |g| g.name == group_name }
|
277
|
+
}.compact.first
|
278
|
+
return existing if existing
|
279
|
+
@groups[portal] << Aker::Group.new(group_name)
|
280
|
+
@groups[portal].last
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'aker/authorities/support'
|
2
|
+
|
3
|
+
module Aker::Authorities::Support
|
4
|
+
##
|
5
|
+
# Provides a singular version of the `find_users` authority method.
|
6
|
+
# Authorities which implement that method can mix this in to get
|
7
|
+
# `find_user` for free.
|
8
|
+
module FindSoleUser
|
9
|
+
##
|
10
|
+
# Finds the sole user which meets the given criteria. If more
|
11
|
+
# than one user meets the criteria, no users are returned.
|
12
|
+
#
|
13
|
+
# @see Aker::Authorities::Composite#find_users
|
14
|
+
# @return [Aker::User,nil] the sole matching user or `nil`
|
15
|
+
def find_user(criteria)
|
16
|
+
result = find_users(criteria)
|
17
|
+
if result.size == 1
|
18
|
+
result.first
|
19
|
+
else
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'aker'
|
2
|
+
|
3
|
+
module Aker
|
4
|
+
##
|
5
|
+
# The namespace for authorities in Aker. The duck-typed definition
|
6
|
+
# of an authority is is outlined in the documentation for
|
7
|
+
# {Aker::Authorities::Composite Composite}.
|
8
|
+
#
|
9
|
+
# Aker ships with four authorities:
|
10
|
+
#
|
11
|
+
# - {Aker::Cas::Authority :cas} provides CAS ticket verification
|
12
|
+
# using a CAS 2 server.
|
13
|
+
# - {Aker::Ldap::Authority :ldap} verifies usernames and
|
14
|
+
# passwords using an LDAP server.
|
15
|
+
# - {Aker::Authorities::Static :static} provides credential
|
16
|
+
# verification and user authorization based on an in-memory set of
|
17
|
+
# users. It can be configured in code or by loading a YAML file.
|
18
|
+
# It is intended for integrated tests and application
|
19
|
+
# bootstrapping.
|
20
|
+
# - {Aker::Authorities::AutomaticAccess :automatic_access} allows
|
21
|
+
# any authenticated user to access your application, even when you
|
22
|
+
# have a portal configured.
|
23
|
+
#
|
24
|
+
# @see Aker::Configuration#authorities=
|
25
|
+
module Authorities
|
26
|
+
autoload :AutomaticAccess, 'aker/authorities/automatic_access'
|
27
|
+
autoload :Composite, 'aker/authorities/composite'
|
28
|
+
autoload :Static, 'aker/authorities/static'
|
29
|
+
|
30
|
+
autoload :Support, 'aker/authorities/support'
|
31
|
+
|
32
|
+
##
|
33
|
+
# The slice that aliases the default authorities.
|
34
|
+
# @private
|
35
|
+
class Slice < Aker::Configuration::Slice
|
36
|
+
def initialize
|
37
|
+
super do
|
38
|
+
alias_authority :automatic_access, Aker::Authorities::AutomaticAccess
|
39
|
+
alias_authority :static, Aker::Authorities::Static
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
Aker::Configuration.add_default_slice(Aker::Authorities::Slice.new)
|