aker 3.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data/CHANGELOG.md +210 -0
  2. data/README.md +282 -0
  3. data/assets/aker/form/login.css +73 -0
  4. data/assets/aker/form/login.html.erb +44 -0
  5. data/lib/aker/authorities/automatic_access.rb +36 -0
  6. data/lib/aker/authorities/composite.rb +301 -0
  7. data/lib/aker/authorities/static.rb +283 -0
  8. data/lib/aker/authorities/support/find_sole_user.rb +24 -0
  9. data/lib/aker/authorities/support.rb +9 -0
  10. data/lib/aker/authorities.rb +46 -0
  11. data/lib/aker/cas/authority.rb +79 -0
  12. data/lib/aker/cas/configuration_helper.rb +85 -0
  13. data/lib/aker/cas/middleware/logout_responder.rb +49 -0
  14. data/lib/aker/cas/middleware/ticket_remover.rb +35 -0
  15. data/lib/aker/cas/middleware.rb +6 -0
  16. data/lib/aker/cas/proxy_mode.rb +108 -0
  17. data/lib/aker/cas/rack_proxy_callback.rb +188 -0
  18. data/lib/aker/cas/service_mode.rb +88 -0
  19. data/lib/aker/cas/service_url.rb +62 -0
  20. data/lib/aker/cas/user_ext.rb +64 -0
  21. data/lib/aker/cas.rb +31 -0
  22. data/lib/aker/central_parameters.rb +101 -0
  23. data/lib/aker/configuration.rb +534 -0
  24. data/lib/aker/deprecation.rb +105 -0
  25. data/lib/aker/form/custom_views_mode.rb +80 -0
  26. data/lib/aker/form/login_form_asset_provider.rb +56 -0
  27. data/lib/aker/form/middleware/custom_view_login_responder.rb +19 -0
  28. data/lib/aker/form/middleware/login_renderer.rb +72 -0
  29. data/lib/aker/form/middleware/login_responder.rb +71 -0
  30. data/lib/aker/form/middleware/logout_responder.rb +26 -0
  31. data/lib/aker/form/middleware.rb +10 -0
  32. data/lib/aker/form/mode.rb +118 -0
  33. data/lib/aker/form.rb +26 -0
  34. data/lib/aker/group.rb +67 -0
  35. data/lib/aker/group_membership.rb +162 -0
  36. data/lib/aker/ldap/authority.rb +392 -0
  37. data/lib/aker/ldap/user_ext.rb +19 -0
  38. data/lib/aker/ldap.rb +22 -0
  39. data/lib/aker/modes/base.rb +85 -0
  40. data/lib/aker/modes/http_basic.rb +100 -0
  41. data/lib/aker/modes/support/attempted_path.rb +22 -0
  42. data/lib/aker/modes/support/rfc_2617.rb +32 -0
  43. data/lib/aker/modes/support.rb +12 -0
  44. data/lib/aker/modes.rb +48 -0
  45. data/lib/aker/rack/authenticate.rb +37 -0
  46. data/lib/aker/rack/configuration_helper.rb +18 -0
  47. data/lib/aker/rack/default_logout_responder.rb +36 -0
  48. data/lib/aker/rack/environment_helper.rb +34 -0
  49. data/lib/aker/rack/facade.rb +102 -0
  50. data/lib/aker/rack/failure.rb +69 -0
  51. data/lib/aker/rack/logout.rb +63 -0
  52. data/lib/aker/rack/request_ext.rb +19 -0
  53. data/lib/aker/rack/session_timer.rb +95 -0
  54. data/lib/aker/rack/setup.rb +77 -0
  55. data/lib/aker/rack.rb +107 -0
  56. data/lib/aker/test/helpers.rb +22 -0
  57. data/lib/aker/test.rb +8 -0
  58. data/lib/aker/user.rb +231 -0
  59. data/lib/aker/version.rb +3 -0
  60. data/lib/aker.rb +51 -0
  61. data/spec/aker/aker-sample.yml +11 -0
  62. data/spec/aker/authorities/automatic_access_spec.rb +52 -0
  63. data/spec/aker/authorities/composite_spec.rb +488 -0
  64. data/spec/aker/authorities/nu-schema.jar +0 -0
  65. data/spec/aker/authorities/static_spec.rb +455 -0
  66. data/spec/aker/authorities/support/find_sole_user_spec.rb +33 -0
  67. data/spec/aker/authorities_spec.rb +16 -0
  68. data/spec/aker/cas/authority_spec.rb +106 -0
  69. data/spec/aker/cas/configuration_helper_spec.rb +92 -0
  70. data/spec/aker/cas/middleware/logout_responder_spec.rb +47 -0
  71. data/spec/aker/cas/middleware/ticket_remover_spec.rb +49 -0
  72. data/spec/aker/cas/proxy_mode_spec.rb +185 -0
  73. data/spec/aker/cas/rack_proxy_callback_spec.rb +190 -0
  74. data/spec/aker/cas/service_mode_spec.rb +122 -0
  75. data/spec/aker/cas/service_url_spec.rb +114 -0
  76. data/spec/aker/cas/user_ext_spec.rb +27 -0
  77. data/spec/aker/cas_spec.rb +19 -0
  78. data/spec/aker/central_parameters_spec.rb +44 -0
  79. data/spec/aker/configuration_spec.rb +465 -0
  80. data/spec/aker/deprecation_spec.rb +115 -0
  81. data/spec/aker/form/a_form_mode.rb +129 -0
  82. data/spec/aker/form/custom_views_mode_spec.rb +34 -0
  83. data/spec/aker/form/login_form_asset_provider_spec.rb +80 -0
  84. data/spec/aker/form/middleware/a_form_login_responder.rb +89 -0
  85. data/spec/aker/form/middleware/custom_view_login_responder_spec.rb +47 -0
  86. data/spec/aker/form/middleware/login_renderer_spec.rb +56 -0
  87. data/spec/aker/form/middleware/login_responder_spec.rb +34 -0
  88. data/spec/aker/form/middleware/logout_responder_spec.rb +55 -0
  89. data/spec/aker/form/mode_spec.rb +15 -0
  90. data/spec/aker/form_spec.rb +11 -0
  91. data/spec/aker/group_membership_spec.rb +208 -0
  92. data/spec/aker/group_spec.rb +66 -0
  93. data/spec/aker/ldap/authority_spec.rb +414 -0
  94. data/spec/aker/ldap/ldap-users.ldif +197 -0
  95. data/spec/aker/ldap_spec.rb +11 -0
  96. data/spec/aker/modes/a_aker_mode.rb +41 -0
  97. data/spec/aker/modes/http_basic_spec.rb +127 -0
  98. data/spec/aker/modes/support/attempted_path_spec.rb +32 -0
  99. data/spec/aker/modes_spec.rb +11 -0
  100. data/spec/aker/rack/authenticate_spec.rb +78 -0
  101. data/spec/aker/rack/default_logout_responder_spec.rb +67 -0
  102. data/spec/aker/rack/facade_spec.rb +154 -0
  103. data/spec/aker/rack/failure_spec.rb +151 -0
  104. data/spec/aker/rack/logout_spec.rb +63 -0
  105. data/spec/aker/rack/request_ext_spec.rb +29 -0
  106. data/spec/aker/rack/session_timer_spec.rb +134 -0
  107. data/spec/aker/rack/setup_spec.rb +87 -0
  108. data/spec/aker/rack_spec.rb +216 -0
  109. data/spec/aker/test/helpers_spec.rb +44 -0
  110. data/spec/aker/user_spec.rb +362 -0
  111. data/spec/aker_spec.rb +80 -0
  112. data/spec/deprecation_helper.rb +58 -0
  113. data/spec/java_helper.rb +5 -0
  114. data/spec/logger_helper.rb +17 -0
  115. data/spec/matchers.rb +31 -0
  116. data/spec/mock_builder.rb +25 -0
  117. data/spec/spec_helper.rb +52 -0
  118. metadata +265 -0
@@ -0,0 +1,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 &mdash; 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 &mdash; 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 &mdash; 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,9 @@
1
+ require 'aker/authorities'
2
+
3
+ module Aker::Authorities
4
+ ##
5
+ # Library code shared by authorities lives here.
6
+ module Support
7
+ autoload :FindSoleUser, 'aker/authorities/support/find_sole_user'
8
+ end
9
+ 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)