hanami-controller 2.0.0.alpha3 → 2.0.0.alpha4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 015e9bf69c01ea01cbdd9a27e7bf4d4cb4f8f9a98f01a14d707fd68457096006
4
- data.tar.gz: 0f2c2e2569453264b41e131451caae329cfbd7d6773d50f610355f5f6fd0b62b
3
+ metadata.gz: 5cc85e1cc9074a18b393db3a0760c7fafb1d944914f2b755bfe0e6e6c35ee6aa
4
+ data.tar.gz: ea3998a90c5f6fb27c720ef400a729a3348b56e6bc5622b8ea07e64e3c2716b2
5
5
  SHA512:
6
- metadata.gz: 7eb2b778608f9dd27cbf777dd00b98e079bc1ec790da5e388113d645babbc9972d10272c083e187dfafa875fe0e2387592f34fc4d48b0ff96be920d1d82c12b8
7
- data.tar.gz: e1290141bb9128031b0593b1edcf2975fe5f01eadb4661fe0bf18c164f339671bf195b43f8933731cab13180b68a7141a4ce6caa01e1df9aabe3e5afb9e0c9a7
6
+ metadata.gz: e843265e291cd6722bf6acf0acb4fb9dc5b16216da02797713b9eb60316802a697b48102c9edf8846c174d987b14c2748f2347224571b089e5b145c229d08579
7
+ data.tar.gz: 0d6cea465c65077a40b41547c323c1e54613f0743f6c20bfe54265b5d5ecea7431e43e33d9e1d01c1cdcddd22eccca0b1a7e186092d99fe2751d43d80c3aa42d
data/CHANGELOG.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # Hanami::Controller
2
2
  Complete, fast and testable actions for Rack
3
3
 
4
+ ## v2.0.0.alpha4 - 2021-12-07
5
+ ### Added
6
+ - [Luca Guidi] Manage Content Security Policy (CSP) defaults and new API via `Hanami::Action::ApplicationConfiguration#content_security_policy`
7
+ - [Tim Riley & Marc Busqué] Provide access to routes inside all application actions via `Hanami::Action::ApplicationAction#routes`
8
+
4
9
  ## v2.0.0.alpha3 - 2021-11-09
5
10
  ### Added
6
11
  - [Luca Guidi] Automatically include session behavior in `Hanami::Action` when sessions are enabled via Hanami application config
@@ -14,7 +14,7 @@ module Hanami
14
14
  def included(action_class)
15
15
  action_class.include InstanceMethods
16
16
 
17
- define_initialize action_class
17
+ define_initialize
18
18
  configure_action action_class
19
19
  extend_behavior action_class
20
20
  end
@@ -25,20 +25,33 @@ module Hanami
25
25
 
26
26
  private
27
27
 
28
- def define_initialize(action_class)
28
+ def define_initialize
29
29
  resolve_view = method(:resolve_paired_view)
30
30
  resolve_context = method(:resolve_view_context)
31
+ resolve_routes = method(:resolve_routes)
31
32
 
32
33
  define_method :initialize do |**deps|
33
34
  # Conditionally assign these to repsect any explictly auto-injected
34
35
  # dependencies provided by the class
35
36
  @view ||= deps[:view] || resolve_view.(self.class)
36
37
  @view_context ||= deps[:view_context] || resolve_context.()
38
+ @routes ||= deps[:routes] || resolve_routes.()
37
39
 
38
40
  super(**deps)
39
41
  end
40
42
  end
41
43
 
44
+ def resolve_paired_view(action_class)
45
+ view_identifiers = application.config.actions.view_name_inferrer.(
46
+ action_name: action_class.name,
47
+ provider: provider
48
+ )
49
+
50
+ view_identifiers.detect { |identifier|
51
+ break provider[identifier] if provider.key?(identifier)
52
+ }
53
+ end
54
+
42
55
  def resolve_view_context
43
56
  identifier = application.config.actions.view_context_identifier
44
57
 
@@ -49,15 +62,8 @@ module Hanami
49
62
  end
50
63
  end
51
64
 
52
- def resolve_paired_view(action_class)
53
- view_identifiers = application.config.actions.view_name_inferrer.(
54
- action_name: action_class.name,
55
- provider: provider
56
- )
57
-
58
- view_identifiers.detect { |identifier|
59
- break provider[identifier] if provider.key?(identifier)
60
- }
65
+ def resolve_routes
66
+ application[:routes_helper] if application.key?(:routes_helper)
61
67
  end
62
68
 
63
69
  def configure_action(action_class)
