hanami 2.2.1 → 2.3.0.beta1
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 +4 -4
- data/CHANGELOG.md +37 -1
- data/README.md +20 -35
- data/hanami.gemspec +3 -2
- data/lib/hanami/app.rb +2 -0
- data/lib/hanami/config/actions/content_security_policy.rb +23 -0
- data/lib/hanami/config/actions.rb +21 -0
- data/lib/hanami/config/console.rb +79 -0
- data/lib/hanami/config/logger.rb +1 -1
- data/lib/hanami/config.rb +13 -0
- data/lib/hanami/constants.rb +3 -0
- data/lib/hanami/extensions/db/repo.rb +11 -6
- data/lib/hanami/extensions/view/context.rb +10 -0
- data/lib/hanami/helpers/assets_helper.rb +92 -25
- data/lib/hanami/middleware/content_security_policy_nonce.rb +53 -0
- data/lib/hanami/slice.rb +22 -6
- data/lib/hanami/slice_registrar.rb +1 -1
- data/lib/hanami/version.rb +1 -1
- data/lib/hanami.rb +10 -2
- data/spec/integration/assets/cross_slice_assets_helpers_spec.rb +0 -1
- data/spec/integration/assets/serve_static_assets_spec.rb +1 -1
- data/spec/integration/container/autoloader_spec.rb +2 -0
- data/spec/integration/db/db_spec.rb +1 -1
- data/spec/integration/db/logging_spec.rb +63 -0
- data/spec/integration/db/repo_spec.rb +87 -2
- data/spec/integration/logging/exception_logging_spec.rb +6 -1
- data/spec/integration/rack_app/middleware_spec.rb +4 -11
- data/spec/integration/view/helpers/form_helper_spec.rb +1 -1
- data/spec/integration/web/content_security_policy_nonce_spec.rb +251 -0
- data/spec/support/app_integration.rb +2 -1
- data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +7 -0
- data/spec/unit/hanami/config/console_spec.rb +22 -0
- data/spec/unit/hanami/env_spec.rb +10 -13
- data/spec/unit/hanami/slice_spec.rb +18 -0
- data/spec/unit/hanami/version_spec.rb +1 -1
- data/spec/unit/hanami/web/rack_logger_spec.rb +11 -4
- metadata +27 -18
- data/spec/support/shared_examples/cli/generate/app.rb +0 -494
- data/spec/support/shared_examples/cli/generate/migration.rb +0 -32
- data/spec/support/shared_examples/cli/generate/model.rb +0 -81
- data/spec/support/shared_examples/cli/new.rb +0 -97
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8faef478dec45e673c25c67704b0fd7a363e784061412c64e96735040f2d07f
|
4
|
+
data.tar.gz: b88eac54b8ba6241645b616ba9eafd20d54edb888e7fca8abc9e310f414dabe5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8233b2e453669ae1528c3cadd8032cbff3a7337c590bb0962d307ddd71bb8b18e28f56cc19a5644225efaf0ad0bfa2f5a008dd8fbae2615a9bc819906e47e1cc
|
7
|
+
data.tar.gz: 07551dada781667f44be90593599a0f505e9580d82d98c70df4e18e4288651e68fb26130d3504bf228dd2b7d2237af7494d7cfac86cd06ece733920657adfa83
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,38 @@
|
|
1
1
|
# Hanami
|
2
2
|
|
3
|
-
|
3
|
+
## [Unreleased]
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
### Changed
|
8
|
+
|
9
|
+
### Deprecated
|
10
|
+
|
11
|
+
### Removed
|
12
|
+
|
13
|
+
### Fixed
|
14
|
+
|
15
|
+
### Security
|
16
|
+
|
17
|
+
## [v2.3.0.beta1] - 2025-10-03
|
18
|
+
|
19
|
+
### Added
|
20
|
+
|
21
|
+
- Add `config.console` settings to app. Set an alternative engine with e.g. `config.console.engine = :pry` (`:irb` is default). Add your own methods to the console with `config.console.include MyModule, AnotherModule`. (@alassek in #1540)
|
22
|
+
- Support optional nonce in Rack requests, CSP header rules and view helpers (@svoop in #1500)
|
23
|
+
- Check `ENV["APP_ENV"]` for the Hanami env if `ENV["HANAMI_ENV"]` is not set. The order of environment variable checks is now `HANAMI_ENV`->`APP_ENV`->`RACK_ENV` (@svoop in #1487).
|
24
|
+
|
25
|
+
### Changed
|
26
|
+
|
27
|
+
- Support both Rack v2 and v3. (@kyleplump in #1493)
|
28
|
+
- Support single-character slice names. (@aaronmallen in #1528)
|
29
|
+
|
30
|
+
### Fixed
|
31
|
+
|
32
|
+
- Allow `include Deps` to be used in `Hanami::DB::Repo` subclasses. (@wuarmin in #1523)
|
33
|
+
- Properly infer root relations for deeper `Hanami::DB::Repo` subclasses, such as in slices. (@wuarmin in #1478)
|
34
|
+
- Delay loading `config/routes.rb` until after autoloading is setup, which means you can access your constants there. (@timriley in #1539)
|
35
|
+
- Avoid warning from referencing deprecated `URI::DEFAULT_PARSER`. (@wuarmin in #1518)
|
4
36
|
|
5
37
|
## v2.2.1 - 2024-11-12
|
6
38
|
|
@@ -1478,3 +1510,7 @@ end
|
|
1478
1510
|
- [Luca Guidi] Introduced `Lotus::Configuration`
|
1479
1511
|
- [Luca Guidi] Introduced `Lotus::Application`
|
1480
1512
|
- [Luca Guidi] Official support for MRI 2.0
|
1513
|
+
|
1514
|
+
|
1515
|
+
[unreleased]: https://github.com/hanami/hanami/compare/v2.3.0.beta1...HEAD
|
1516
|
+
[v2.3.0.beta1] https://github.com/hanami/hanami/compare/v2.2.1...v2.3.0.beta1
|
data/README.md
CHANGED
@@ -1,12 +1,6 @@
|
|
1
1
|
# Hanami :cherry_blossom:
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
## Version
|
6
|
-
|
7
|
-
**This branch contains the code for `hanami`: 2.2**
|
8
|
-
|
9
|
-
## Frameworks
|
3
|
+
**A flexible framework for maintainable Ruby apps.**
|
10
4
|
|
11
5
|
Hanami is a **full-stack** Ruby web framework. It's made up of smaller, single-purpose libraries.
|
12
6
|
|
@@ -14,8 +8,8 @@ This repository is for the full-stack framework, which provides the glue that ti
|
|
14
8
|
|
15
9
|
* [**Hanami::Router**](https://github.com/hanami/router) - Rack compatible HTTP router for Ruby
|
16
10
|
* [**Hanami::Controller**](https://github.com/hanami/controller) - Full featured, fast and testable actions for Rack
|
17
|
-
* [**Hanami::Validations**](https://github.com/hanami/validations) - Parameter validations & coercion for actions
|
18
11
|
* [**Hanami::View**](https://github.com/hanami/view) - Presentation with a separation between views and templates
|
12
|
+
* [**Hanami::DB**](https://github.com/hanami/db) - Database integration, complete with migrations, repositories, relations, and structs
|
19
13
|
* [**Hanami::Assets**](https://github.com/hanami/assets) - Assets management for Ruby
|
20
14
|
|
21
15
|
These components are designed to be used independently or together in a Hanami application.
|
@@ -24,12 +18,10 @@ These components are designed to be used independently or together in a Hanami a
|
|
24
18
|
|
25
19
|
[](https://badge.fury.io/rb/hanami)
|
26
20
|
[](https://github.com/hanami/hanami/actions?query=workflow%3Aci+branch%3Amain)
|
27
|
-
[](https://codecov.io/gh/hanami/hanami)
|
28
|
-
[](https://depfu.com/github/hanami/hanami?project=Bundler)
|
29
21
|
|
30
22
|
## Installation
|
31
23
|
|
32
|
-
|
24
|
+
Hanami supports Ruby (MRI) 3.1+.
|
33
25
|
|
34
26
|
```shell
|
35
27
|
gem install hanami
|
@@ -40,7 +32,8 @@ gem install hanami
|
|
40
32
|
```shell
|
41
33
|
hanami new bookshelf
|
42
34
|
cd bookshelf && bundle
|
43
|
-
bundle exec hanami
|
35
|
+
bundle exec hanami dev
|
36
|
+
# Now visit http://localhost:2300
|
44
37
|
```
|
45
38
|
|
46
39
|
Please follow along with the [Getting Started guide](https://guides.hanamirb.org/getting-started/).
|
@@ -49,30 +42,22 @@ Please follow along with the [Getting Started guide](https://guides.hanamirb.org
|
|
49
42
|
|
50
43
|
You can give back to Open Source, by supporting Hanami development via [GitHub Sponsors](https://github.com/sponsors/hanami).
|
51
44
|
|
52
|
-
### Supporters
|
53
|
-
|
54
|
-
* [Trung Lê](https://github.com/runlevel5)
|
55
|
-
* [James Carlson](https://github.com/jxxcarlson)
|
56
|
-
* [Creditas](https://www.creditas.com.br/)
|
57
|
-
|
58
45
|
## Contact
|
59
46
|
|
60
|
-
* Home page
|
61
|
-
* Community
|
62
|
-
* Guides
|
63
|
-
*
|
64
|
-
*
|
65
|
-
*
|
66
|
-
* Bugs/Issues: https://github.com/hanami/hanami/issues
|
67
|
-
* Stack Overflow: http://stackoverflow.com/questions/tagged/hanami
|
68
|
-
* Forum: https://discourse.hanamirb.org
|
69
|
-
* **Chat**: http://chat.hanamirb.org
|
47
|
+
* [Home page](http://hanamirb.org)
|
48
|
+
* [Community](http://hanamirb.org/community)
|
49
|
+
* [Guides](https://guides.hanamirb.org)
|
50
|
+
* [Issues](https://github.com/hanami/hanami/issues)
|
51
|
+
* [Forum](https://discourse.hanamirb.org)
|
52
|
+
* [Chat](https://discord.gg/KFCxDmk3JQ)
|
70
53
|
|
71
54
|
## Community
|
72
55
|
|
73
|
-
We
|
56
|
+
We care about building a friendly, inclusive and helpful community. We welcome people of all backgrounds, genders and experience levels, and respect you all equally.
|
57
|
+
|
58
|
+
We do not tolerate nazis, transphobes, racists, or any kind of bigotry. See our [code of conduct](http://hanamirb.org/community/#code-of-conduct) for more.
|
74
59
|
|
75
|
-
## Contributing
|
60
|
+
## Contributing
|
76
61
|
|
77
62
|
1. Fork it ( https://github.com/hanami/hanami/fork )
|
78
63
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
@@ -110,14 +95,14 @@ $ bundle exec rspec path/to/spec.rb
|
|
110
95
|
|
111
96
|
### Development Requirements
|
112
97
|
|
113
|
-
|
114
|
-
|
115
|
-
|
98
|
+
* Ruby >= 3.1
|
99
|
+
* Bundler
|
100
|
+
* Node.js
|
116
101
|
|
117
102
|
## Versioning
|
118
103
|
|
119
|
-
|
104
|
+
Hanami uses [Semantic Versioning 2.0.0](http://semver.org).
|
120
105
|
|
121
106
|
## Copyright
|
122
107
|
|
123
|
-
Copyright © 2014–
|
108
|
+
Copyright © 2014–2025 Hanami Team – Released under MIT License.
|
data/hanami.gemspec
CHANGED
@@ -38,12 +38,13 @@ Gem::Specification.new do |spec|
|
|
38
38
|
spec.add_dependency "dry-monitor", "~> 1.0", ">= 1.0.1", "< 2"
|
39
39
|
spec.add_dependency "dry-system", "~> 1.1"
|
40
40
|
spec.add_dependency "dry-logger", "~> 1.0", "< 2"
|
41
|
-
spec.add_dependency "hanami-cli", "~> 2.
|
41
|
+
spec.add_dependency "hanami-cli", "~> 2.3.0.beta1"
|
42
42
|
spec.add_dependency "hanami-utils", "~> 2.2"
|
43
43
|
spec.add_dependency "json", ">= 2.7.2"
|
44
44
|
spec.add_dependency "zeitwerk", "~> 2.6"
|
45
|
+
spec.add_dependency "rack-session"
|
45
46
|
|
46
47
|
spec.add_development_dependency "rspec", "~> 3.8"
|
47
|
-
spec.add_development_dependency "rack-test", "~>
|
48
|
+
spec.add_development_dependency "rack-test", "~> 2.0"
|
48
49
|
spec.add_development_dependency "rake", "~> 13.0"
|
49
50
|
end
|
data/lib/hanami/app.rb
CHANGED
@@ -168,6 +168,8 @@ module Hanami
|
|
168
168
|
end
|
169
169
|
|
170
170
|
def prepare_autoloader
|
171
|
+
autoloader.tag = "hanami.app.#{slice_name.name}"
|
172
|
+
|
171
173
|
# Component dirs are automatically pushed to the autoloader by dry-system's zeitwerk plugin.
|
172
174
|
# This method adds other dirs that are not otherwise configured as component dirs.
|
173
175
|
|
@@ -96,6 +96,29 @@ module Hanami
|
|
96
96
|
@policy.delete(key)
|
97
97
|
end
|
98
98
|
|
99
|
+
# Returns true if 'nonce' is used in any of the policies.
|
100
|
+
#
|
101
|
+
# @return [Boolean]
|
102
|
+
#
|
103
|
+
# @api public
|
104
|
+
# @since x.x.x
|
105
|
+
def nonce?
|
106
|
+
@policy.any? { _2.match?(/'nonce'/) }
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns an array of middleware name to support 'nonce' in
|
110
|
+
# policies, or an empty array if 'nonce' is not used.
|
111
|
+
#
|
112
|
+
# @return [Array<(Symbol, Array)>]
|
113
|
+
#
|
114
|
+
# @api public
|
115
|
+
# @since x.x.x
|
116
|
+
def middleware
|
117
|
+
return [] unless nonce?
|
118
|
+
|
119
|
+
[Hanami::Middleware::ContentSecurityPolicyNonce]
|
120
|
+
end
|
121
|
+
|
99
122
|
# @since 2.0.0
|
100
123
|
# @api private
|
101
124
|
def to_s
|
@@ -74,6 +74,23 @@ module Hanami
|
|
74
74
|
# @since 2.0.0
|
75
75
|
attr_accessor :content_security_policy
|
76
76
|
|
77
|
+
# Returns the proc to generate Content Security Policy nonce values.
|
78
|
+
#
|
79
|
+
# The current Rack request object is provided as an optional argument
|
80
|
+
# to the proc, enabling the generation of nonces based on session IDs.
|
81
|
+
#
|
82
|
+
# @example Independent random nonce (default)
|
83
|
+
# -> { SecureRandom.urlsafe_base64(16) }
|
84
|
+
#
|
85
|
+
# @example Session dependent nonce
|
86
|
+
# ->(request) { Digest::SHA256.base64digest(request.session[:uuid])[0, 16] }
|
87
|
+
#
|
88
|
+
# @return [Proc]
|
89
|
+
#
|
90
|
+
# @api public
|
91
|
+
# @since x.x.x
|
92
|
+
setting :content_security_policy_nonce_generator, default: -> { SecureRandom.urlsafe_base64(16) }
|
93
|
+
|
77
94
|
# @!attribute [rw] method_override
|
78
95
|
# Sets or returns whether HTTP method override should be enabled for action classes.
|
79
96
|
#
|
@@ -138,6 +155,10 @@ module Hanami
|
|
138
155
|
end
|
139
156
|
end
|
140
157
|
|
158
|
+
# @api public
|
159
|
+
# @since x.x.x
|
160
|
+
def content_security_policy? = !!@content_security_policy
|
161
|
+
|
141
162
|
private
|
142
163
|
|
143
164
|
# Apply defaults for base config
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/configurable"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
class Config
|
7
|
+
# Hanami console config
|
8
|
+
#
|
9
|
+
# @since 2.3.0
|
10
|
+
# @api public
|
11
|
+
class Console
|
12
|
+
include Dry::Configurable
|
13
|
+
|
14
|
+
# @!attribute [rw] engine
|
15
|
+
# Sets or returns the interactive console engine to be used by `hanami console`.
|
16
|
+
# Supported values are `:irb` (default) and `:pry`.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# config.console.engine = :pry
|
20
|
+
#
|
21
|
+
# @return [Symbol]
|
22
|
+
#
|
23
|
+
# @api public
|
24
|
+
# @since 2.3.0
|
25
|
+
setting :engine, default: :irb
|
26
|
+
|
27
|
+
# Returns the complete list of extensions to be used in the console
|
28
|
+
#
|
29
|
+
# @example
|
30
|
+
# config.console.include MyExtension, OtherExtension
|
31
|
+
# config.console.include ThirdExtension
|
32
|
+
#
|
33
|
+
# config.console.extensions
|
34
|
+
# # => [MyExtension, OtherExtension, ThirdExtension]
|
35
|
+
#
|
36
|
+
# @return [Array<Module>]
|
37
|
+
#
|
38
|
+
# @api public
|
39
|
+
# @since 2.3.0
|
40
|
+
def extensions = @extensions.dup.freeze
|
41
|
+
|
42
|
+
# Define a module extension to be included in the console
|
43
|
+
#
|
44
|
+
# @param mod [Module] one or more modules to be included in the console
|
45
|
+
# @return [void]
|
46
|
+
#
|
47
|
+
# @api public
|
48
|
+
# @since 2.3.0
|
49
|
+
def include(*mod)
|
50
|
+
@extensions.concat(mod).uniq!
|
51
|
+
end
|
52
|
+
|
53
|
+
# @api private
|
54
|
+
def initialize
|
55
|
+
@extensions = []
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# @api private
|
61
|
+
def initialize_copy(source)
|
62
|
+
super
|
63
|
+
@extensions = [*source.extensions]
|
64
|
+
end
|
65
|
+
|
66
|
+
def method_missing(name, *args, &block)
|
67
|
+
if config.respond_to?(name)
|
68
|
+
config.public_send(name, *args, &block)
|
69
|
+
else
|
70
|
+
super
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def respond_to_missing?(name, _include_all = false)
|
75
|
+
config.respond_to?(name) || super
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/hanami/config/logger.rb
CHANGED
data/lib/hanami/config.rb
CHANGED
@@ -6,6 +6,7 @@ require "dry/configurable"
|
|
6
6
|
require "dry/inflector"
|
7
7
|
|
8
8
|
require_relative "constants"
|
9
|
+
require_relative "config/console"
|
9
10
|
|
10
11
|
module Hanami
|
11
12
|
# Hanami app config
|
@@ -184,6 +185,18 @@ module Hanami
|
|
184
185
|
"Hanami::Router::NotFoundError" => :not_found,
|
185
186
|
)
|
186
187
|
|
188
|
+
# @!attribute [rw] console
|
189
|
+
# Returns the app's console config
|
190
|
+
#
|
191
|
+
# @example
|
192
|
+
# config.console.engine # => :irb
|
193
|
+
#
|
194
|
+
# @return [Hanami::Config::Console]
|
195
|
+
#
|
196
|
+
# @api public
|
197
|
+
# @since 2.3.0
|
198
|
+
setting :console, default: Hanami::Config::Console.new
|
199
|
+
|
187
200
|
# Returns the app or slice's {Hanami::SliceName slice_name}.
|
188
201
|
#
|
189
202
|
# This is useful for default config values that depend on this name.
|
data/lib/hanami/constants.rb
CHANGED
@@ -69,7 +69,8 @@ module Hanami
|
|
69
69
|
resolve_rom = method(:resolve_rom)
|
70
70
|
|
71
71
|
define_method(:new) do |**kwargs|
|
72
|
-
|
72
|
+
container = kwargs.delete(:container) || resolve_rom.()
|
73
|
+
super(container: container, **kwargs)
|
73
74
|
end
|
74
75
|
end
|
75
76
|
|
@@ -77,14 +78,18 @@ module Hanami
|
|
77
78
|
slice["db.rom"]
|
78
79
|
end
|
79
80
|
|
80
|
-
|
81
|
-
return unless repo_class.to_s.end_with?("Repo")
|
81
|
+
REPO_CLASS_NAME_REGEX = /^(?<name>.+)_(repo|repository)$/
|
82
82
|
|
83
|
-
|
83
|
+
def root_for_repo_class(repo_class)
|
84
|
+
repo_class_name = slice.inflector.demodulize(repo_class)
|
84
85
|
.then { slice.inflector.underscore(_1) }
|
85
|
-
|
86
|
+
|
87
|
+
repo_class_match = repo_class_name.match(REPO_CLASS_NAME_REGEX)
|
88
|
+
return unless repo_class_match
|
89
|
+
|
90
|
+
repo_class_match[:name]
|
86
91
|
.then { slice.inflector.pluralize(_1) }
|
87
|
-
.then
|
92
|
+
.then(&:to_sym)
|
88
93
|
end
|
89
94
|
|
90
95
|
def struct_namespace
|
@@ -172,6 +172,16 @@ module Hanami
|
|
172
172
|
@request
|
173
173
|
end
|
174
174
|
|
175
|
+
# Returns true if the view is rendered from within an action and a request is available.
|
176
|
+
#
|
177
|
+
# @return [Boolean]
|
178
|
+
#
|
179
|
+
# @api public
|
180
|
+
# @since x.x.x
|
181
|
+
def request?
|
182
|
+
!!@request
|
183
|
+
end
|
184
|
+
|
175
185
|
# Returns the app's routes helper.
|
176
186
|
#
|
177
187
|
# @return [Hanami::Slice::RoutesHelper] the routes helper
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "uri"
|
4
4
|
require "hanami/view"
|
5
|
+
require_relative "../constants"
|
5
6
|
|
6
7
|
# rubocop:disable Metrics/ModuleLength
|
7
8
|
|
@@ -61,7 +62,10 @@ module Hanami
|
|
61
62
|
|
62
63
|
# @since 0.3.0
|
63
64
|
# @api private
|
64
|
-
|
65
|
+
# TODO: we can drop the defined?-check and fallback once Ruby 3.3 becomes our minimum required version
|
66
|
+
ABSOLUTE_URL_MATCHER = (
|
67
|
+
defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::DEFAULT_PARSER
|
68
|
+
).make_regexp
|
65
69
|
|
66
70
|
# @since 1.1.0
|
67
71
|
# @api private
|
@@ -85,7 +89,12 @@ module Hanami
|
|
85
89
|
# name of the algorithm, then a hyphen, then the hash value of the file.
|
86
90
|
# If more than one algorithm is used, they"ll be separated by a space.
|
87
91
|
#
|
88
|
-
#
|
92
|
+
# If the Content Security Policy uses 'nonce' and the source is not
|
93
|
+
# absolute, the nonce value of the current request is automatically added
|
94
|
+
# as an attribute. You can override this with the `nonce: false` option.
|
95
|
+
# See {#content_security_policy_nonce} for more.
|
96
|
+
#
|
97
|
+
# @param sources [Array<String, #url>] one or more assets by name or absolute URL
|
89
98
|
#
|
90
99
|
# @return [Hanami::View::HTML::SafeString] the markup
|
91
100
|
#
|
@@ -136,6 +145,10 @@ module Hanami
|
|
136
145
|
#
|
137
146
|
# # <script src="/assets/application.js" type="text/javascript" defer="defer"></script>
|
138
147
|
#
|
148
|
+
# @example Disable nonce
|
149
|
+
#
|
150
|
+
# <%= javascript_tag "application", nonce: false %>
|
151
|
+
#
|
139
152
|
# @example Absolute URL
|
140
153
|
#
|
141
154
|
# <%= javascript_tag "https://code.jquery.com/jquery-2.1.4.min.js" %>
|
@@ -154,13 +167,15 @@ module Hanami
|
|
154
167
|
#
|
155
168
|
# # <script src="https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js"
|
156
169
|
# # type="text/javascript"></script>
|
157
|
-
def javascript_tag(*
|
170
|
+
def javascript_tag(*sources, **options)
|
158
171
|
options = options.reject { |k, _| k.to_sym == :src }
|
172
|
+
nonce_option = options.delete(:nonce)
|
159
173
|
|
160
|
-
_safe_tags(*
|
174
|
+
_safe_tags(*sources) do |source|
|
161
175
|
attributes = {
|
162
|
-
src:
|
163
|
-
type: JAVASCRIPT_MIME_TYPE
|
176
|
+
src: _typed_url(source, JAVASCRIPT_EXT),
|
177
|
+
type: JAVASCRIPT_MIME_TYPE,
|
178
|
+
nonce: _nonce(source, nonce_option)
|
164
179
|
}
|
165
180
|
attributes.merge!(options)
|
166
181
|
|
@@ -189,7 +204,12 @@ module Hanami
|
|
189
204
|
# name of the algorithm, then a hyphen, then the hashed value of the file.
|
190
205
|
# If more than one algorithm is used, they"ll be separated by a space.
|
191
206
|
#
|
192
|
-
#
|
207
|
+
# If the Content Security Policy uses 'nonce' and the source is not
|
208
|
+
# absolute, the nonce value of the current request is automatically added
|
209
|
+
# as an attribute. You can override this with the `nonce: false` option.
|
210
|
+
# See {#content_security_policy_nonce} for more.
|
211
|
+
#
|
212
|
+
# @param sources [Array<String, #url>] one or more assets by name or absolute URL
|
193
213
|
#
|
194
214
|
# @return [Hanami::View::HTML::SafeString] the markup
|
195
215
|
#
|
@@ -214,6 +234,10 @@ module Hanami
|
|
214
234
|
# # <link href="/assets/application.css" type="text/css" rel="stylesheet">
|
215
235
|
# # <link href="/assets/dashboard.css" type="text/css" rel="stylesheet">
|
216
236
|
#
|
237
|
+
# @example Disable nonce
|
238
|
+
#
|
239
|
+
# <%= stylesheet_tag "application", nonce: false %>
|
240
|
+
#
|
217
241
|
# @example Subresource Integrity
|
218
242
|
#
|
219
243
|
# <%= stylesheet_tag "application" %>
|
@@ -247,19 +271,21 @@ module Hanami
|
|
247
271
|
#
|
248
272
|
# # <link href="https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.css"
|
249
273
|
# # type="text/css" rel="stylesheet">
|
250
|
-
def stylesheet_tag(*
|
274
|
+
def stylesheet_tag(*sources, **options)
|
251
275
|
options = options.reject { |k, _| k.to_sym == :href }
|
276
|
+
nonce_option = options.delete(:nonce)
|
252
277
|
|
253
|
-
_safe_tags(*
|
278
|
+
_safe_tags(*sources) do |source|
|
254
279
|
attributes = {
|
255
|
-
href:
|
280
|
+
href: _typed_url(source, STYLESHEET_EXT),
|
256
281
|
type: STYLESHEET_MIME_TYPE,
|
257
|
-
rel: STYLESHEET_REL
|
282
|
+
rel: STYLESHEET_REL,
|
283
|
+
nonce: _nonce(source, nonce_option)
|
258
284
|
}
|
259
285
|
attributes.merge!(options)
|
260
286
|
|
261
287
|
if _context.assets.subresource_integrity? || attributes.include?(:integrity)
|
262
|
-
attributes[:integrity] ||= _subresource_integrity_value(
|
288
|
+
attributes[:integrity] ||= _subresource_integrity_value(source, STYLESHEET_EXT)
|
263
289
|
attributes[:crossorigin] ||= CROSSORIGIN_ANONYMOUS
|
264
290
|
end
|
265
291
|
|
@@ -626,7 +652,7 @@ module Hanami
|
|
626
652
|
#
|
627
653
|
# If CDN mode is on, it returns the absolute URL of the asset.
|
628
654
|
#
|
629
|
-
# @param
|
655
|
+
# @param source [String, #url] the asset name or asset object
|
630
656
|
#
|
631
657
|
# @return [String] the asset path
|
632
658
|
#
|
@@ -665,42 +691,71 @@ module Hanami
|
|
665
691
|
# <%= asset_url "application.js" %>
|
666
692
|
#
|
667
693
|
# # "https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js"
|
668
|
-
def asset_url(
|
669
|
-
return
|
670
|
-
return
|
694
|
+
def asset_url(source)
|
695
|
+
return source.url if source.respond_to?(:url)
|
696
|
+
return source if _absolute_url?(source)
|
671
697
|
|
672
|
-
_context.assets[
|
698
|
+
_context.assets[source].url
|
699
|
+
end
|
700
|
+
|
701
|
+
# Random per request nonce value for Content Security Policy (CSP) rules.
|
702
|
+
#
|
703
|
+
# If the `Hanami::Middleware::ContentSecurityPolicyNonce` middleware is
|
704
|
+
# in use, this helper returns the nonce value for the current request
|
705
|
+
# or `nil` otherwise.
|
706
|
+
#
|
707
|
+
# For this policy to work in the browser, you have to add the `'nonce'`
|
708
|
+
# placeholder to the script and/or style source policy rule. It will be
|
709
|
+
# substituted by the current nonce value like `'nonce-A12OggyZ'.
|
710
|
+
#
|
711
|
+
# @return [String, nil] nonce value of the current request
|
712
|
+
#
|
713
|
+
# @since x.x.x
|
714
|
+
#
|
715
|
+
# @example App configuration
|
716
|
+
#
|
717
|
+
# config.middleware.use Hanami::Middleware::ContentSecurityPolicyNonce
|
718
|
+
# config.actions.content_security_policy[:script_src] = "'self' 'nonce'"
|
719
|
+
# config.actions.content_security_policy[:style_src] = "'self' 'nonce'"
|
720
|
+
#
|
721
|
+
# @example View helper
|
722
|
+
#
|
723
|
+
# <script nonce="<%= content_security_policy_nonce %>">
|
724
|
+
def content_security_policy_nonce
|
725
|
+
return unless _context.request?
|
726
|
+
|
727
|
+
_context.request.env[CONTENT_SECURITY_POLICY_NONCE_REQUEST_KEY]
|
673
728
|
end
|
674
729
|
|
675
730
|
private
|
676
731
|
|
677
732
|
# @since 2.1.0
|
678
733
|
# @api private
|
679
|
-
def _safe_tags(*
|
734
|
+
def _safe_tags(*sources, &blk)
|
680
735
|
::Hanami::View::HTML::SafeString.new(
|
681
|
-
|
736
|
+
sources.map(&blk).join(NEW_LINE_SEPARATOR)
|
682
737
|
)
|
683
738
|
end
|
684
739
|
|
685
740
|
# @since 2.1.0
|
686
741
|
# @api private
|
687
|
-
def
|
742
|
+
def _typed_url(source, ext)
|
688
743
|
source = "#{source}#{ext}" if source.is_a?(String) && _append_extension?(source, ext)
|
689
744
|
asset_url(source)
|
690
745
|
end
|
691
746
|
|
692
747
|
# @api private
|
693
|
-
def _subresource_integrity_value(
|
694
|
-
return if _absolute_url?(
|
748
|
+
def _subresource_integrity_value(source, ext)
|
749
|
+
return if _absolute_url?(source)
|
695
750
|
|
696
|
-
|
697
|
-
_context.assets[
|
751
|
+
source = "#{source}#{ext}" unless /#{Regexp.escape(ext)}\z/.match?(source)
|
752
|
+
_context.assets[source].sri
|
698
753
|
end
|
699
754
|
|
700
755
|
# @since 2.1.0
|
701
756
|
# @api private
|
702
757
|
def _absolute_url?(source)
|
703
|
-
ABSOLUTE_URL_MATCHER.match(source)
|
758
|
+
ABSOLUTE_URL_MATCHER.match?(source.respond_to?(:url) ? source.url : source)
|
704
759
|
end
|
705
760
|
|
706
761
|
# @since 1.2.0
|
@@ -711,6 +766,18 @@ module Hanami
|
|
711
766
|
_context.assets.crossorigin?(source)
|
712
767
|
end
|
713
768
|
|
769
|
+
# @since x.x.x
|
770
|
+
# @api private
|
771
|
+
def _nonce(source, nonce_option)
|
772
|
+
if nonce_option == false
|
773
|
+
nil
|
774
|
+
elsif nonce_option == true || (nonce_option.nil? && !_absolute_url?(source))
|
775
|
+
content_security_policy_nonce
|
776
|
+
else
|
777
|
+
nonce_option
|
778
|
+
end
|
779
|
+
end
|
780
|
+
|
714
781
|
# @since 2.1.0
|
715
782
|
# @api private
|
716
783
|
def _source_options(src, options, &blk)
|