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,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)
|