@@ -87,6 +93,7 @@ module Hanami
87
93
  module InstanceMethods
88
94
  attr_reader :view
89
95
  attr_reader :view_context
96
+ attr_reader :routes
90
97
 
91
98
  def build_response(**options)
92
99
  options = options.merge(view_options: method(:view_options))
@@ -101,11 +108,23 @@ module Hanami
101
108
  {request: req, response: res}
102
109
  end
103
110
 
104
- # Automatically render the view, if the body hasn't been populated yet
105
111
  def finish(req, res, halted)
106
- res.render(view, **req.params, **res.exposures) if view && res.body.empty?
112
+ res.render(view, **req.params) if render?(res)
107
113
  super
108
114
  end
115
+
116
+ # Decide whether to render the current response with the associated view.
117
+ # This can be overridden to enable/disable automatic rendering.
118
+ #
119
+ # @param res [Hanami::Action::Response]
120
+ #
121
+ # @return [TrueClass,FalseClass]
122
+ #
123
+ # @since 2.0.0
124
+ # @api public
125
+ def render?(res)
126
+ view && res.body.empty?
127
+ end
109
128
  end
110
129
  end
111
130
  end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class Action
5
+ class ApplicationConfiguration
6
+ # Configuration for Content Security Policy in Hanami applications
7
+ #
8
+ # @since 2.0.0
9
+ class ContentSecurityPolicy
10
+ # @since 2.0.0
11
+ # @api private
12
+ def initialize(&blk)
13
+ @policy = {
14
+ base_uri: "'self'",
15
+ child_src: "'self'",
16
+ connect_src: "'self'",
17
+ default_src: "'none'",
18
+ font_src: "'self'",
19
+ form_action: "'self'",
20
+ frame_ancestors: "'self'",
21
+ frame_src: "'self'",
22
+ img_src: "'self' https: data:",
23
+ media_src: "'self'",
24
+ object_src: "'none'",
25
+ plugin_types: "application/pdf",
26
+ script_src: "'self'",
27
+ style_src: "'self' 'unsafe-inline' https:"
28
+ }
29
+
30
+ blk&.(self)
31
+ end
32
+
33
+ # @since 2.0.0
34
+ # @api private
35
+ def initialize_copy(original_object)
36
+ @policy = original_object.instance_variable_get(:@policy).dup
37
+ super
38
+ end
39
+
40
+ # Get a CSP setting
41
+ #
42
+ # @param key [Symbol] the underscored name of the CPS setting
43
+ # @return [String,NilClass] the CSP setting, if any
44
+ #
45
+ # @since 2.0.0
46
+ # @api public
47
+ #
48
+ # @example
49
+ # module MyApp
50
+ # class Application < Hanami::Application
51
+ # config.actions.content_security_policy[:base_uri] # => "'self'"
52
+ # end
53
+ # end
54
+ def [](key)
55
+ @policy[key]
56
+ end
57
+
58
+ # Set a CSP setting
59
+ #
60
+ # @param key [Symbol] the underscored name of the CPS setting
61
+ # @param value [String] the CSP setting value
62
+ #
63
+ # @since 2.0.0
64
+ # @api public
65
+ #
66
+ # @example Replace a default value
67
+ # module MyApp
68
+ # class Application < Hanami::Application
69
+ # config.actions.content_security_policy[:plugin_types] = nil
70
+ # end
71
+ # end
72
+ #
73
+ # @example Append to a default value
74
+ # module MyApp
75
+ # class Application < Hanami::Application
76
+ # config.actions.content_security_policy[:script_src] += " https://my.cdn.test"
77
+ # end
78
+ # end
79
+ def []=(key, value)
80
+ @policy[key] = value
81
+ end
82
+
83
+ # Deletes a CSP key
84
+ #
85
+ # @param key [Symbol] the underscored name of the CPS setting
86
+ #
87
+ # @since 2.0.0
88
+ # @api public
89
+ #
90
+ # @example
91
+ # module MyApp
92
+ # class Application < Hanami::Application
93
+ # config.actions.content_security_policy.delete(:object_src)
94
+ # end
95
+ # end
96
+ def delete(key)
97
+ @policy.delete(key)
98
+ end
99
+
100
+ # @since 2.0.0
101
+ # @api private
102
+ def to_str
103
+ @policy.map do |key, value|
104
+ "#{dasherize(key)} #{value}"
105
+ end.join(";\n")
106
+ end
107
+
108
+ private
109
+
110
+ # @since 2.0.0
111
+ # @api private
112
+ def dasherize(key)
113
+ key.to_s.gsub("_", "-")
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "application_configuration/cookies"
4
4
  require_relative "application_configuration/sessions"
