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
data/lib/aker/modes.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'aker'
|
|
2
|
+
|
|
3
|
+
module Aker
|
|
4
|
+
##
|
|
5
|
+
# Mode support code.
|
|
6
|
+
#
|
|
7
|
+
# A mode implements an authentication protocol, and is classified as a _UI
|
|
8
|
+
# mode_, an _API mode_, or both. UI modes are intended for interactive use;
|
|
9
|
+
# API modes are intended for non-interactive use.
|
|
10
|
+
#
|
|
11
|
+
# Aker ships with five modes:
|
|
12
|
+
#
|
|
13
|
+
# - {Aker::Cas::ServiceMode :cas} is a UI mode that provides interactive login via
|
|
14
|
+
# a CAS server.
|
|
15
|
+
# - {Aker::Cas::ProxyMode :cas_proxy} is an API mode that implements the
|
|
16
|
+
# CAS proxying protocol.
|
|
17
|
+
# - {Aker::Form::Mode :form} is a UI mode that provides an HTML form that
|
|
18
|
+
# prompts for username and password.
|
|
19
|
+
# - {Aker::Form::CustomViewsMode :custom_form} is a specialization
|
|
20
|
+
# of `:form` for apps that wish to provide their own form views.
|
|
21
|
+
# - {Aker::Modes::HttpBasic :http_basic} is an API/UI mode that implements
|
|
22
|
+
# the HTTP Basic authentication protocol. (It's both an API and UI mode
|
|
23
|
+
# because it can be used by automated Web clients and humans alike.)
|
|
24
|
+
#
|
|
25
|
+
# Aker permits applications to use as many API modes as they wish,
|
|
26
|
+
# but requires that applications have one and only one UI mode. The
|
|
27
|
+
# default UI mode is `:form`.
|
|
28
|
+
#
|
|
29
|
+
# @see Aker::Configuration#ui_mode=
|
|
30
|
+
# @see Aker::Configuration#api_modes=
|
|
31
|
+
module Modes
|
|
32
|
+
autoload :Base, 'aker/modes/base'
|
|
33
|
+
autoload :HttpBasic, 'aker/modes/http_basic'
|
|
34
|
+
autoload :Support, 'aker/modes/support'
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# @private
|
|
38
|
+
class Slice < Aker::Configuration::Slice
|
|
39
|
+
def initialize
|
|
40
|
+
super do
|
|
41
|
+
register_mode Aker::Modes::HttpBasic
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Aker::Configuration.add_default_slice(Aker::Modes::Slice.new)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'aker'
|
|
2
|
+
|
|
3
|
+
module Aker::Rack
|
|
4
|
+
##
|
|
5
|
+
# The middleware which actually performs authentication according to
|
|
6
|
+
# the mode that applies to the request (if any). Most of the heavy
|
|
7
|
+
# lifting is performed by Warden.
|
|
8
|
+
class Authenticate
|
|
9
|
+
include EnvironmentHelper
|
|
10
|
+
|
|
11
|
+
def initialize(app)
|
|
12
|
+
@app = app
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# Authenticates incoming requests using Warden.
|
|
17
|
+
#
|
|
18
|
+
# Additionally, this class exposes the `aker.check` environment
|
|
19
|
+
# variable to downstream middleware and the app. It is an
|
|
20
|
+
# instance of {Aker::Rack::Facade} permitting authentication and
|
|
21
|
+
# authorization queries about the current user (if any).
|
|
22
|
+
def call(env)
|
|
23
|
+
configuration = configuration(env)
|
|
24
|
+
warden = env['warden']
|
|
25
|
+
|
|
26
|
+
if interactive?(env)
|
|
27
|
+
warden.authenticate(configuration.ui_mode)
|
|
28
|
+
else
|
|
29
|
+
warden.authenticate(*configuration.api_modes)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
env['aker.check'] = Facade.new(configuration, warden.user)
|
|
33
|
+
|
|
34
|
+
@app.call(env)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'aker'
|
|
2
|
+
|
|
3
|
+
module Aker::Rack
|
|
4
|
+
##
|
|
5
|
+
# Methods used by Rack middleware for reading configuration data out of the
|
|
6
|
+
# Rack environment.
|
|
7
|
+
module ConfigurationHelper
|
|
8
|
+
include EnvironmentHelper
|
|
9
|
+
|
|
10
|
+
def login_path(env)
|
|
11
|
+
configuration(env).parameters_for(:rack)[:login_path]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def logout_path(env)
|
|
15
|
+
configuration(env).parameters_for(:rack)[:logout_path]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'aker'
|
|
2
|
+
|
|
3
|
+
module Aker::Rack
|
|
4
|
+
##
|
|
5
|
+
# Provides a default response for `GET` of the application's
|
|
6
|
+
# configured logout path.
|
|
7
|
+
class DefaultLogoutResponder
|
|
8
|
+
include ConfigurationHelper
|
|
9
|
+
|
|
10
|
+
##
|
|
11
|
+
# Instantiates the middleware.
|
|
12
|
+
#
|
|
13
|
+
# @param app [Rack app] the Rack application on which this middleware
|
|
14
|
+
# should be layered
|
|
15
|
+
def initialize(app)
|
|
16
|
+
@app = app
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# When the path is the configured logout path, renders a logout
|
|
21
|
+
# response.
|
|
22
|
+
#
|
|
23
|
+
# @param env [Hash] a Rack environment
|
|
24
|
+
def call(env)
|
|
25
|
+
result = @app.call(env)
|
|
26
|
+
|
|
27
|
+
if result.first == 404 &&
|
|
28
|
+
env['REQUEST_METHOD'] == 'GET' &&
|
|
29
|
+
env['PATH_INFO'] == logout_path(env)
|
|
30
|
+
::Rack::Response.new('You have been logged out.', 200).finish
|
|
31
|
+
else
|
|
32
|
+
result
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'aker'
|
|
2
|
+
|
|
3
|
+
module Aker::Rack
|
|
4
|
+
##
|
|
5
|
+
# Methods used by Rack middleware for reading Aker data out of the Rack
|
|
6
|
+
# environment.
|
|
7
|
+
module EnvironmentHelper
|
|
8
|
+
##
|
|
9
|
+
# Returns the {Configuration} instance for the current request.
|
|
10
|
+
#
|
|
11
|
+
# @return [Aker::Configuration]
|
|
12
|
+
def configuration(env)
|
|
13
|
+
env['aker.configuration']
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# Whether the current request is interactive.
|
|
18
|
+
#
|
|
19
|
+
# @see Aker::Rack::Setup#call
|
|
20
|
+
# @see Aker::Rack::Setup#interactive?
|
|
21
|
+
# @return [Boolean]
|
|
22
|
+
def interactive?(env)
|
|
23
|
+
env['aker.interactive']
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# The authority to use for credential validation.
|
|
28
|
+
#
|
|
29
|
+
# @return [Object]
|
|
30
|
+
def authority(env)
|
|
31
|
+
env['aker.authority']
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require 'aker/rack'
|
|
2
|
+
|
|
3
|
+
module Aker::Rack
|
|
4
|
+
##
|
|
5
|
+
# Provides a simple interface which aker-using rack apps may use to
|
|
6
|
+
# indicate that authentication or authorization is required for a
|
|
7
|
+
# particular action.
|
|
8
|
+
#
|
|
9
|
+
# An instance of this class is available in the rack environment
|
|
10
|
+
# under the `"aker"` key.
|
|
11
|
+
class Facade
|
|
12
|
+
##
|
|
13
|
+
# The current authenticated user.
|
|
14
|
+
#
|
|
15
|
+
# @return [Aker::User]
|
|
16
|
+
attr_accessor :user
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
# The aker configuration in effect for this application.
|
|
20
|
+
#
|
|
21
|
+
# @return [Aker::Configuration]
|
|
22
|
+
attr_accessor :configuration
|
|
23
|
+
|
|
24
|
+
def initialize(config, user)
|
|
25
|
+
@configuration = config
|
|
26
|
+
@user = user
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Indicates that authentication is required for a particular
|
|
31
|
+
# request. If the user is not authenticated, any application code
|
|
32
|
+
# after this method is called will not be executed. The user will
|
|
33
|
+
# be directed to authenticate according to their access style
|
|
34
|
+
# (ui vs. api) and the application configuration (i.e., the
|
|
35
|
+
# appropriate {Modes mode}).
|
|
36
|
+
#
|
|
37
|
+
# If the application has a {Aker::Configuration#portal portal}
|
|
38
|
+
# configured, aker will also check that the user has access to
|
|
39
|
+
# that portal. If the user is authenticated but does not have
|
|
40
|
+
# access to the portal, she will get a `403 Forbidden` response.
|
|
41
|
+
#
|
|
42
|
+
# @see #authenticated?
|
|
43
|
+
# @return [void]
|
|
44
|
+
def authentication_required!
|
|
45
|
+
throw :warden, inauthentic_reason unless authenticated?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Returns true if there is an authenticated user, false otherwise.
|
|
50
|
+
# This check follows the same rules as
|
|
51
|
+
# {#authentication_required!}, including the portal check.
|
|
52
|
+
# However, it does not halt processing if the user is not
|
|
53
|
+
# authenticated.
|
|
54
|
+
#
|
|
55
|
+
# @return [Boolean]
|
|
56
|
+
def authenticated?
|
|
57
|
+
inauthentic_reason.nil?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
##
|
|
61
|
+
# A shortcut to invoking {Aker::User#permit?} on the {#user
|
|
62
|
+
# current user}. As with that method, the block is optional.
|
|
63
|
+
#
|
|
64
|
+
# This method safely handles the case where there is no user
|
|
65
|
+
# logged in.
|
|
66
|
+
#
|
|
67
|
+
# @param [Array<#to_sym>] groups
|
|
68
|
+
# @return [Boolean, Object, nil] `nil` if there's no one logged in;
|
|
69
|
+
# otherwise the same as {Aker::User#permit?}.
|
|
70
|
+
def permit?(*groups, &block)
|
|
71
|
+
return nil unless user
|
|
72
|
+
user.permit?(*groups, &block)
|
|
73
|
+
end
|
|
74
|
+
alias :permit :permit?
|
|
75
|
+
|
|
76
|
+
##
|
|
77
|
+
# Indicates that a user must be in one of the specified groups to
|
|
78
|
+
# proceed. If there is a user logged in and she is not in any of
|
|
79
|
+
# the specified groups, she will get a `403 Forbidden` response.
|
|
80
|
+
# If the user is not logged in, she will be prompted to log in
|
|
81
|
+
# (just like with {#authentication_required!}).
|
|
82
|
+
#
|
|
83
|
+
# @return [void]
|
|
84
|
+
def permit!(*groups)
|
|
85
|
+
authentication_required!
|
|
86
|
+
throw :warden, :groups_required => groups unless user.permit?(*groups)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def inauthentic_reason
|
|
92
|
+
@inauthentic_reason =
|
|
93
|
+
if !user
|
|
94
|
+
{ :login_required => true }
|
|
95
|
+
elsif !configuration.portal? || user.may_access?(configuration.portal)
|
|
96
|
+
nil
|
|
97
|
+
else
|
|
98
|
+
{ :portal_required => configuration.portal }
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require 'aker/rack'
|
|
2
|
+
require 'warden'
|
|
3
|
+
|
|
4
|
+
module Aker::Rack
|
|
5
|
+
##
|
|
6
|
+
# The Rack endpoint which handles authentication failures.
|
|
7
|
+
#
|
|
8
|
+
# @see Aker::Rack.use_in
|
|
9
|
+
# @see http://wiki.github.com/hassox/warden/failures
|
|
10
|
+
# Warden failures documentation
|
|
11
|
+
class Failure
|
|
12
|
+
include EnvironmentHelper
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# Receives the rack environment in case of a failure and renders a
|
|
16
|
+
# response based on the interactiveness of the request and the
|
|
17
|
+
# nature of the configured modes.
|
|
18
|
+
#
|
|
19
|
+
# @param [Hash] env a rack environment
|
|
20
|
+
#
|
|
21
|
+
# @return [Array] a rack response
|
|
22
|
+
def call(env)
|
|
23
|
+
conf = configuration(env)
|
|
24
|
+
if login_required?(env)
|
|
25
|
+
if interactive?(env)
|
|
26
|
+
::Warden::Strategies[conf.ui_mode].new(env).on_ui_failure.finish
|
|
27
|
+
else
|
|
28
|
+
headers = {}
|
|
29
|
+
headers["WWW-Authenticate"] =
|
|
30
|
+
conf.api_modes.collect { |mode_key|
|
|
31
|
+
::Warden::Strategies[mode_key].new(env).challenge
|
|
32
|
+
}.join("\n")
|
|
33
|
+
headers["Content-Type"] = "text/plain"
|
|
34
|
+
[401, headers, ["Authentication required"]]
|
|
35
|
+
end
|
|
36
|
+
else
|
|
37
|
+
log_authorization_failure(env)
|
|
38
|
+
msg = "#{user(env).username} may not use this page."
|
|
39
|
+
Rack::Response.
|
|
40
|
+
new("<html><head><title>Authorization denied</title></head><body>#{msg}</body></html>",
|
|
41
|
+
403,
|
|
42
|
+
"Content-Type" => "text/html").finish
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def login_required?(env)
|
|
49
|
+
env['warden.options'][:login_required]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def user(env)
|
|
53
|
+
env['aker.check'].user
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def log_authorization_failure(env)
|
|
57
|
+
wo = env['warden.options']
|
|
58
|
+
msg = "Resource authorization failure: User \"#{user(env).username}\" " <<
|
|
59
|
+
if wo[:portal_required]
|
|
60
|
+
"is not in the #{wo[:portal_required].inspect} portal."
|
|
61
|
+
elsif wo[:groups_required]
|
|
62
|
+
"is not in any of the required groups #{wo[:groups_required].inspect}."
|
|
63
|
+
else
|
|
64
|
+
"is just generally unauthorized. Don't ask questions."
|
|
65
|
+
end
|
|
66
|
+
configuration(env).logger.info(msg)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require 'aker'
|
|
2
|
+
|
|
3
|
+
module Aker
|
|
4
|
+
module Rack
|
|
5
|
+
##
|
|
6
|
+
# Middleware for ending authenticated sessions. This middleware
|
|
7
|
+
# listens for `GET` requests to the logout path and when such
|
|
8
|
+
# requests are received, clears user data.
|
|
9
|
+
#
|
|
10
|
+
# The logout path is `/logout` by default. It may be overridden in
|
|
11
|
+
# the Aker configuration by setting a value for `:logout_path` in
|
|
12
|
+
# the `:rack` parameter group.
|
|
13
|
+
#
|
|
14
|
+
# ## Implications of GET
|
|
15
|
+
#
|
|
16
|
+
# `GET` was chosen to ensure that there always exists a way to clear
|
|
17
|
+
# application session data independent of whether it is possible to get to a
|
|
18
|
+
# logout link. (If unmarshalable data exists in the session -- say, stored
|
|
19
|
+
# objects whose format has changed between application revisions -- it is
|
|
20
|
+
# possible to get into a state where logout links cannot be accessed.)
|
|
21
|
+
#
|
|
22
|
+
# Using `GET` does mean that it is possible to execute CSRF attacks that
|
|
23
|
+
# will log out the user. The severity of this can range from a minor
|
|
24
|
+
# annoyance (just having to log in again while browsing a series of pages)
|
|
25
|
+
# to major (losing all data in a large POST).
|
|
26
|
+
#
|
|
27
|
+
# @see Aker::Rack.use_in
|
|
28
|
+
#
|
|
29
|
+
# @author David Yip
|
|
30
|
+
class Logout
|
|
31
|
+
include ConfigurationHelper
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
# Instantiates the middleware.
|
|
35
|
+
#
|
|
36
|
+
# @param app [Rack app] the Rack application on which this middleware
|
|
37
|
+
# should be layered
|
|
38
|
+
def initialize(app)
|
|
39
|
+
@app = app
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# When given a `GET` for the configured logout path, invokes
|
|
44
|
+
# Warden's logout procedure (which resets the session), and
|
|
45
|
+
# passes control down to the rest of the application.
|
|
46
|
+
#
|
|
47
|
+
# If the application or a mode does not provide a handler for
|
|
48
|
+
# the configured logout path, then the handler defined by
|
|
49
|
+
# {DefaultLogoutResponder} will be invoked.
|
|
50
|
+
#
|
|
51
|
+
# @see Aker::Rack.use_in
|
|
52
|
+
# @param env [Hash] a Rack environment
|
|
53
|
+
# @return [Array] a finished Rack response
|
|
54
|
+
def call(env)
|
|
55
|
+
if env['REQUEST_METHOD'] == 'GET' && env['PATH_INFO'] == logout_path(env)
|
|
56
|
+
env['warden'].logout
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
@app.call(env)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'aker'
|
|
2
|
+
|
|
3
|
+
module Aker::Rack
|
|
4
|
+
##
|
|
5
|
+
# Extensions for `Rack::Request`.
|
|
6
|
+
#
|
|
7
|
+
# To use these, `include` them into `Rack::Request`.
|
|
8
|
+
module RequestExt
|
|
9
|
+
include Aker::Rack::EnvironmentHelper
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
# Whether the current request is interactive.
|
|
13
|
+
#
|
|
14
|
+
# @return [Boolean]
|
|
15
|
+
def interactive?
|
|
16
|
+
super(env)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require 'aker'
|
|
2
|
+
|
|
3
|
+
module Aker::Rack
|
|
4
|
+
##
|
|
5
|
+
# Middleware that permits a Web application to enforce a session inactivity
|
|
6
|
+
# limit. When a request is made after the session expires, the
|
|
7
|
+
# middleware resets the session, forcing the user to be reauthenticated.
|
|
8
|
+
#
|
|
9
|
+
# The session inactivity limit is determined by the `session-timeout-seconds`
|
|
10
|
+
# parameter in Aker's `policy` parameter group. It defaults to 1800 seconds
|
|
11
|
+
# (30 minutes), and can be overridden by a {Aker::ConfiguratorLanguage Aker
|
|
12
|
+
# configuration block} or {Aker::CentralParameters central parameters file}.
|
|
13
|
+
# To disable session timeout, set `session-timeout-seconds` to `nil` or `0`.
|
|
14
|
+
#
|
|
15
|
+
# Algorithm
|
|
16
|
+
# =========
|
|
17
|
+
#
|
|
18
|
+
# On each request:
|
|
19
|
+
#
|
|
20
|
+
# let lr = timestamp of last request,
|
|
21
|
+
# cr = timestamp of current request,
|
|
22
|
+
# st = session timeout from configuration,
|
|
23
|
+
# ta = lr + st
|
|
24
|
+
#
|
|
25
|
+
# if st is nil
|
|
26
|
+
# pass control to rest of application
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# store ta in the Rack environment as aker.timeout_at
|
|
30
|
+
# lr := cr
|
|
31
|
+
# store lr in the session
|
|
32
|
+
#
|
|
33
|
+
# if lr is nil
|
|
34
|
+
# pass control to rest of application
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# if cr is in [lr, ta]
|
|
38
|
+
# pass control to rest of application
|
|
39
|
+
# else
|
|
40
|
+
# reset session
|
|
41
|
+
# pass control to rest of application
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
#
|
|
45
|
+
# Requirements
|
|
46
|
+
# ============
|
|
47
|
+
#
|
|
48
|
+
# SessionTimer expects a session manager that behaves like a `Rack::Session`
|
|
49
|
+
# session manager to be present in the `rack.session` Rack environment
|
|
50
|
+
# variable.
|
|
51
|
+
#
|
|
52
|
+
# SessionTimer should be configured earlier in the middleware stack
|
|
53
|
+
# than any middleware which checks for cached
|
|
54
|
+
# credentials. {Aker::Rack.use_in} arranges things this way.
|
|
55
|
+
class SessionTimer
|
|
56
|
+
include EnvironmentHelper
|
|
57
|
+
include ConfigurationHelper
|
|
58
|
+
|
|
59
|
+
def initialize(app)
|
|
60
|
+
@app = app
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
# Determines whether the incoming request arrived within the timeout
|
|
65
|
+
# window. If it did, then the request is passed onto the rest of the Rack
|
|
66
|
+
# stack; otherwise, the user is redirected to the configured
|
|
67
|
+
# logout path.
|
|
68
|
+
#
|
|
69
|
+
def call(env)
|
|
70
|
+
now = Time.now.to_i
|
|
71
|
+
session = env['rack.session']
|
|
72
|
+
window_size = window_size(env)
|
|
73
|
+
previous_timeout = session['aker.last_request_at']
|
|
74
|
+
|
|
75
|
+
return @app.call(env) unless window_size > 0
|
|
76
|
+
|
|
77
|
+
env['aker.timeout_at'] = now + window_size
|
|
78
|
+
session['aker.last_request_at'] = now
|
|
79
|
+
|
|
80
|
+
return @app.call(env) unless previous_timeout
|
|
81
|
+
|
|
82
|
+
if now >= previous_timeout + window_size
|
|
83
|
+
env['aker.session_expired'] = true
|
|
84
|
+
env['warden'].logout
|
|
85
|
+
end
|
|
86
|
+
@app.call(env)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def window_size(env)
|
|
92
|
+
configuration(env).parameters_for(:policy)[%s(session-timeout-seconds)].to_i
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require 'aker/rack'
|
|
2
|
+
|
|
3
|
+
module Aker::Rack
|
|
4
|
+
##
|
|
5
|
+
# The middleware which makes the aker environment available in the
|
|
6
|
+
# rake environment and authenticates the credentials that the
|
|
7
|
+
# request provides (if any). It is responsible for determining
|
|
8
|
+
# whether the request is interactive or not and will use the
|
|
9
|
+
# appropriate configured {Aker::Modes mode} based on this decision.
|
|
10
|
+
#
|
|
11
|
+
# You probably don't want to `use` this directly; use
|
|
12
|
+
# {Aker::Rack.use_in} to configure in this middleware and all its
|
|
13
|
+
# dependencies simultaneously.
|
|
14
|
+
#
|
|
15
|
+
# @see Aker::Rack.use_in
|
|
16
|
+
# @see Aker::Configuration#ui_mode
|
|
17
|
+
# @see Aker::Configuration#api_modes
|
|
18
|
+
class Setup
|
|
19
|
+
##
|
|
20
|
+
# Creates a new instance of the middleware.
|
|
21
|
+
#
|
|
22
|
+
# @param [#call] app the application this middleware is being
|
|
23
|
+
# wrapped around.
|
|
24
|
+
# @param [Aker::Configuration] configuration the configuration to use for
|
|
25
|
+
# this instance.
|
|
26
|
+
#
|
|
27
|
+
# @see Aker::Rack.use_in
|
|
28
|
+
def initialize(app, configuration)
|
|
29
|
+
@app = app
|
|
30
|
+
@configuration = configuration
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
# Implements the rack middleware behavior.
|
|
35
|
+
#
|
|
36
|
+
# This class exposes three environment variables to downstream
|
|
37
|
+
# middleware and the app:
|
|
38
|
+
#
|
|
39
|
+
# * `"aker.configuration"`: the {Aker::Configuration configuration}
|
|
40
|
+
# for this application.
|
|
41
|
+
# * `"aker.authority"`: the {Aker::Authorities authority} for
|
|
42
|
+
# this application.
|
|
43
|
+
# * `"aker.interactive"`: a boolean indicating whether this
|
|
44
|
+
# request is being treated as an interactive (UI) or
|
|
45
|
+
# non-interactive (API) request
|
|
46
|
+
#
|
|
47
|
+
# [There is a related fourth environment variable:
|
|
48
|
+
#
|
|
49
|
+
# * `"aker.check"`: an instance of {Aker::Rack::Facade}
|
|
50
|
+
# permitting authentication and authorization queries about the
|
|
51
|
+
# current user (if any).
|
|
52
|
+
#
|
|
53
|
+
# This fourth variable is added by the {Authenticate} middleware;
|
|
54
|
+
# see its documentation for more.]
|
|
55
|
+
#
|
|
56
|
+
# @param [Hash] env the rack env
|
|
57
|
+
# @return [Array] the standard rack return
|
|
58
|
+
def call(env)
|
|
59
|
+
env['aker.configuration'] = @configuration
|
|
60
|
+
env['aker.authority'] = @configuration.composite_authority
|
|
61
|
+
env['aker.interactive'] = interactive?(env)
|
|
62
|
+
|
|
63
|
+
@app.call(env)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# Determines if the given rack env represents an interactive
|
|
68
|
+
# request.
|
|
69
|
+
#
|
|
70
|
+
# @return [Boolean, nil] true if interactive, false or nil otherwise
|
|
71
|
+
def interactive?(env)
|
|
72
|
+
@configuration.api_modes.empty? or
|
|
73
|
+
env["HTTP_ACCEPT"] =~ %r{text/html} or
|
|
74
|
+
env["HTTP_USER_AGENT"] =~ %r{Mozilla}
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|