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,56 @@
|
|
|
1
|
+
require 'aker/modes/support'
|
|
2
|
+
require 'erb'
|
|
3
|
+
require 'rack'
|
|
4
|
+
|
|
5
|
+
module Aker::Form
|
|
6
|
+
##
|
|
7
|
+
# Provides HTML and CSS for login forms.
|
|
8
|
+
#
|
|
9
|
+
# @author David Yip
|
|
10
|
+
module LoginFormAssetProvider
|
|
11
|
+
include Rack::Utils
|
|
12
|
+
include Aker::Rack::ConfigurationHelper
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# Where to look for HTML and CSS assets.
|
|
16
|
+
#
|
|
17
|
+
# This is currently hardcoded as `(aker gem root)/assets/aker/form`.
|
|
18
|
+
#
|
|
19
|
+
# @return [String] a directory path
|
|
20
|
+
def asset_root
|
|
21
|
+
File.expand_path(File.join(File.dirname(__FILE__),
|
|
22
|
+
%w(.. .. ..),
|
|
23
|
+
%w(assets aker form)))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# Provides the HTML for the login form.
|
|
28
|
+
#
|
|
29
|
+
# This method expects to find a `login.html.erb` ERB template in
|
|
30
|
+
# {#asset_root}. The ERB template is evaluated in an environment where
|
|
31
|
+
# a local variable named `script_name` is bound to the value of the
|
|
32
|
+
# `SCRIPT_NAME` Rack environment variable, which is useful for CSS and
|
|
33
|
+
# form action URL generation.
|
|
34
|
+
#
|
|
35
|
+
# @param env [Rack environment] a Rack environment
|
|
36
|
+
# @param [Hash] options rendering options
|
|
37
|
+
# @option options [Boolean] :login_failed If true, will render a failure message
|
|
38
|
+
# @option options [Boolean] :logged_out If true, will render a logout notification
|
|
39
|
+
# @option options [String] :username Text for the username field
|
|
40
|
+
# @option options [String] :url A URL to redirect to upon successful login
|
|
41
|
+
# @return [String] HTML data
|
|
42
|
+
def login_html(env, options = {})
|
|
43
|
+
login_base = env['SCRIPT_NAME'] + login_path(env)
|
|
44
|
+
template = File.read(File.join(asset_root, 'login.html.erb'))
|
|
45
|
+
ERB.new(template).result(binding)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Provides the CSS for the login form.
|
|
50
|
+
#
|
|
51
|
+
# @return [String] CSS data
|
|
52
|
+
def login_css
|
|
53
|
+
File.read(File.join(asset_root, 'login.css'))
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'aker'
|
|
2
|
+
|
|
3
|
+
module Aker::Form::Middleware
|
|
4
|
+
##
|
|
5
|
+
# Extends {LoginResponder} to allow the application to re-render the
|
|
6
|
+
# login form when using {CustomViewsMode}.
|
|
7
|
+
class CustomViewLoginResponder < LoginResponder
|
|
8
|
+
protected
|
|
9
|
+
|
|
10
|
+
def unauthenticated(env)
|
|
11
|
+
request = ::Rack::Request.new(env)
|
|
12
|
+
|
|
13
|
+
env['aker.form.login_failed'] = true
|
|
14
|
+
env['aker.form.username'] = request['username']
|
|
15
|
+
|
|
16
|
+
@app.call(env)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'aker'
|
|
2
|
+
|
|
3
|
+
module Aker::Form::Middleware
|
|
4
|
+
##
|
|
5
|
+
# Rack middleware used by {Aker::Form::Mode} to render an HTML login
|
|
6
|
+
# form.
|
|
7
|
+
#
|
|
8
|
+
# This middleware implements half of the form login process. The
|
|
9
|
+
# other half is implemented by {LoginResponder}.
|
|
10
|
+
#
|
|
11
|
+
# @author David Yip
|
|
12
|
+
class LoginRenderer
|
|
13
|
+
include Aker::Form::LoginFormAssetProvider
|
|
14
|
+
include Aker::Rack::ConfigurationHelper
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# Instantiates the middleware.
|
|
18
|
+
#
|
|
19
|
+
# @param app [Rack app] The Rack application on which this middleware
|
|
20
|
+
# should be layered.
|
|
21
|
+
# @param login_path [String] the login path
|
|
22
|
+
def initialize(app)
|
|
23
|
+
@app = app
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# Rack entry point.
|
|
28
|
+
#
|
|
29
|
+
# `call` returns one of three responses, depending on the path and
|
|
30
|
+
# method.
|
|
31
|
+
#
|
|
32
|
+
# * If the method is GET and the path is `login_path`, `call` returns
|
|
33
|
+
# an HTML form for submitting a username and password.
|
|
34
|
+
# * If the method is GET and the path is `login_path + "/login.css"`,
|
|
35
|
+
# `call` returns the CSS for the aforementioned form.
|
|
36
|
+
# * Otherwise, `call` passes the request down through the Rack stack.
|
|
37
|
+
#
|
|
38
|
+
# @return a finished Rack response
|
|
39
|
+
def call(env)
|
|
40
|
+
case [env['REQUEST_METHOD'], env['PATH_INFO']]
|
|
41
|
+
when ['GET', login_path(env)]; provide_login_html(env)
|
|
42
|
+
when ['GET', login_path(env) + '/login.css']; provide_login_css
|
|
43
|
+
else @app.call(env)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
##
|
|
50
|
+
# An HTML form for logging in.
|
|
51
|
+
#
|
|
52
|
+
# @param env the Rack environment
|
|
53
|
+
# @return a finished Rack response
|
|
54
|
+
def provide_login_html(env)
|
|
55
|
+
request = ::Rack::Request.new(env)
|
|
56
|
+
|
|
57
|
+
::Rack::Response.new(
|
|
58
|
+
login_html(env, :url => request['url'], :session_expired => request['session_expired'])
|
|
59
|
+
).finish
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# CSS for the form provided by {provide_login_html}.
|
|
64
|
+
#
|
|
65
|
+
# @return a finished Rack response
|
|
66
|
+
def provide_login_css
|
|
67
|
+
::Rack::Response.new(login_css) do |resp|
|
|
68
|
+
resp['Content-Type'] = 'text/css'
|
|
69
|
+
end.finish
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require 'aker'
|
|
2
|
+
|
|
3
|
+
module Aker::Form::Middleware
|
|
4
|
+
##
|
|
5
|
+
# Rack middleware used by {Aker::Form::Mode} that finishes login
|
|
6
|
+
# requests by rendering a "Login successful" message.
|
|
7
|
+
#
|
|
8
|
+
# This middleware implements half of the form login process. The
|
|
9
|
+
# other half is implemented by {LoginRenderer}.
|
|
10
|
+
#
|
|
11
|
+
# @author David Yip
|
|
12
|
+
class LoginResponder
|
|
13
|
+
include Aker::Form::LoginFormAssetProvider
|
|
14
|
+
include Aker::Rack::ConfigurationHelper
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# Instantiates the middleware.
|
|
18
|
+
#
|
|
19
|
+
# @param app [Rack app] the Rack application on which this middleware
|
|
20
|
+
# should be layered
|
|
21
|
+
def initialize(app)
|
|
22
|
+
@app = app
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Rack entry point. Responds to a `POST` to the configured login
|
|
27
|
+
# path.
|
|
28
|
+
#
|
|
29
|
+
# If the user is authenticated and a URL is given in the `url`
|
|
30
|
+
# parameter, then this action will redirect to `url`.
|
|
31
|
+
#
|
|
32
|
+
# @param env the Rack environment
|
|
33
|
+
# @return a finished Rack response
|
|
34
|
+
def call(env)
|
|
35
|
+
case [env['REQUEST_METHOD'], env['PATH_INFO']]
|
|
36
|
+
when ['POST', login_path(env)]; respond(env)
|
|
37
|
+
else @app.call(env)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
protected
|
|
42
|
+
|
|
43
|
+
def respond(env)
|
|
44
|
+
warden = env['warden']
|
|
45
|
+
|
|
46
|
+
if !warden.authenticated?
|
|
47
|
+
warden.custom_failure!
|
|
48
|
+
unauthenticated(env)
|
|
49
|
+
else
|
|
50
|
+
redirect_to_target(env)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def unauthenticated(env)
|
|
55
|
+
request = Rack::Request.new(env)
|
|
56
|
+
body = login_html(env,
|
|
57
|
+
:login_failed => true,
|
|
58
|
+
:username => request['username'],
|
|
59
|
+
:url => request['url'])
|
|
60
|
+
|
|
61
|
+
::Rack::Response.new(body, 401).finish
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def redirect_to_target(env)
|
|
65
|
+
request = Rack::Request.new(env)
|
|
66
|
+
target = !(request['url'].blank?) ? request['url'] : request.env['SCRIPT_NAME'] + '/'
|
|
67
|
+
|
|
68
|
+
::Rack::Response.new { |resp| resp.redirect(target) }.finish
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'aker'
|
|
2
|
+
|
|
3
|
+
module Aker::Form::Middleware
|
|
4
|
+
class LogoutResponder
|
|
5
|
+
include Aker::Form::LoginFormAssetProvider
|
|
6
|
+
include Aker::Rack::ConfigurationHelper
|
|
7
|
+
|
|
8
|
+
def initialize(app)
|
|
9
|
+
@app = app
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
# When given `GET` to the configured logout path, builds a Rack
|
|
14
|
+
# response containing the login form with a "you have been logged
|
|
15
|
+
# out" notification. Otherwise, passes the response on.
|
|
16
|
+
#
|
|
17
|
+
# @return a finished Rack response
|
|
18
|
+
def call(env)
|
|
19
|
+
if env['REQUEST_METHOD'] == 'GET' && env['PATH_INFO'] == logout_path(env)
|
|
20
|
+
::Rack::Response.new(login_html(env, :logged_out => true)).finish
|
|
21
|
+
else
|
|
22
|
+
@app.call(env)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require 'aker'
|
|
2
|
+
|
|
3
|
+
module Aker::Form
|
|
4
|
+
module Middleware
|
|
5
|
+
autoload :CustomViewLoginResponder, 'aker/form/middleware/custom_view_login_responder'
|
|
6
|
+
autoload :LogoutResponder, 'aker/form/middleware/logout_responder'
|
|
7
|
+
autoload :LoginRenderer, 'aker/form/middleware/login_renderer'
|
|
8
|
+
autoload :LoginResponder, 'aker/form/middleware/login_responder'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require 'aker'
|
|
2
|
+
require 'uri'
|
|
3
|
+
require 'rack'
|
|
4
|
+
|
|
5
|
+
module Aker
|
|
6
|
+
module Form
|
|
7
|
+
##
|
|
8
|
+
# An interactive mode that accepts a username and password POSTed from an
|
|
9
|
+
# HTML form.
|
|
10
|
+
#
|
|
11
|
+
# It expects the username in a `username` parameter and the unobfuscated
|
|
12
|
+
# password in a `password` parameter.
|
|
13
|
+
#
|
|
14
|
+
# By default, the form is rendered at and the credentials are
|
|
15
|
+
# received on '/login'; this can be overridden in the
|
|
16
|
+
# configuration like so:
|
|
17
|
+
#
|
|
18
|
+
# Aker.configure {
|
|
19
|
+
# rack_parameters :login_path => '/log-in-here'
|
|
20
|
+
# }
|
|
21
|
+
#
|
|
22
|
+
# This mode also renders said HTML form if authentication
|
|
23
|
+
# fails. Rendering is handled by by {Middleware::LoginRenderer}.
|
|
24
|
+
#
|
|
25
|
+
# @author David Yip
|
|
26
|
+
class Mode < Aker::Modes::Base
|
|
27
|
+
include ::Rack::Utils
|
|
28
|
+
include Aker::Modes::Support::AttemptedPath
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# A key that refers to this mode; used for configuration convenience.
|
|
32
|
+
#
|
|
33
|
+
# @return [Symbol]
|
|
34
|
+
def self.key
|
|
35
|
+
:form
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# Appends the {Middleware::LoginResponder login responder} to its
|
|
40
|
+
# position in the Rack middleware stack.
|
|
41
|
+
def self.append_middleware(builder)
|
|
42
|
+
builder.use(Middleware::LoginResponder)
|
|
43
|
+
builder.use(Middleware::LogoutResponder)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
##
|
|
47
|
+
# Prepends the {Middleware::LoginRenderer login form renderer} to
|
|
48
|
+
# its position in the Rack middleware stack.
|
|
49
|
+
def self.prepend_middleware(builder)
|
|
50
|
+
builder.use(Middleware::LoginRenderer)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
# The type of credentials supplied by this mode.
|
|
55
|
+
#
|
|
56
|
+
# @return [Symbol]
|
|
57
|
+
def kind
|
|
58
|
+
:user
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# Extracts username and password from request parameters.
|
|
63
|
+
#
|
|
64
|
+
# @return [Array<String>] username and password, username (if password
|
|
65
|
+
# missing), or an empty array
|
|
66
|
+
def credentials
|
|
67
|
+
[request['username'], request['password']].compact
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
# Returns true if username and password are present, false otherwise.
|
|
72
|
+
def valid?
|
|
73
|
+
credentials.length == 2
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
##
|
|
77
|
+
# The absolute URL for the login form.
|
|
78
|
+
#
|
|
79
|
+
# @return [String]
|
|
80
|
+
def login_url
|
|
81
|
+
uri = URI.parse(request.url)
|
|
82
|
+
uri.path = env['SCRIPT_NAME'] + login_path(configuration)
|
|
83
|
+
uri.to_s
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
##
|
|
87
|
+
# Builds a Rack response that redirects to the login form.
|
|
88
|
+
#
|
|
89
|
+
# @return [Rack::Response]
|
|
90
|
+
def on_ui_failure
|
|
91
|
+
::Rack::Response.new do |resp|
|
|
92
|
+
target = login_url + '?url=' + escape(attempted_path)
|
|
93
|
+
if env['aker.session_expired']
|
|
94
|
+
target += '&session_expired=true'
|
|
95
|
+
end
|
|
96
|
+
resp.redirect(target)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
##
|
|
101
|
+
# The path at which the login form will be accessible, as
|
|
102
|
+
# configured in the specified context.
|
|
103
|
+
#
|
|
104
|
+
# This path is specified relative to the application's mount point. If
|
|
105
|
+
# you're looking for the absolute URL of the login form, you need to use
|
|
106
|
+
# {#login_url}.
|
|
107
|
+
#
|
|
108
|
+
# @param [Aker::Configuration] configuration the configuration
|
|
109
|
+
# from which to derive the login path.
|
|
110
|
+
#
|
|
111
|
+
# @return [String]
|
|
112
|
+
def login_path(configuration)
|
|
113
|
+
configuration.parameters_for(:rack)[:login_path]
|
|
114
|
+
end
|
|
115
|
+
private :login_path
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
data/lib/aker/form.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'aker'
|
|
2
|
+
|
|
3
|
+
module Aker
|
|
4
|
+
##
|
|
5
|
+
# The Aker mode that supports a traditional HTML login form, and its
|
|
6
|
+
# support infrastructure.
|
|
7
|
+
module Form
|
|
8
|
+
autoload :CustomViewsMode, 'aker/form/custom_views_mode'
|
|
9
|
+
autoload :LoginFormAssetProvider, 'aker/form/login_form_asset_provider'
|
|
10
|
+
autoload :Middleware, 'aker/form/middleware'
|
|
11
|
+
autoload :Mode, 'aker/form/mode'
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# @private
|
|
15
|
+
class Slice < Aker::Configuration::Slice
|
|
16
|
+
def initialize
|
|
17
|
+
super do
|
|
18
|
+
register_mode Mode
|
|
19
|
+
register_mode CustomViewsMode
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
Aker::Configuration.add_default_slice(Aker::Form::Slice.new)
|
data/lib/aker/group.rb
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require 'tree'
|
|
2
|
+
|
|
3
|
+
require 'aker'
|
|
4
|
+
|
|
5
|
+
module Aker
|
|
6
|
+
##
|
|
7
|
+
# The authority-independent representation of a group.
|
|
8
|
+
#
|
|
9
|
+
# Groups can be related in a tree. If so, a membership in an
|
|
10
|
+
# ancestor group implies membership in all its descendents.
|
|
11
|
+
#
|
|
12
|
+
# @see http://rubytree.rubyforge.org/rdoc/Tree/TreeNode.html
|
|
13
|
+
class Group < Tree::TreeNode
|
|
14
|
+
##
|
|
15
|
+
# Creates a new group with the given name. You can add children
|
|
16
|
+
# using `<<`.
|
|
17
|
+
#
|
|
18
|
+
# @param [#to_s] name the desired name
|
|
19
|
+
# @param [Array,nil] args additional arguments. Included for
|
|
20
|
+
# marshalling compatibility with the base class.
|
|
21
|
+
def initialize(name, *args)
|
|
22
|
+
super # overridden to attach docs
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Determines whether this group or any of its children matches the
|
|
27
|
+
# given parameter for authorization purposes.
|
|
28
|
+
#
|
|
29
|
+
# @param [#to_s,Group] other the thing to compare this
|
|
30
|
+
# group to
|
|
31
|
+
# @return [Boolean] true if the name of this group or any of its
|
|
32
|
+
# children is a case-insensitive match for the other.
|
|
33
|
+
def include?(other)
|
|
34
|
+
other_name =
|
|
35
|
+
case other
|
|
36
|
+
when Group; other.name;
|
|
37
|
+
else other.to_s;
|
|
38
|
+
end
|
|
39
|
+
self.find { |g| g.name.downcase == other_name.downcase }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# Copy-pasted from parent in order to use appropriate class when
|
|
44
|
+
# deserializing children.
|
|
45
|
+
#
|
|
46
|
+
# @private
|
|
47
|
+
def marshal_load(dumped_tree_array)
|
|
48
|
+
nodes = { }
|
|
49
|
+
|
|
50
|
+
for node_hash in dumped_tree_array do
|
|
51
|
+
name = node_hash[:name]
|
|
52
|
+
parent_name = node_hash[:parent]
|
|
53
|
+
content = Marshal.load(node_hash[:content])
|
|
54
|
+
|
|
55
|
+
if parent_name then
|
|
56
|
+
nodes[name] = current_node = self.class.new(name, content)
|
|
57
|
+
nodes[parent_name].add current_node
|
|
58
|
+
else
|
|
59
|
+
# This is the root node, hence initialize self.
|
|
60
|
+
initialize(name, content)
|
|
61
|
+
|
|
62
|
+
nodes[name] = self # Add self to the list of nodes
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
require 'aker'
|
|
2
|
+
|
|
3
|
+
module Aker
|
|
4
|
+
##
|
|
5
|
+
# The authority-independent representation of a user's association
|
|
6
|
+
# with a particular group, possibly constrained by affiliate.
|
|
7
|
+
class GroupMembership
|
|
8
|
+
##
|
|
9
|
+
# The affiliate IDs to which this membership is scoped. If this
|
|
10
|
+
# array is blank or nil, the membership applies to all affiliates.
|
|
11
|
+
#
|
|
12
|
+
# An "affiliate" is an arbitrary scope designator for a
|
|
13
|
+
# membership. The specific form will depend on the authority that
|
|
14
|
+
# is authorizing the user.
|
|
15
|
+
#
|
|
16
|
+
# @return [Array<Object>]
|
|
17
|
+
attr_accessor :affiliate_ids
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# Create a new instance.
|
|
21
|
+
#
|
|
22
|
+
# @param [Group] group the group for which this object records
|
|
23
|
+
# membership
|
|
24
|
+
def initialize(group)
|
|
25
|
+
@group = group
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# Determines whether this membership applies to the given
|
|
30
|
+
# affiliate.
|
|
31
|
+
#
|
|
32
|
+
# @param [Object] affiliate_id
|
|
33
|
+
# @return [Boolean]
|
|
34
|
+
def include_affiliate?(affiliate_id)
|
|
35
|
+
affiliate_ids.blank? ? true : affiliate_ids.include?(affiliate_id)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# @return [String] the name of the group for which this object
|
|
40
|
+
# indicates membership.
|
|
41
|
+
def group_name
|
|
42
|
+
self.group.name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# @return [Group] the group for which this is a membership
|
|
47
|
+
def group
|
|
48
|
+
@group
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def affiliate_ids
|
|
52
|
+
@affiliate_ids ||= []
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# An authority-independent collection of all the group memberships
|
|
58
|
+
# for a particular user at a particular portal.
|
|
59
|
+
class GroupMemberships < Array
|
|
60
|
+
##
|
|
61
|
+
# The portal for which all these group memberships apply.
|
|
62
|
+
#
|
|
63
|
+
# @return [Symbol]
|
|
64
|
+
attr_reader :portal
|
|
65
|
+
|
|
66
|
+
# n.b.: if you add more attributes, be sure to add them to the
|
|
67
|
+
# custom serialization.
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# Create a new instance.
|
|
71
|
+
#
|
|
72
|
+
# @param [#to_sym] portal
|
|
73
|
+
def initialize(portal)
|
|
74
|
+
@portal = portal.to_sym
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
# Determines whether this collection indicates that the user is
|
|
79
|
+
# authorized in the the given group, possibly constrained by one
|
|
80
|
+
# or more affiliates.
|
|
81
|
+
#
|
|
82
|
+
# (Note that this method hides the superclass `include?` method.)
|
|
83
|
+
#
|
|
84
|
+
# @param [Group,#to_s] group the group in question or its name
|
|
85
|
+
# @param [Array<Object>,nil] *affiliate_ids the affiliates to use to
|
|
86
|
+
# constrain the query.
|
|
87
|
+
#
|
|
88
|
+
# @return [Boolean] true so long as the user is authorized in
|
|
89
|
+
# `group` for **at least one** of the specified affiliates. If
|
|
90
|
+
# no affiliates are specified, only the groups themselves are
|
|
91
|
+
# considered.
|
|
92
|
+
def include?(group, *affiliate_ids)
|
|
93
|
+
!find(group, *affiliate_ids).empty?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
##
|
|
97
|
+
# Finds the group memberships that match the given group, possibly
|
|
98
|
+
# constrained by one or more affiliates.
|
|
99
|
+
#
|
|
100
|
+
# (Note that this method hides the `Enumerable` method `find`.
|
|
101
|
+
# You can still use it under its `detect` alias.)
|
|
102
|
+
#
|
|
103
|
+
# @param [Group,#to_s] group the group in question or its name
|
|
104
|
+
# @param [Array<Object>,nil] *affiliate_ids the affiliates to use to
|
|
105
|
+
# constrain the query.
|
|
106
|
+
#
|
|
107
|
+
# @return [Array<GroupMembership>]
|
|
108
|
+
def find(group, *affiliate_ids)
|
|
109
|
+
candidates = self.select { |gm| gm.group.include?(group) }
|
|
110
|
+
return candidates if affiliate_ids.empty?
|
|
111
|
+
candidates.select { |gm| affiliate_ids.detect { |id| gm.include_affiliate?(id) } }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
##
|
|
115
|
+
# Custom serialization for this array. Needed because we need to
|
|
116
|
+
# serialize the full tree for all referenced groups in order to be
|
|
117
|
+
# able to do {#include?} and {#find} correctly on the deserialized
|
|
118
|
+
# result.
|
|
119
|
+
#
|
|
120
|
+
# @return [Hash] suitable for passing to {#marshal_load}
|
|
121
|
+
def marshal_dump
|
|
122
|
+
{
|
|
123
|
+
:group_roots => find_group_roots,
|
|
124
|
+
:memberships => dump_gm_hashes,
|
|
125
|
+
:portal => portal
|
|
126
|
+
}
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
##
|
|
130
|
+
# Custom deserialization for this array. Reverses
|
|
131
|
+
# {#marshal_dump}.
|
|
132
|
+
#
|
|
133
|
+
# @return [void]
|
|
134
|
+
def marshal_load(dump)
|
|
135
|
+
@portal = dump[:portal]
|
|
136
|
+
roots = dump[:group_roots]
|
|
137
|
+
dump[:memberships].each do |gm_hash|
|
|
138
|
+
self << GroupMembership.new(find_group_from_roots(gm_hash[:group_name], roots)).
|
|
139
|
+
tap { |gm| gm.affiliate_ids.concat(gm_hash[:affiliate_ids]) }
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private
|
|
144
|
+
|
|
145
|
+
def find_group_from_roots(group_name, roots)
|
|
146
|
+
roots.each do |root|
|
|
147
|
+
root.each do |group|
|
|
148
|
+
return group if group.name == group_name
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
raise "Could not find #{group_name} in any of the roots (#{roots.inspect})"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def find_group_roots
|
|
155
|
+
self.collect { |gm| gm.group.root }.uniq
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def dump_gm_hashes
|
|
159
|
+
self.collect { |gm| { :group_name => gm.group_name, :affiliate_ids => gm.affiliate_ids } }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|