5
+ require_relative "application_configuration/content_security_policy"
5
6
  require_relative "configuration"
6
7
  require_relative "view_name_inferrer"
7
8
 
@@ -19,10 +20,18 @@ module Hanami
19
20
  setting :view_name_inferrer, default: ViewNameInferrer
20
21
  setting :view_name_inference_base, default: "views"
21
22
 
22
- def initialize(*)
23
- super
23
+ attr_accessor :content_security_policy
24
+
25
+ def initialize(*, **options)
26
+ super()
24
27
 
25
28
  @base_configuration = Configuration.new
29
+ @content_security_policy = ContentSecurityPolicy.new do |csp|
30
+ if assets_server_url = options[:assets_server_url]
31
+ csp[:script_src] += " #{assets_server_url}"
32
+ csp[:style_src] += " #{assets_server_url}"
33
+ end
34
+ end
26
35
 
27
36
  configure_defaults
28
37
  end
@@ -31,6 +40,10 @@ module Hanami
31
40
  # A nil value for `csrf_protection` means it has not been explicitly configured
32
41
  # (neither true nor false), so we can default it to whether sessions are enabled
33
42
  self.csrf_protection = sessions.enabled? if csrf_protection.nil?
43
+
44
+ if self.content_security_policy
45
+ self.default_headers["Content-Security-Policy"] = self.content_security_policy.to_str
46
+ end
34
47
  end
35
48
 
36
49
  # Returns the list of available settings
@@ -55,22 +68,7 @@ module Hanami
55
68
  self.default_headers = {
56
69
  "X-Frame-Options" => "DENY",
57
70
  "X-Content-Type-Options" => "nosniff",
58
- "X-XSS-Protection" => "1; mode=block",
59
- "Content-Security-Policy" => \
60
- "base-uri 'self'; " \
61
- "child-src 'self'; " \
62
- "connect-src 'self'; " \
63
- "default-src 'none'; " \
64
- "font-src 'self'; " \
65
- "form-action 'self'; " \
66
- "frame-ancestors 'self'; " \
67
- "frame-src 'self'; " \
68
- "img-src 'self' https: data:; " \
69
- "media-src 'self'; " \
70
- "object-src 'none'; " \
71
- "plugin-types application/pdf; " \
72
- "script-src 'self'; " \
73
- "style-src 'self' 'unsafe-inline' https:"
71
+ "X-XSS-Protection" => "1; mode=block"
74
72
  }
75
73
  end
76
74
 
@@ -76,7 +76,7 @@ module Hanami
76
76
  end
77
77
 
78
78
  def render(view, **options)
79
- self.body = view.(**view_options.(request, self), **options).to_str
79
+ self.body = view.(**view_options.(request, self), **exposures.merge(options)).to_str
80
80
  end
81
81
 
82
82
  def format=(args)
@@ -559,7 +559,6 @@ module Hanami
559
559
  # @api private
560
560
  # @abstract
561
561
  #
562
- # @see Hanami::Action::Callable#finish
563
562
  # @see Hanami::Action::Session#finish
564
563
  # @see Hanami::Action::Cookies#finish
565
564
  # @see Hanami::Action::Cache#finish
@@ -3,6 +3,6 @@ module Hanami
3
3
  # Defines the version
4
4
  #
5
5
  # @since 0.1.0
6
- VERSION = '2.0.0.alpha3'.freeze
6
+ VERSION = '2.0.0.alpha4'.freeze
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hanami-controller
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.alpha3
4
+ version: 2.0.0.alpha4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luca Guidi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-09 00:00:00.000000000 Z
11
+ date: 2021-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -134,6 +134,7 @@ files:
134
134
  - lib/hanami/action.rb
135
135
  - lib/hanami/action/application_action.rb
136
136
  - lib/hanami/action/application_configuration.rb
137
+ - lib/hanami/action/application_configuration/content_security_policy.rb
137
138
  - lib/hanami/action/application_configuration/cookies.rb
138
139
  - lib/hanami/action/application_configuration/sessions.rb
139
140
  - lib/hanami/action/base_params.rb
@@ -181,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
181
182
  - !ruby/object:Gem::Version
182
183
  version: 1.3.1
183
184
  requirements: []
184
- rubygems_version: 3.2.3
185
+ rubygems_version: 3.2.29
185
186
  signing_key:
186
187
  specification_version: 4
187
188
  summary: Complete, fast and testable actions for Rack and Hanami