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