ultra-lite-hub 0.0.1
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.
- checksums.yaml +7 -0
- data/roda-3.105.0/MIT-LICENSE +21 -0
- data/roda-3.105.0/lib/roda/cache.rb +42 -0
- data/roda-3.105.0/lib/roda/plugins/Integer_matcher_max.rb +55 -0
- data/roda-3.105.0/lib/roda/plugins/_after_hook.rb +11 -0
- data/roda-3.105.0/lib/roda/plugins/_base64.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/_before_hook.rb +41 -0
- data/roda-3.105.0/lib/roda/plugins/_optimized_matching.rb +227 -0
- data/roda-3.105.0/lib/roda/plugins/_symbol_class_matchers.rb +108 -0
- data/roda-3.105.0/lib/roda/plugins/_symbol_regexp_matchers.rb +24 -0
- data/roda-3.105.0/lib/roda/plugins/additional_render_engines.rb +61 -0
- data/roda-3.105.0/lib/roda/plugins/additional_view_directories.rb +66 -0
- data/roda-3.105.0/lib/roda/plugins/all_verbs.rb +50 -0
- data/roda-3.105.0/lib/roda/plugins/assets.rb +902 -0
- data/roda-3.105.0/lib/roda/plugins/assets_preloading.rb +89 -0
- data/roda-3.105.0/lib/roda/plugins/assume_ssl.rb +28 -0
- data/roda-3.105.0/lib/roda/plugins/autoload_hash_branches.rb +79 -0
- data/roda-3.105.0/lib/roda/plugins/autoload_named_routes.rb +65 -0
- data/roda-3.105.0/lib/roda/plugins/backtracking_array.rb +88 -0
- data/roda-3.105.0/lib/roda/plugins/bearer_token.rb +28 -0
- data/roda-3.105.0/lib/roda/plugins/branch_locals.rb +74 -0
- data/roda-3.105.0/lib/roda/plugins/break.rb +43 -0
- data/roda-3.105.0/lib/roda/plugins/caching.rb +206 -0
- data/roda-3.105.0/lib/roda/plugins/capture_erb.rb +94 -0
- data/roda-3.105.0/lib/roda/plugins/chunked.rb +331 -0
- data/roda-3.105.0/lib/roda/plugins/class_level_routing.rb +111 -0
- data/roda-3.105.0/lib/roda/plugins/class_matchers.rb +155 -0
- data/roda-3.105.0/lib/roda/plugins/common_logger.rb +80 -0
- data/roda-3.105.0/lib/roda/plugins/conditional_sessions.rb +67 -0
- data/roda-3.105.0/lib/roda/plugins/content_for.rb +103 -0
- data/roda-3.105.0/lib/roda/plugins/content_security_policy.rb +329 -0
- data/roda-3.105.0/lib/roda/plugins/cookie_flags.rb +157 -0
- data/roda-3.105.0/lib/roda/plugins/cookies.rb +51 -0
- data/roda-3.105.0/lib/roda/plugins/csrf.rb +81 -0
- data/roda-3.105.0/lib/roda/plugins/custom_block_results.rb +85 -0
- data/roda-3.105.0/lib/roda/plugins/custom_matchers.rb +89 -0
- data/roda-3.105.0/lib/roda/plugins/default_headers.rb +64 -0
- data/roda-3.105.0/lib/roda/plugins/default_status.rb +35 -0
- data/roda-3.105.0/lib/roda/plugins/delay_build.rb +18 -0
- data/roda-3.105.0/lib/roda/plugins/delegate.rb +77 -0
- data/roda-3.105.0/lib/roda/plugins/delete_empty_headers.rb +36 -0
- data/roda-3.105.0/lib/roda/plugins/direct_call.rb +38 -0
- data/roda-3.105.0/lib/roda/plugins/disallow_file_uploads.rb +38 -0
- data/roda-3.105.0/lib/roda/plugins/drop_body.rb +43 -0
- data/roda-3.105.0/lib/roda/plugins/each_part.rb +87 -0
- data/roda-3.105.0/lib/roda/plugins/early_hints.rb +25 -0
- data/roda-3.105.0/lib/roda/plugins/empty_root.rb +49 -0
- data/roda-3.105.0/lib/roda/plugins/environments.rb +79 -0
- data/roda-3.105.0/lib/roda/plugins/erb_h.rb +43 -0
- data/roda-3.105.0/lib/roda/plugins/error_email.rb +152 -0
- data/roda-3.105.0/lib/roda/plugins/error_handler.rb +133 -0
- data/roda-3.105.0/lib/roda/plugins/error_mail.rb +149 -0
- data/roda-3.105.0/lib/roda/plugins/exception_page.rb +445 -0
- data/roda-3.105.0/lib/roda/plugins/filter_common_logger.rb +46 -0
- data/roda-3.105.0/lib/roda/plugins/flash.rb +117 -0
- data/roda-3.105.0/lib/roda/plugins/h.rb +59 -0
- data/roda-3.105.0/lib/roda/plugins/halt.rb +108 -0
- data/roda-3.105.0/lib/roda/plugins/hash_branch_view_subdir.rb +76 -0
- data/roda-3.105.0/lib/roda/plugins/hash_branches.rb +145 -0
- data/roda-3.105.0/lib/roda/plugins/hash_matcher.rb +39 -0
- data/roda-3.105.0/lib/roda/plugins/hash_paths.rb +128 -0
- data/roda-3.105.0/lib/roda/plugins/hash_public.rb +125 -0
- data/roda-3.105.0/lib/roda/plugins/hash_public_cache.rb +91 -0
- data/roda-3.105.0/lib/roda/plugins/hash_routes.rb +304 -0
- data/roda-3.105.0/lib/roda/plugins/head.rb +99 -0
- data/roda-3.105.0/lib/roda/plugins/header_matchers.rb +88 -0
- data/roda-3.105.0/lib/roda/plugins/heartbeat.rb +41 -0
- data/roda-3.105.0/lib/roda/plugins/hmac_paths.rb +413 -0
- data/roda-3.105.0/lib/roda/plugins/hooks.rb +92 -0
- data/roda-3.105.0/lib/roda/plugins/host_authorization.rb +155 -0
- data/roda-3.105.0/lib/roda/plugins/host_routing.rb +190 -0
- data/roda-3.105.0/lib/roda/plugins/hsts.rb +35 -0
- data/roda-3.105.0/lib/roda/plugins/indifferent_params.rb +124 -0
- data/roda-3.105.0/lib/roda/plugins/inject_erb.rb +33 -0
- data/roda-3.105.0/lib/roda/plugins/invalid_request_body.rb +107 -0
- data/roda-3.105.0/lib/roda/plugins/ip_from_header.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/json.rb +119 -0
- data/roda-3.105.0/lib/roda/plugins/json_parser.rb +107 -0
- data/roda-3.105.0/lib/roda/plugins/link_to.rb +83 -0
- data/roda-3.105.0/lib/roda/plugins/mail_processor.rb +629 -0
- data/roda-3.105.0/lib/roda/plugins/mailer.rb +283 -0
- data/roda-3.105.0/lib/roda/plugins/map_matcher.rb +48 -0
- data/roda-3.105.0/lib/roda/plugins/match_affix.rb +64 -0
- data/roda-3.105.0/lib/roda/plugins/match_hook.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/match_hook_args.rb +93 -0
- data/roda-3.105.0/lib/roda/plugins/middleware.rb +225 -0
- data/roda-3.105.0/lib/roda/plugins/middleware_stack.rb +101 -0
- data/roda-3.105.0/lib/roda/plugins/module_include.rb +96 -0
- data/roda-3.105.0/lib/roda/plugins/multi_public.rb +95 -0
- data/roda-3.105.0/lib/roda/plugins/multi_route.rb +156 -0
- data/roda-3.105.0/lib/roda/plugins/multi_run.rb +144 -0
- data/roda-3.105.0/lib/roda/plugins/multi_view.rb +83 -0
- data/roda-3.105.0/lib/roda/plugins/multibyte_string_matcher.rb +57 -0
- data/roda-3.105.0/lib/roda/plugins/named_routes.rb +167 -0
- data/roda-3.105.0/lib/roda/plugins/named_templates.rb +102 -0
- data/roda-3.105.0/lib/roda/plugins/not_allowed.rb +149 -0
- data/roda-3.105.0/lib/roda/plugins/not_found.rb +55 -0
- data/roda-3.105.0/lib/roda/plugins/optimized_segment_matchers.rb +53 -0
- data/roda-3.105.0/lib/roda/plugins/optimized_string_matchers.rb +50 -0
- data/roda-3.105.0/lib/roda/plugins/padrino_render.rb +42 -0
- data/roda-3.105.0/lib/roda/plugins/param_matchers.rb +83 -0
- data/roda-3.105.0/lib/roda/plugins/params_capturing.rb +116 -0
- data/roda-3.105.0/lib/roda/plugins/part.rb +76 -0
- data/roda-3.105.0/lib/roda/plugins/partials.rb +74 -0
- data/roda-3.105.0/lib/roda/plugins/pass.rb +47 -0
- data/roda-3.105.0/lib/roda/plugins/path.rb +255 -0
- data/roda-3.105.0/lib/roda/plugins/path_matchers.rb +54 -0
- data/roda-3.105.0/lib/roda/plugins/path_rewriter.rb +111 -0
- data/roda-3.105.0/lib/roda/plugins/permissions_policy.rb +336 -0
- data/roda-3.105.0/lib/roda/plugins/placeholder_string_matchers.rb +48 -0
- data/roda-3.105.0/lib/roda/plugins/plain_hash_response_headers.rb +32 -0
- data/roda-3.105.0/lib/roda/plugins/precompile_templates.rb +156 -0
- data/roda-3.105.0/lib/roda/plugins/public.rb +182 -0
- data/roda-3.105.0/lib/roda/plugins/r.rb +35 -0
- data/roda-3.105.0/lib/roda/plugins/recheck_precompiled_assets.rb +107 -0
- data/roda-3.105.0/lib/roda/plugins/redirect_http_to_https.rb +99 -0
- data/roda-3.105.0/lib/roda/plugins/redirect_path.rb +59 -0
- data/roda-3.105.0/lib/roda/plugins/relative_path.rb +74 -0
- data/roda-3.105.0/lib/roda/plugins/render.rb +998 -0
- data/roda-3.105.0/lib/roda/plugins/render_coverage.rb +105 -0
- data/roda-3.105.0/lib/roda/plugins/render_each.rb +224 -0
- data/roda-3.105.0/lib/roda/plugins/render_locals.rb +102 -0
- data/roda-3.105.0/lib/roda/plugins/request_aref.rb +77 -0
- data/roda-3.105.0/lib/roda/plugins/request_headers.rb +81 -0
- data/roda-3.105.0/lib/roda/plugins/response_attachment.rb +94 -0
- data/roda-3.105.0/lib/roda/plugins/response_content_type.rb +79 -0
- data/roda-3.105.0/lib/roda/plugins/response_request.rb +29 -0
- data/roda-3.105.0/lib/roda/plugins/route_block_args.rb +50 -0
- data/roda-3.105.0/lib/roda/plugins/route_csrf.rb +388 -0
- data/roda-3.105.0/lib/roda/plugins/run_append_slash.rb +51 -0
- data/roda-3.105.0/lib/roda/plugins/run_handler.rb +51 -0
- data/roda-3.105.0/lib/roda/plugins/run_require_slash.rb +46 -0
- data/roda-3.105.0/lib/roda/plugins/sec_fetch_site_csrf.rb +131 -0
- data/roda-3.105.0/lib/roda/plugins/send_file.rb +127 -0
- data/roda-3.105.0/lib/roda/plugins/sessions.rb +537 -0
- data/roda-3.105.0/lib/roda/plugins/shared_vars.rb +86 -0
- data/roda-3.105.0/lib/roda/plugins/sinatra_helpers.rb +443 -0
- data/roda-3.105.0/lib/roda/plugins/slash_path_empty.rb +26 -0
- data/roda-3.105.0/lib/roda/plugins/static.rb +42 -0
- data/roda-3.105.0/lib/roda/plugins/static_routing.rb +89 -0
- data/roda-3.105.0/lib/roda/plugins/status_303.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/status_handler.rb +86 -0
- data/roda-3.105.0/lib/roda/plugins/streaming.rb +170 -0
- data/roda-3.105.0/lib/roda/plugins/strip_path_prefix.rb +33 -0
- data/roda-3.105.0/lib/roda/plugins/symbol_matchers.rb +188 -0
- data/roda-3.105.0/lib/roda/plugins/symbol_status.rb +32 -0
- data/roda-3.105.0/lib/roda/plugins/symbol_views.rb +37 -0
- data/roda-3.105.0/lib/roda/plugins/timestamp_public.rb +78 -0
- data/roda-3.105.0/lib/roda/plugins/type_routing.rb +215 -0
- data/roda-3.105.0/lib/roda/plugins/typecast_params.rb +1183 -0
- data/roda-3.105.0/lib/roda/plugins/typecast_params_sized_integers.rb +108 -0
- data/roda-3.105.0/lib/roda/plugins/unescape_path.rb +41 -0
- data/roda-3.105.0/lib/roda/plugins/view_options.rb +201 -0
- data/roda-3.105.0/lib/roda/plugins/view_subdir_leading_slash.rb +49 -0
- data/roda-3.105.0/lib/roda/plugins.rb +68 -0
- data/roda-3.105.0/lib/roda/request.rb +722 -0
- data/roda-3.105.0/lib/roda/response.rb +221 -0
- data/roda-3.105.0/lib/roda/session_middleware.rb +183 -0
- data/roda-3.105.0/lib/roda/version.rb +18 -0
- data/roda-3.105.0/lib/roda.rb +586 -0
- data/ultra-lite-hub.gemspec +12 -0
- metadata +201 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
OpenSSL::Cipher.new("aes-256-ctr")
|
|
7
|
+
rescue OpenSSL::Cipher::CipherError
|
|
8
|
+
# :nocov:
|
|
9
|
+
raise LoadError, "Roda sessions plugin requires the aes-256-ctr cipher"
|
|
10
|
+
# :nocov:
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
require 'json'
|
|
14
|
+
require 'securerandom'
|
|
15
|
+
require 'zlib'
|
|
16
|
+
require 'rack/utils'
|
|
17
|
+
|
|
18
|
+
class Roda
|
|
19
|
+
module RodaPlugins
|
|
20
|
+
# The sessions plugin adds support for sessions using cookies. It is the recommended
|
|
21
|
+
# way to support sessions in Roda applications.
|
|
22
|
+
#
|
|
23
|
+
# The session cookies are encrypted with AES-256-CTR using a separate encryption key per cookie,
|
|
24
|
+
# and then signed with HMAC-SHA-256. By default, session data is padded to reduce information
|
|
25
|
+
# leaked based on the session size.
|
|
26
|
+
#
|
|
27
|
+
# Sessions are serialized via JSON, so session information should only store data that
|
|
28
|
+
# allows roundtrips via JSON (String, Integer, Float, Array, Hash, true, false, and nil).
|
|
29
|
+
# In particular, note that Symbol does not round trip via JSON, so symbols should not be
|
|
30
|
+
# used in sessions when this plugin is used. This plugin sets the
|
|
31
|
+
# +:sessions_convert_symbols+ application option to +true+ if it hasn't been set yet,
|
|
32
|
+
# for better integration with plugins that can use either symbol or string session or
|
|
33
|
+
# flash keys. Unlike Rack::Session::Cookie, the session is stored as a plain ruby hash,
|
|
34
|
+
# and does not convert all keys to strings.
|
|
35
|
+
#
|
|
36
|
+
# All sessions are timestamped and session expiration is enabled by default, with sessions
|
|
37
|
+
# being valid for 30 days maximum and 7 days since last use by default. Session creation time is
|
|
38
|
+
# reset whenever the session is empty when serialized and also whenever +clear_session+
|
|
39
|
+
# is called while processing the request.
|
|
40
|
+
#
|
|
41
|
+
# Session secrets can be rotated. See options below.
|
|
42
|
+
#
|
|
43
|
+
# The sessions plugin can transparently upgrade sessions from versions of Rack::Session::Cookie
|
|
44
|
+
# shipped with Rack before Rack 3,
|
|
45
|
+
# if the default Rack::Session::Cookie coder and HMAC are used, see options below.
|
|
46
|
+
# It is recommended to only enable transparent upgrades for a brief transition period,
|
|
47
|
+
# and remove support for them once old sessions have converted or timed out.
|
|
48
|
+
#
|
|
49
|
+
# If the final cookie is too large (>=4096 bytes), a Roda::RodaPlugins::Sessions::CookieTooLarge
|
|
50
|
+
# exception will be raised.
|
|
51
|
+
#
|
|
52
|
+
# = Required Options
|
|
53
|
+
#
|
|
54
|
+
# The session cookies this plugin uses are both encrypted and signed, so two separate
|
|
55
|
+
# secrets are used internally. However, for ease of use, these secrets are combined into
|
|
56
|
+
# a single +:secret+ option. The +:secret+ option must be a string of at least 64 bytes
|
|
57
|
+
# and should be randomly generated. The first 32 bytes are used as the secret for the
|
|
58
|
+
# cipher, any remaining bytes are used for the secret for the HMAC.
|
|
59
|
+
#
|
|
60
|
+
# = Other Options
|
|
61
|
+
#
|
|
62
|
+
# :cookie_options :: Any cookie options to set on the session cookie. By default, uses
|
|
63
|
+
# <tt>httponly: true, path: '/', same_site: :lax</tt> so that the cookie is not accessible
|
|
64
|
+
# to javascript, allowed for all paths, and will not be used for cross-site non-GET requests
|
|
65
|
+
# that. If the +:secure+ option is not present in the hash, then
|
|
66
|
+
# <tt>secure: true</tt> is also set if the request is made over HTTPS. If this option is
|
|
67
|
+
# given, it will be merged into the default cookie options.
|
|
68
|
+
# :env_key :: The key in `env` where the session should be located. Defaults to <tt>"rack.session"</tt>, the
|
|
69
|
+
# default key for sessions in Rack.
|
|
70
|
+
# :gzip_over :: For session data over this many bytes, compress it with the deflate algorithm (default: nil,
|
|
71
|
+
# so never compress). Note that compression should not be enabled if you are storing data in
|
|
72
|
+
# the session derived from user input and also storing sensitive data in the session.
|
|
73
|
+
# :key :: The cookie name to use (default: <tt>'roda.session'</tt>)
|
|
74
|
+
# :max_seconds :: The maximum number of seconds to allow for total session lifetime, starting with when
|
|
75
|
+
# the session was originally created. Default is <tt>86400*30</tt> (30 days). Can be set to
|
|
76
|
+
# +nil+ to disable session lifetime checks.
|
|
77
|
+
# :max_idle_seconds :: The maximum number of seconds to allow since the session was last updated.
|
|
78
|
+
# Default is <tt>86400*7</tt> (7 days). Can be set to nil to disable session idleness
|
|
79
|
+
# checks.
|
|
80
|
+
# :old_secret :: The previous secret to use, allowing for secret rotation. Must be a string of at least 64
|
|
81
|
+
# bytes if given.
|
|
82
|
+
# :pad_size :: Pad session data (after possible compression, before encryption), to a multiple of this
|
|
83
|
+
# many bytes (default: 32). This can be between 2-4096 bytes, or +nil+ to disable padding.
|
|
84
|
+
# :per_cookie_cipher_secret :: Uses a separate cipher key for every cookie, with the key used generated using
|
|
85
|
+
# HMAC-SHA-256 of 32 bytes of random data with the default cipher secret. This
|
|
86
|
+
# offers additional protection in case the random initialization vector used when
|
|
87
|
+
# encrypting the session data has been reused. Odds of that are 1 in 2**64 if
|
|
88
|
+
# initialization vector is truly random, but weaknesses in the random number
|
|
89
|
+
# generator could make the odds much higher. Default is +true+.
|
|
90
|
+
# :parser :: The parser for the serialized session data (default: <tt>JSON.method(:parse)</tt>).
|
|
91
|
+
# :serializer :: The serializer for the session data (default +:to_json.to_proc+).
|
|
92
|
+
# :skip_within :: If the last update time for the session cookie is less than this number of seconds from the
|
|
93
|
+
# current time, and the session has not been modified, do not set a new session cookie
|
|
94
|
+
# (default: 3600).
|
|
95
|
+
# :upgrade_from_rack_session_cookie_key :: The cookie name to use for transparently upgrading from
|
|
96
|
+
# Rack::Session:Cookie (defaults to <tt>'rack.session'</tt>).
|
|
97
|
+
# :upgrade_from_rack_session_cookie_secret :: The secret for the HMAC-SHA1 signature when allowing
|
|
98
|
+
# transparent upgrades from Rack::Session::Cookie. Using this
|
|
99
|
+
# option is only recommended during a short transition period,
|
|
100
|
+
# and is not enabled by default as it lowers security.
|
|
101
|
+
# :upgrade_from_rack_session_cookie_options :: Options to pass when deleting the cookie used by
|
|
102
|
+
# Rack::Session::Cookie after converting it to use the session
|
|
103
|
+
# cookies used by this plugin.
|
|
104
|
+
#
|
|
105
|
+
# = Not a Rack Middleware
|
|
106
|
+
#
|
|
107
|
+
# Unlike some other approaches to sessions, the sessions plugin does not use
|
|
108
|
+
# a rack middleware, so session information is not available to other rack middleware,
|
|
109
|
+
# only to the application itself, with the session not being loaded from the cookie
|
|
110
|
+
# until the +session+ method is called.
|
|
111
|
+
#
|
|
112
|
+
# If you need rack middleware to access the session information, then
|
|
113
|
+
# <tt>require 'roda/session_middleware'</tt> and <tt>use RodaSessionMiddleware</tt>.
|
|
114
|
+
# <tt>RodaSessionMiddleware</tt> passes the options given to this plugin.
|
|
115
|
+
#
|
|
116
|
+
# = Session Cookie Cryptography/Format
|
|
117
|
+
#
|
|
118
|
+
# Session cookies created by this plugin by default use the following format:
|
|
119
|
+
#
|
|
120
|
+
# urlsafe_base64("\1" + random_data + IV + encrypted session data + HMAC)
|
|
121
|
+
#
|
|
122
|
+
# If +:per_cookie_cipher_secret+ option is set to +false+, an older format is used:
|
|
123
|
+
#
|
|
124
|
+
# urlsafe_base64("\0" + IV + encrypted session data + HMAC)
|
|
125
|
+
#
|
|
126
|
+
# where:
|
|
127
|
+
#
|
|
128
|
+
# version :: 1 byte, currently must be 1 or 0, other values reserved for future expansion.
|
|
129
|
+
# random_data :: 32 bytes, used for generating the per-cookie secret
|
|
130
|
+
# IV :: 16 bytes, initialization vector for AES-256-CTR cipher.
|
|
131
|
+
# encrypted session data :: >=12 bytes of data encrypted with AES-256-CTR cipher, see below.
|
|
132
|
+
# HMAC :: 32 bytes, HMAC-SHA-256 of all preceding data plus cookie key (so that a cookie value
|
|
133
|
+
# for a different key cannot be used even if the secret is the same).
|
|
134
|
+
#
|
|
135
|
+
# The encrypted session data uses the following format:
|
|
136
|
+
#
|
|
137
|
+
# bitmap + creation time + update time + padding + serialized data
|
|
138
|
+
#
|
|
139
|
+
# where:
|
|
140
|
+
#
|
|
141
|
+
# bitmap :: 2 bytes in little endian format, lower 12 bits storing number of padding
|
|
142
|
+
# bytes, 13th bit storing whether serialized data is compressed with deflate.
|
|
143
|
+
# Bits 14-16 reserved for future expansion.
|
|
144
|
+
# creation time :: 4 byte integer in unsigned little endian format, storing unix timestamp
|
|
145
|
+
# since session initially created.
|
|
146
|
+
# update time :: 4 byte integer in unsigned little endian format, storing unix timestamp
|
|
147
|
+
# since session last updated.
|
|
148
|
+
# padding :: >=0 padding bytes specified in bitmap, filled with random data, can be ignored.
|
|
149
|
+
# serialized data :: >=2 bytes of serialized data in JSON format. If the bitmap indicates
|
|
150
|
+
# deflate compression, this contains the deflate compressed data.
|
|
151
|
+
module Sessions
|
|
152
|
+
DEFAULT_COOKIE_OPTIONS = {:httponly=>true, :path=>'/'.freeze, :same_site=>:lax}.freeze
|
|
153
|
+
DEFAULT_OPTIONS = {:key => 'roda.session'.freeze, :max_seconds=>86400*30, :max_idle_seconds=>86400*7, :pad_size=>32, :gzip_over=>nil, :skip_within=>3600, :env_key=>'rack.session'}.freeze
|
|
154
|
+
DEFLATE_BIT = 0x1000
|
|
155
|
+
PADDING_MASK = 0x0fff
|
|
156
|
+
SESSION_CREATED_AT = 'roda.session.created_at'.freeze
|
|
157
|
+
SESSION_UPDATED_AT = 'roda.session.updated_at'.freeze
|
|
158
|
+
SESSION_SERIALIZED = 'roda.session.serialized'.freeze
|
|
159
|
+
SESSION_VERSION_NUM = 'roda.session.version'.freeze
|
|
160
|
+
SESSION_DELETE_RACK_COOKIE = 'roda.session.delete_rack_session_cookie'.freeze
|
|
161
|
+
|
|
162
|
+
# Exception class used when creating a session cookie that would exceed the
|
|
163
|
+
# allowable cookie size limit.
|
|
164
|
+
class CookieTooLarge < RodaError
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Split given secret into a cipher secret and an hmac secret.
|
|
168
|
+
def self.split_secret(name, secret)
|
|
169
|
+
raise RodaError, "sessions plugin :#{name} option must be a String" unless secret.is_a?(String)
|
|
170
|
+
raise RodaError, "invalid sessions plugin :#{name} option length: #{secret.bytesize}, must be >=64" unless secret.bytesize >= 64
|
|
171
|
+
hmac_secret = secret = secret.dup.force_encoding('BINARY')
|
|
172
|
+
cipher_secret = secret.slice!(0, 32)
|
|
173
|
+
[cipher_secret.freeze, hmac_secret.freeze]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def self.load_dependencies(app, opts=OPTS)
|
|
177
|
+
app.plugin :_base64
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Configure the plugin, see Sessions for details on options.
|
|
181
|
+
def self.configure(app, opts=OPTS)
|
|
182
|
+
opts = (app.opts[:sessions] || DEFAULT_OPTIONS).merge(opts)
|
|
183
|
+
co = opts[:cookie_options] = DEFAULT_COOKIE_OPTIONS.merge(opts[:cookie_options] || OPTS).freeze
|
|
184
|
+
opts[:remove_cookie_options] = co.merge(:max_age=>'0', :expires=>Time.at(0))
|
|
185
|
+
opts[:parser] ||= app.opts[:json_parser] || JSON.method(:parse)
|
|
186
|
+
opts[:serializer] ||= app.opts[:json_serializer] || :to_json.to_proc
|
|
187
|
+
|
|
188
|
+
opts[:per_cookie_cipher_secret] = true unless opts.has_key?(:per_cookie_cipher_secret)
|
|
189
|
+
opts[:session_version_num] = opts[:per_cookie_cipher_secret] ? 1 : 0
|
|
190
|
+
|
|
191
|
+
if opts[:upgrade_from_rack_session_cookie_secret]
|
|
192
|
+
opts[:upgrade_from_rack_session_cookie_key] ||= 'rack.session'
|
|
193
|
+
rsco = opts[:upgrade_from_rack_session_cookie_options] = Hash[opts[:upgrade_from_rack_session_cookie_options] || OPTS]
|
|
194
|
+
rsco[:path] ||= co[:path]
|
|
195
|
+
rsco[:domain] ||= co[:domain]
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
opts[:cipher_secret], opts[:hmac_secret] = split_secret(:secret, opts[:secret])
|
|
199
|
+
opts[:old_cipher_secret], opts[:old_hmac_secret] = (split_secret(:old_secret, opts[:old_secret]) if opts[:old_secret])
|
|
200
|
+
|
|
201
|
+
case opts[:pad_size]
|
|
202
|
+
when nil
|
|
203
|
+
# no changes
|
|
204
|
+
when Integer
|
|
205
|
+
raise RodaError, "invalid :pad_size: #{opts[:pad_size]}, must be >=2, < 4096" unless opts[:pad_size] >= 2 && opts[:pad_size] < 4096
|
|
206
|
+
else
|
|
207
|
+
raise RodaError, "invalid :pad_size option: #{opts[:pad_size].inspect}, must be Integer or nil"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
app.opts[:sessions] = opts.freeze
|
|
211
|
+
app.opts[:sessions_convert_symbols] = true unless app.opts.has_key?(:sessions_convert_symbols)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
module InstanceMethods
|
|
215
|
+
# Clear data from the session, and update the request environment
|
|
216
|
+
# so that the session cookie will use a new creation timestamp
|
|
217
|
+
# instead of the previous creation timestamp.
|
|
218
|
+
def clear_session
|
|
219
|
+
session.clear
|
|
220
|
+
env.delete(SESSION_CREATED_AT)
|
|
221
|
+
env.delete(SESSION_UPDATED_AT)
|
|
222
|
+
nil
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
private
|
|
226
|
+
|
|
227
|
+
# If session information has been set in the request environment,
|
|
228
|
+
# update the rack response headers to set the session cookie in
|
|
229
|
+
# the response.
|
|
230
|
+
def _roda_after_50__sessions(res)
|
|
231
|
+
if res && (session = env[self.class.opts[:sessions][:env_key]])
|
|
232
|
+
@_request.persist_session(res[1], session)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
module RequestMethods
|
|
238
|
+
# Load the session information from the cookie. With the sessions
|
|
239
|
+
# plugin, you must call this method to get the session, instead of
|
|
240
|
+
# trying to access the session directly through the request environment.
|
|
241
|
+
# For maximum compatibility with other software that uses rack sessions,
|
|
242
|
+
# this method stores the session in 'rack.session' in the request environment,
|
|
243
|
+
# but that does not happen until this method is called.
|
|
244
|
+
def session
|
|
245
|
+
@env[roda_class.opts[:sessions][:env_key]] ||= _load_session
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# The time the session was originally created. nil if there is no active session.
|
|
249
|
+
def session_created_at
|
|
250
|
+
session
|
|
251
|
+
Time.at(@env[SESSION_CREATED_AT]) if @env[SESSION_SERIALIZED]
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# The time the session was last updated. nil if there is no active session.
|
|
255
|
+
def session_updated_at
|
|
256
|
+
session
|
|
257
|
+
Time.at(@env[SESSION_UPDATED_AT]) if @env[SESSION_SERIALIZED]
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Persist the session data as a cookie. If transparently upgrading from
|
|
261
|
+
# Rack::Session::Cookie, mark the related cookie for expiration so it isn't
|
|
262
|
+
# sent in the future.
|
|
263
|
+
def persist_session(headers, session)
|
|
264
|
+
opts = roda_class.opts[:sessions]
|
|
265
|
+
|
|
266
|
+
if session.empty?
|
|
267
|
+
if env[SESSION_SERIALIZED]
|
|
268
|
+
# If session was submitted and is now empty, remove the cookie
|
|
269
|
+
Rack::Utils.delete_cookie_header!(headers, opts[:key], opts[:remove_cookie_options])
|
|
270
|
+
# else
|
|
271
|
+
# If no session was submitted, and the session is empty
|
|
272
|
+
# then there is no need to do anything
|
|
273
|
+
end
|
|
274
|
+
elsif cookie_value = _serialize_session(session)
|
|
275
|
+
cookie = Hash[opts[:cookie_options]]
|
|
276
|
+
cookie[:value] = cookie_value
|
|
277
|
+
cookie[:secure] = true if !cookie.has_key?(:secure) && ssl?
|
|
278
|
+
|
|
279
|
+
before_size = if (set_cookie_before = headers[RodaResponseHeaders::SET_COOKIE]).is_a?(String)
|
|
280
|
+
set_cookie_before.bytesize
|
|
281
|
+
else
|
|
282
|
+
0
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
Rack::Utils.set_cookie_header!(headers, opts[:key], cookie)
|
|
286
|
+
|
|
287
|
+
cookie_size = case set_cookie_after = headers[RodaResponseHeaders::SET_COOKIE]
|
|
288
|
+
when String
|
|
289
|
+
# Rack < 3 always takes this branch, combines cookies into string, subtract previous size
|
|
290
|
+
# Rack 3+ takes this branch if this is the first cookie set, in which case before size is 0
|
|
291
|
+
set_cookie_after.bytesize - before_size
|
|
292
|
+
else # when Array
|
|
293
|
+
# Rack 3+ takes branch if this is not the first cookie set, and last element of the array
|
|
294
|
+
# is most recently added cookie
|
|
295
|
+
set_cookie_after.last.bytesize
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
if cookie_size >= 4096
|
|
299
|
+
raise CookieTooLarge, "attempted to create cookie larger than 4096 bytes (bytes: #{cookie_size})"
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
if env[SESSION_DELETE_RACK_COOKIE]
|
|
304
|
+
Rack::Utils.delete_cookie_header!(headers, opts[:upgrade_from_rack_session_cookie_key], opts[:upgrade_from_rack_session_cookie_options])
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
nil
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
private
|
|
311
|
+
|
|
312
|
+
# Load the session by looking for the appropriate cookie, or falling
|
|
313
|
+
# back to the rack session cookie if configured.
|
|
314
|
+
def _load_session
|
|
315
|
+
opts = roda_class.opts[:sessions]
|
|
316
|
+
cs = cookies
|
|
317
|
+
|
|
318
|
+
if data = cs[opts[:key]]
|
|
319
|
+
_deserialize_session(data)
|
|
320
|
+
elsif (key = opts[:upgrade_from_rack_session_cookie_key]) && (data = cs[key])
|
|
321
|
+
_deserialize_rack_session(data)
|
|
322
|
+
end || {}
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# If 'rack.errors' is set, write the error message to it.
|
|
326
|
+
# This is used for errors that shouldn't be raised as exceptions,
|
|
327
|
+
# such as improper session cookies.
|
|
328
|
+
def _session_serialization_error(msg)
|
|
329
|
+
return unless error_stream = @env['rack.errors']
|
|
330
|
+
error_stream.puts(msg)
|
|
331
|
+
nil
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Interpret given cookie data as a Rack::Session::Cookie
|
|
335
|
+
# serialized session using the default Rack::Session::Cookie
|
|
336
|
+
# hmac and coder.
|
|
337
|
+
def _deserialize_rack_session(data)
|
|
338
|
+
opts = roda_class.opts[:sessions]
|
|
339
|
+
data, digest = data.split("--", 2)
|
|
340
|
+
unless digest
|
|
341
|
+
return _session_serialization_error("Not decoding Rack::Session::Cookie session: invalid format")
|
|
342
|
+
end
|
|
343
|
+
unless Rack::Utils.secure_compare(digest, OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, opts[:upgrade_from_rack_session_cookie_secret], data))
|
|
344
|
+
return _session_serialization_error("Not decoding Rack::Session::Cookie session: HMAC invalid")
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
begin
|
|
348
|
+
session = Marshal.load(data.unpack('m').first)
|
|
349
|
+
rescue
|
|
350
|
+
return _session_serialization_error("Error decoding Rack::Session::Cookie session: not base64 encoded marshal dump")
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Mark rack session cookie for deletion on success
|
|
354
|
+
env[SESSION_DELETE_RACK_COOKIE] = true
|
|
355
|
+
|
|
356
|
+
# Delete the session id before serializing it. Starting in rack 2.0.8,
|
|
357
|
+
# this is an object and not just a string, and calling to_s on it raises
|
|
358
|
+
# a RuntimeError.
|
|
359
|
+
session.delete("session_id")
|
|
360
|
+
|
|
361
|
+
# Convert the rack session by roundtripping it through
|
|
362
|
+
# the parser and serializer, so that you would get the
|
|
363
|
+
# same result as you would if the session was handled
|
|
364
|
+
# by this plugin.
|
|
365
|
+
env[SESSION_SERIALIZED] = data = opts[:serializer].call(session)
|
|
366
|
+
env[SESSION_CREATED_AT] = Time.now.to_i
|
|
367
|
+
opts[:parser].call(data)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Interpret given cookie data as a Rack::Session::Cookie
|
|
371
|
+
def _deserialize_session(data)
|
|
372
|
+
opts = roda_class.opts[:sessions]
|
|
373
|
+
|
|
374
|
+
begin
|
|
375
|
+
data = Base64_.urlsafe_decode64(data)
|
|
376
|
+
rescue ArgumentError
|
|
377
|
+
return _session_serialization_error("Unable to decode session: invalid base64")
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
case version = data.getbyte(0)
|
|
381
|
+
when 1
|
|
382
|
+
per_cookie_secret = true
|
|
383
|
+
# minimum length (1+32+16+12+32) (version+random_data+cipher_iv+minimum session+hmac)
|
|
384
|
+
# 1 : version
|
|
385
|
+
# 32 : random_data (if per_cookie_cipher_secret)
|
|
386
|
+
# 16 : cipher_iv
|
|
387
|
+
# 12 : minimum_session
|
|
388
|
+
# 2 : bitmap for gzip + padding info
|
|
389
|
+
# 4 : creation time
|
|
390
|
+
# 4 : update time
|
|
391
|
+
# 2 : data
|
|
392
|
+
# 32 : HMAC-SHA-256
|
|
393
|
+
min_data_length = 93
|
|
394
|
+
when 0
|
|
395
|
+
per_cookie_secret = false
|
|
396
|
+
# minimum length (1+16+12+32) (version+cipher_iv+minimum session+hmac)
|
|
397
|
+
min_data_length = 61
|
|
398
|
+
when nil
|
|
399
|
+
return _session_serialization_error("Unable to decode session: no data")
|
|
400
|
+
else
|
|
401
|
+
return _session_serialization_error("Unable to decode session: version marker unsupported")
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
length = data.bytesize
|
|
405
|
+
if data.length < min_data_length
|
|
406
|
+
return _session_serialization_error("Unable to decode session: data too short")
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
encrypted_data = data.slice!(0, length-32)
|
|
410
|
+
unless Rack::Utils.secure_compare(data, OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, opts[:hmac_secret], encrypted_data+opts[:key]))
|
|
411
|
+
if opts[:old_hmac_secret] && Rack::Utils.secure_compare(data, OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, opts[:old_hmac_secret], encrypted_data+opts[:key]))
|
|
412
|
+
use_old_cipher_secret = true
|
|
413
|
+
else
|
|
414
|
+
return _session_serialization_error("Not decoding session: HMAC invalid")
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# Remove version
|
|
419
|
+
encrypted_data.slice!(0)
|
|
420
|
+
|
|
421
|
+
cipher_secret = opts[use_old_cipher_secret ? :old_cipher_secret : :cipher_secret]
|
|
422
|
+
if per_cookie_secret
|
|
423
|
+
cipher_secret = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, cipher_secret, encrypted_data.slice!(0, 32))
|
|
424
|
+
end
|
|
425
|
+
cipher_iv = encrypted_data.slice!(0, 16)
|
|
426
|
+
|
|
427
|
+
cipher = OpenSSL::Cipher.new("aes-256-ctr")
|
|
428
|
+
|
|
429
|
+
# Not rescuing cipher errors. If there is an error in the decryption, that's
|
|
430
|
+
# either a bug in the plugin that needs to be fixed, or an attacker is already
|
|
431
|
+
# able to forge a valid HMAC, in which case the error should be raised to
|
|
432
|
+
# alert the application owner about the problem.
|
|
433
|
+
cipher.decrypt
|
|
434
|
+
cipher.key = cipher_secret
|
|
435
|
+
cipher.iv = cipher_iv
|
|
436
|
+
data = cipher.update(encrypted_data) << cipher.final
|
|
437
|
+
|
|
438
|
+
bitmap, created_at, updated_at = data.unpack('vVV')
|
|
439
|
+
padding_bytes = bitmap & PADDING_MASK
|
|
440
|
+
now = Time.now.to_i
|
|
441
|
+
if (max = opts[:max_seconds]) && now > created_at + max
|
|
442
|
+
return _session_serialization_error("Not returning session: maximum session time expired")
|
|
443
|
+
end
|
|
444
|
+
if (max = opts[:max_idle_seconds]) && now > updated_at + max
|
|
445
|
+
return _session_serialization_error("Not returning session: maximum session idle time expired")
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
data = data.slice(10+padding_bytes, data.bytesize)
|
|
449
|
+
|
|
450
|
+
if bitmap & DEFLATE_BIT > 0
|
|
451
|
+
data = Zlib::Inflate.inflate(data)
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
env = @env
|
|
455
|
+
env[SESSION_CREATED_AT] = created_at
|
|
456
|
+
env[SESSION_UPDATED_AT] = updated_at
|
|
457
|
+
env[SESSION_SERIALIZED] = data
|
|
458
|
+
env[SESSION_VERSION_NUM] = version
|
|
459
|
+
|
|
460
|
+
opts[:parser].call(data)
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def _serialize_session(session)
|
|
464
|
+
opts = roda_class.opts[:sessions]
|
|
465
|
+
env = @env
|
|
466
|
+
now = Time.now.to_i
|
|
467
|
+
json_data = opts[:serializer].call(session).force_encoding('BINARY')
|
|
468
|
+
|
|
469
|
+
if (serialized_session = env[SESSION_SERIALIZED]) &&
|
|
470
|
+
(opts[:session_version_num] == env[SESSION_VERSION_NUM]) &&
|
|
471
|
+
(updated_at = env[SESSION_UPDATED_AT]) &&
|
|
472
|
+
(now - updated_at < opts[:skip_within]) &&
|
|
473
|
+
(serialized_session == json_data)
|
|
474
|
+
return
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
bitmap = 0
|
|
478
|
+
json_length = json_data.bytesize
|
|
479
|
+
gzip_over = opts[:gzip_over]
|
|
480
|
+
|
|
481
|
+
if gzip_over && json_length > gzip_over
|
|
482
|
+
json_data = Zlib.deflate(json_data)
|
|
483
|
+
json_length = json_data.bytesize
|
|
484
|
+
bitmap |= DEFLATE_BIT
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# When calculating padding bytes to use, include 10 bytes for bitmap and
|
|
488
|
+
# session create/update times, so total size of encrypted data is a
|
|
489
|
+
# multiple of pad_size.
|
|
490
|
+
if (pad_size = opts[:pad_size]) && (padding_bytes = (json_length+10) % pad_size) != 0
|
|
491
|
+
padding_bytes = pad_size - padding_bytes
|
|
492
|
+
bitmap |= padding_bytes
|
|
493
|
+
padding_data = SecureRandom.random_bytes(padding_bytes)
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
session_create_time = env[SESSION_CREATED_AT]
|
|
497
|
+
serialized_data = [bitmap, session_create_time||now, now].pack('vVV')
|
|
498
|
+
|
|
499
|
+
serialized_data << padding_data if padding_data
|
|
500
|
+
serialized_data << json_data
|
|
501
|
+
|
|
502
|
+
cipher_secret = opts[:cipher_secret]
|
|
503
|
+
if opts[:per_cookie_cipher_secret]
|
|
504
|
+
version = "\1"
|
|
505
|
+
per_cookie_secret_base = SecureRandom.random_bytes(32)
|
|
506
|
+
cipher_secret = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, cipher_secret, per_cookie_secret_base)
|
|
507
|
+
else
|
|
508
|
+
version = "\0"
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
cipher = OpenSSL::Cipher.new("aes-256-ctr")
|
|
512
|
+
cipher.encrypt
|
|
513
|
+
cipher.key = cipher_secret
|
|
514
|
+
cipher_iv = cipher.random_iv
|
|
515
|
+
encrypted_data = cipher.update(serialized_data) << cipher.final
|
|
516
|
+
|
|
517
|
+
data = String.new
|
|
518
|
+
data << version
|
|
519
|
+
data << per_cookie_secret_base if per_cookie_secret_base
|
|
520
|
+
data << cipher_iv
|
|
521
|
+
data << encrypted_data
|
|
522
|
+
data << OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, opts[:hmac_secret], data+opts[:key])
|
|
523
|
+
|
|
524
|
+
data = Base64_.urlsafe_encode64(data)
|
|
525
|
+
|
|
526
|
+
if data.bytesize >= 4096
|
|
527
|
+
raise CookieTooLarge, "attempted to create cookie larger than 4096 bytes (bytes: #{data.bytesize})"
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
data
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
register_plugin(:sessions, Sessions)
|
|
536
|
+
end
|
|
537
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The shared_vars plugin adds a +shared+ method for storing
|
|
7
|
+
# shared variables across nested Roda apps.
|
|
8
|
+
#
|
|
9
|
+
# class API < Roda
|
|
10
|
+
# plugin :shared_vars
|
|
11
|
+
# route do |r|
|
|
12
|
+
# user = shared[:user]
|
|
13
|
+
# # ...
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# class App < Roda
|
|
18
|
+
# plugin :shared_vars
|
|
19
|
+
#
|
|
20
|
+
# route do |r|
|
|
21
|
+
# r.on Integer do |user_id|
|
|
22
|
+
# shared[:user] = User[user_id]
|
|
23
|
+
# r.run API
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# If you pass a hash to shared, it will update the shared
|
|
29
|
+
# vars with the content of the hash:
|
|
30
|
+
#
|
|
31
|
+
# route do |r|
|
|
32
|
+
# r.on Integer do |user_id|
|
|
33
|
+
# shared(user: User[user_id])
|
|
34
|
+
# r.run API
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# You can also pass a block to shared, which will set the
|
|
39
|
+
# shared variables only for the given block, restoring the
|
|
40
|
+
# previous shared variables afterward:
|
|
41
|
+
#
|
|
42
|
+
# route do |r|
|
|
43
|
+
# r.on Integer do |user_id|
|
|
44
|
+
# shared(user: User[user_id]) do
|
|
45
|
+
# r.run API
|
|
46
|
+
# end
|
|
47
|
+
# end
|
|
48
|
+
# end
|
|
49
|
+
module SharedVars
|
|
50
|
+
module InstanceMethods
|
|
51
|
+
# Returns the current shared vars for the request. These are
|
|
52
|
+
# stored in the request's environment, so they will be implicitly
|
|
53
|
+
# shared with other apps using this plugin.
|
|
54
|
+
#
|
|
55
|
+
# If the +vars+ argument is given, it should be a hash that will be
|
|
56
|
+
# merged into the current shared vars.
|
|
57
|
+
#
|
|
58
|
+
# If a block is given, a +vars+ argument must be provided, and it will
|
|
59
|
+
# only make the changes to the shared vars for the duration of the
|
|
60
|
+
# block, restoring the previous shared vars before the block returns.
|
|
61
|
+
def shared(vars=nil)
|
|
62
|
+
h = env['roda.shared'] ||= {}
|
|
63
|
+
|
|
64
|
+
if defined?(yield)
|
|
65
|
+
if vars
|
|
66
|
+
begin
|
|
67
|
+
env['roda.shared'] = h.merge(vars)
|
|
68
|
+
yield
|
|
69
|
+
ensure
|
|
70
|
+
env['roda.shared'] = h
|
|
71
|
+
end
|
|
72
|
+
else
|
|
73
|
+
raise RodaError, "must pass a vars hash when calling shared with a block"
|
|
74
|
+
end
|
|
75
|
+
elsif vars
|
|
76
|
+
h.merge!(vars)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
h
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
register_plugin(:shared_vars, SharedVars)
|
|
85
|
+
end
|
|
86
|
+
end
|