pakyow-routing 1.0.0.rc1
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/CHANGELOG.md +137 -0
- data/LICENSE +4 -0
- data/README.md +33 -0
- data/lib/pakyow/behavior/definition.rb +35 -0
- data/lib/pakyow/routing/actions/respond_missing.rb +13 -0
- data/lib/pakyow/routing/controller/behavior/error_handling.rb +149 -0
- data/lib/pakyow/routing/controller/behavior/param_verification.rb +76 -0
- data/lib/pakyow/routing/controller.rb +872 -0
- data/lib/pakyow/routing/expansion.rb +104 -0
- data/lib/pakyow/routing/extensions/resource.rb +158 -0
- data/lib/pakyow/routing/extensions.rb +3 -0
- data/lib/pakyow/routing/framework.rb +82 -0
- data/lib/pakyow/routing/helpers/exposures.rb +25 -0
- data/lib/pakyow/routing/route.rb +85 -0
- data/lib/pakyow/routing.rb +10 -0
- data/lib/pakyow/security/base.rb +47 -0
- data/lib/pakyow/security/behavior/config.rb +34 -0
- data/lib/pakyow/security/behavior/disabling.rb +37 -0
- data/lib/pakyow/security/behavior/helpers.rb +19 -0
- data/lib/pakyow/security/behavior/insecure.rb +21 -0
- data/lib/pakyow/security/behavior/pipeline.rb +21 -0
- data/lib/pakyow/security/csrf/verify_authenticity_token.rb +26 -0
- data/lib/pakyow/security/csrf/verify_same_origin.rb +73 -0
- data/lib/pakyow/security/errors.rb +19 -0
- data/lib/pakyow/security/helpers/csrf.rb +15 -0
- data/lib/pakyow/security/pipelines/csrf.rb +24 -0
- metadata +98 -0
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module Pakyow
|
6
|
+
module Routing
|
7
|
+
# Expands a route template.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class Expansion
|
11
|
+
attr_reader :expander, :controller, :name
|
12
|
+
|
13
|
+
extend Forwardable
|
14
|
+
def_delegators :@expander, *%i(default template).concat(Controller::DEFINABLE_HTTP_METHODS)
|
15
|
+
def_delegators :@controller, :action
|
16
|
+
|
17
|
+
def initialize(template_name, controller, options, &template_block)
|
18
|
+
@controller = controller
|
19
|
+
|
20
|
+
# Create the controller that stores available routes, groups, and namespaces.
|
21
|
+
#
|
22
|
+
@expander = Controller.make(set_const: false)
|
23
|
+
|
24
|
+
# Evaluate the template to define available routes, groups, and namespaces.
|
25
|
+
#
|
26
|
+
instance_exec(**options, &template_block)
|
27
|
+
|
28
|
+
# Define helper methods for routes
|
29
|
+
#
|
30
|
+
local_expander = @expander
|
31
|
+
@expander.routes.each do |method, routes|
|
32
|
+
routes.each do |route|
|
33
|
+
unless @controller.singleton_class.instance_methods(false).include?(route.name)
|
34
|
+
@controller.define_singleton_method route.name do |*args, &block|
|
35
|
+
# Handle template parts named `new` by determining if we're calling `new` to expand
|
36
|
+
# part of a template, or if we're intending to create a new controller instance.
|
37
|
+
#
|
38
|
+
# If args are empty we can be sure that we're creating a route.
|
39
|
+
#
|
40
|
+
if args.any?
|
41
|
+
super(*args)
|
42
|
+
else
|
43
|
+
build_route(method, route.name, route.path || route.matcher, &block).tap do
|
44
|
+
# Make sure the route was inserted in the same order as found in the template.
|
45
|
+
#
|
46
|
+
index_of_last_insert = local_expander.routes[method].index { |expander_route|
|
47
|
+
expander_route.name == @routes[method].last.name
|
48
|
+
}
|
49
|
+
|
50
|
+
insert_before_this_index = @routes[method].select { |each_route|
|
51
|
+
local_expander.routes[method].any? { |expander_route|
|
52
|
+
each_route.name == expander_route.name
|
53
|
+
}
|
54
|
+
}.map { |each_route|
|
55
|
+
local_expander.routes[method].index { |expander_route|
|
56
|
+
expander_route.name == each_route.name
|
57
|
+
}
|
58
|
+
}.select { |index|
|
59
|
+
index > index_of_last_insert
|
60
|
+
}.first
|
61
|
+
|
62
|
+
if insert_before_this_index
|
63
|
+
@routes[method].insert(
|
64
|
+
@routes[method].index { |each_route|
|
65
|
+
each_route.name == local_expander.routes[method][insert_before_this_index].name
|
66
|
+
}, @routes[method].delete_at(index_of_last_insert)
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Define helper methods for groups and namespaces
|
77
|
+
#
|
78
|
+
@expander.children.each do |child|
|
79
|
+
unless @controller.singleton_class.instance_methods(false).include?(child.__object_name.name)
|
80
|
+
@controller.define_singleton_method child.__object_name.name do |&block|
|
81
|
+
if child.path.nil?
|
82
|
+
group(child.__object_name.name, &block)
|
83
|
+
else
|
84
|
+
namespace(child.__object_name.name, child.path || child.matcher, &block)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Set the expansion on the controller.
|
91
|
+
#
|
92
|
+
@controller.expansions << template_name
|
93
|
+
end
|
94
|
+
|
95
|
+
def group(*args, **kwargs, &block)
|
96
|
+
@expander.send(:group, *args, set_const: false, **kwargs, &block)
|
97
|
+
end
|
98
|
+
|
99
|
+
def namespace(*args, **kwargs, &block)
|
100
|
+
@expander.send(:namespace, *args, set_const: false, **kwargs, &block)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/support/extension"
|
4
|
+
|
5
|
+
module Pakyow
|
6
|
+
module Routing
|
7
|
+
module Extension
|
8
|
+
# An extension for defining RESTful Resources. For example:
|
9
|
+
#
|
10
|
+
# resource :posts, "/posts" do
|
11
|
+
# list do
|
12
|
+
# # list the posts
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# +Resource+ is available in all controllers by default.
|
17
|
+
#
|
18
|
+
# = Supported Actions
|
19
|
+
#
|
20
|
+
# These actions are supported:
|
21
|
+
#
|
22
|
+
# - +list+ -- +GET /+
|
23
|
+
# - +new+ -- +GET /new+
|
24
|
+
# - +create+ -- +POST /+
|
25
|
+
# - +edit+ -- +GET /:resource_id/edit+
|
26
|
+
# - +update+ -- +PATCH /:resource_id+
|
27
|
+
# - +replace+ -- +PUT /:resource_id+
|
28
|
+
# - +delete+ -- +DELETE /:resource_id+
|
29
|
+
# - +show+ -- +GET /:resource_id+
|
30
|
+
#
|
31
|
+
# = Nested Resources
|
32
|
+
#
|
33
|
+
# Resources can be nested. For example:
|
34
|
+
#
|
35
|
+
# resource :posts, "/posts" do
|
36
|
+
# resource :comments, "/comments" do
|
37
|
+
# list do
|
38
|
+
# # available at GET /posts/:post_id/comments
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# = Collection Routes
|
44
|
+
#
|
45
|
+
# Routes can be defined for the collection. For example:
|
46
|
+
#
|
47
|
+
# resource :posts, "/posts" do
|
48
|
+
# collection do
|
49
|
+
# get "/foo" do
|
50
|
+
# # available at GET /posts/foo
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# = Member Routes
|
56
|
+
#
|
57
|
+
# Routes can be defined as members. For example:
|
58
|
+
#
|
59
|
+
# resource :posts, "/posts" do
|
60
|
+
# member do
|
61
|
+
# get "/foo" do
|
62
|
+
# # available at GET /posts/:post_id/foo
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
module Resource
|
68
|
+
extend Support::Extension
|
69
|
+
restrict_extension Controller
|
70
|
+
|
71
|
+
DEFAULT_PARAM = :id
|
72
|
+
|
73
|
+
apply_extension do
|
74
|
+
template :resource do |param: DEFAULT_PARAM|
|
75
|
+
resource_id = ":#{param}"
|
76
|
+
nested_param = "#{Support.inflector.singularize(controller.__object_name.name)}_#{param}".to_sym
|
77
|
+
nested_resource_id = ":#{nested_param}"
|
78
|
+
|
79
|
+
action :update_request_path_for_show, only: [:show] do
|
80
|
+
connection.get(:__endpoint_path).gsub!(resource_id, "show")
|
81
|
+
end
|
82
|
+
|
83
|
+
controller.class_eval do
|
84
|
+
allow_params param
|
85
|
+
|
86
|
+
unless singleton_class.instance_methods(false).include?(:param)
|
87
|
+
define_singleton_method :param do
|
88
|
+
param
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
unless singleton_class.instance_methods(false).include?(:nested_param)
|
93
|
+
define_singleton_method :nested_param do
|
94
|
+
nested_param
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
unless instance_methods(false).include?(:update_request_path_for_show)
|
99
|
+
define_method :update_request_path_for_show do
|
100
|
+
connection.get(:__endpoint_path).gsub!(resource_id, "show")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
NestedResource.define(self, nested_resource_id, nested_param)
|
105
|
+
end
|
106
|
+
|
107
|
+
get :list, "/"
|
108
|
+
get :new, "/new"
|
109
|
+
post :create, "/"
|
110
|
+
get :edit, "/#{resource_id}/edit"
|
111
|
+
patch :update, "/#{resource_id}"
|
112
|
+
put :replace, "/#{resource_id}"
|
113
|
+
delete :delete, "/#{resource_id}"
|
114
|
+
get :show, "/#{resource_id}"
|
115
|
+
|
116
|
+
group :collection
|
117
|
+
namespace :member, nested_resource_id
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module NestedResource
|
122
|
+
# Nest resources as members of the current resource.
|
123
|
+
#
|
124
|
+
def self.define(controller, nested_resource_id, nested_param)
|
125
|
+
unless controller.singleton_class.instance_methods(false).include?(:namespace)
|
126
|
+
controller.define_singleton_method :namespace do |*args, &block|
|
127
|
+
super(*args, &block).tap do |namespace|
|
128
|
+
namespace.allow_params nested_param
|
129
|
+
namespace.action :update_request_path_for_parent do
|
130
|
+
connection.get(:__endpoint_path).gsub!("/#{nested_resource_id}", "")
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
unless controller.singleton_class.instance_methods(false).include?(:resource)
|
137
|
+
controller.define_singleton_method :resource do |name, matcher, param: DEFAULT_PARAM, &block|
|
138
|
+
if existing_resource = children.find { |child| child.expansions.include?(:resource) && child.__object_name.name == name }
|
139
|
+
existing_resource.instance_exec(&block); existing_resource
|
140
|
+
else
|
141
|
+
expand(:resource, name, File.join(nested_resource_id, matcher), param: param) do
|
142
|
+
allow_params nested_param
|
143
|
+
|
144
|
+
action :update_request_path_for_parent do
|
145
|
+
connection.get(:__endpoint_path).gsub!("/#{nested_resource_id}", "")
|
146
|
+
end
|
147
|
+
|
148
|
+
class_eval(&block)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/framework"
|
4
|
+
|
5
|
+
require "pakyow/routing/controller"
|
6
|
+
require "pakyow/routing/extensions"
|
7
|
+
require "pakyow/routing/helpers/exposures"
|
8
|
+
|
9
|
+
require "pakyow/behavior/definition"
|
10
|
+
|
11
|
+
require "pakyow/security/behavior/config"
|
12
|
+
require "pakyow/security/behavior/disabling"
|
13
|
+
require "pakyow/security/behavior/helpers"
|
14
|
+
require "pakyow/security/behavior/insecure"
|
15
|
+
require "pakyow/security/behavior/pipeline"
|
16
|
+
|
17
|
+
module Pakyow
|
18
|
+
module Routing
|
19
|
+
class Framework < Pakyow::Framework(:routing)
|
20
|
+
def boot
|
21
|
+
object.class_eval do
|
22
|
+
include Pakyow::Behavior::Definition
|
23
|
+
|
24
|
+
isolate Controller do
|
25
|
+
include Extension::Resource
|
26
|
+
end
|
27
|
+
|
28
|
+
# Make controllers definable on the app.
|
29
|
+
#
|
30
|
+
stateful :controller, isolated(:Controller) do |args, _opts|
|
31
|
+
if self.ancestors.include?(Plugin)
|
32
|
+
# When using plugins, prefix controller paths with the mount path.
|
33
|
+
#
|
34
|
+
name, matcher = Controller.send(:parse_name_and_matcher_from_args, *args)
|
35
|
+
path = File.join(@mount_path, Controller.send(:path_from_matcher, matcher).to_s)
|
36
|
+
args.replace([name, path])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Load controllers for the app.
|
41
|
+
#
|
42
|
+
aspect :controllers
|
43
|
+
|
44
|
+
# Load resources for the app.
|
45
|
+
#
|
46
|
+
aspect :resources
|
47
|
+
|
48
|
+
register_helper :active, Helpers::Exposures
|
49
|
+
|
50
|
+
# Include helpers into the controller class.
|
51
|
+
#
|
52
|
+
on "load" do
|
53
|
+
self.class.include_helpers :active, isolated(:Controller)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Create the global controller instance.
|
57
|
+
#
|
58
|
+
after "initialize" do
|
59
|
+
@global_controller = isolated(:Controller).new(self)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Expose the global controller for handling errors from other frameworks.
|
63
|
+
#
|
64
|
+
def controller_for_connection(connection)
|
65
|
+
@global_controller.dup.tap do |controller|
|
66
|
+
controller.instance_variable_set(:@connection, connection)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
require "pakyow/support/message_verifier"
|
71
|
+
handle Support::MessageVerifier::TamperedMessage, as: :forbidden
|
72
|
+
|
73
|
+
include Security::Behavior::Config
|
74
|
+
include Security::Behavior::Disabling
|
75
|
+
include Security::Behavior::Helpers
|
76
|
+
include Security::Behavior::Insecure
|
77
|
+
include Security::Behavior::Pipeline
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pakyow
|
4
|
+
module Routing
|
5
|
+
module Helpers
|
6
|
+
module Exposures
|
7
|
+
# Expose a value by name.
|
8
|
+
#
|
9
|
+
def expose(name, default_value = default_omitted = true, &block)
|
10
|
+
value = if block_given?
|
11
|
+
yield
|
12
|
+
elsif default_omitted
|
13
|
+
__send__(name)
|
14
|
+
end
|
15
|
+
|
16
|
+
unless default_omitted
|
17
|
+
value ||= default_value
|
18
|
+
end
|
19
|
+
|
20
|
+
@connection.set(name, value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/support/aargv"
|
4
|
+
require "pakyow/support/core_refinements/string/normalization"
|
5
|
+
|
6
|
+
module Pakyow
|
7
|
+
module Routing
|
8
|
+
# A {Controller} endpoint.
|
9
|
+
#
|
10
|
+
class Route
|
11
|
+
using Support::Refinements::String::Normalization
|
12
|
+
|
13
|
+
attr_reader :path, :method, :name, :block
|
14
|
+
|
15
|
+
# @api private
|
16
|
+
attr_accessor :pipeline
|
17
|
+
|
18
|
+
def initialize(path_or_matcher, name:, method:, &block)
|
19
|
+
@name, @method, @block = name, method, block
|
20
|
+
|
21
|
+
if path_or_matcher.is_a?(String)
|
22
|
+
@path = path_or_matcher.to_s
|
23
|
+
@matcher = create_matcher_from_path(@path)
|
24
|
+
else
|
25
|
+
@path = ""
|
26
|
+
@matcher = path_or_matcher
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def match(path_to_match)
|
31
|
+
@matcher.match(path_to_match)
|
32
|
+
end
|
33
|
+
|
34
|
+
def call(context)
|
35
|
+
context.instance_exec(&@block) if @block
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_path(path_to_self, **params)
|
39
|
+
working_path = String.normalize_path(File.join(path_to_self.to_s, @path))
|
40
|
+
|
41
|
+
params.each do |key, value|
|
42
|
+
working_path.sub!(":#{key}", value.to_s)
|
43
|
+
end
|
44
|
+
|
45
|
+
working_path.sub!("/#", "#")
|
46
|
+
working_path
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def create_matcher_from_path(path)
|
52
|
+
converted_path = String.normalize_path(path.split("/").map { |segment|
|
53
|
+
if segment.include?(":")
|
54
|
+
"(?<#{segment[(segment.index(":") + 1)..-1]}>(\\w|[-~:@!$\\'\\(\\)\\*\\+,;])+)"
|
55
|
+
else
|
56
|
+
segment
|
57
|
+
end
|
58
|
+
}.join("/"))
|
59
|
+
|
60
|
+
Regexp.new("^#{converted_path}$")
|
61
|
+
end
|
62
|
+
|
63
|
+
class EndpointBuilder
|
64
|
+
attr_reader :params
|
65
|
+
|
66
|
+
def initialize(route:, path:)
|
67
|
+
@route, @path = route, path
|
68
|
+
@params = String.normalize_path(File.join(@path.to_s, @route.path)).split("/").select { |segment|
|
69
|
+
segment.start_with?(":")
|
70
|
+
}.map { |segment|
|
71
|
+
segment[1..-1].to_sym
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def call(**params)
|
76
|
+
@route.build_path(@path, params)
|
77
|
+
end
|
78
|
+
|
79
|
+
def source_location
|
80
|
+
@route.block&.source_location || []
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/support/hookable"
|
4
|
+
|
5
|
+
require "pakyow/security/errors"
|
6
|
+
|
7
|
+
module Pakyow
|
8
|
+
module Security
|
9
|
+
class Base
|
10
|
+
include Support::Hookable
|
11
|
+
events :reject
|
12
|
+
|
13
|
+
SAFE_HTTP_METHODS = %i(get head options trace).freeze
|
14
|
+
|
15
|
+
def initialize(config)
|
16
|
+
@config = config
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(connection)
|
20
|
+
unless safe?(connection) || allowed?(connection)
|
21
|
+
reject(connection)
|
22
|
+
end
|
23
|
+
|
24
|
+
connection
|
25
|
+
end
|
26
|
+
|
27
|
+
def reject(connection)
|
28
|
+
performing :reject do
|
29
|
+
connection.logger.warn "Request rejected by #{self.class}; connection: #{connection.inspect}"
|
30
|
+
|
31
|
+
connection.status = 403
|
32
|
+
connection.body = StringIO.new("Forbidden")
|
33
|
+
|
34
|
+
raise InsecureRequest
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def safe?(connection)
|
39
|
+
SAFE_HTTP_METHODS.include? connection.method
|
40
|
+
end
|
41
|
+
|
42
|
+
def allowed?(_)
|
43
|
+
false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/support/extension"
|
4
|
+
|
5
|
+
module Pakyow
|
6
|
+
module Security
|
7
|
+
module Behavior
|
8
|
+
module Config
|
9
|
+
extend Support::Extension
|
10
|
+
|
11
|
+
apply_extension do
|
12
|
+
configurable :security do
|
13
|
+
configurable :csrf do
|
14
|
+
setting :protection, {}
|
15
|
+
setting :origin_whitelist, []
|
16
|
+
setting :param, :authenticity_token
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require "pakyow/security/csrf/verify_same_origin"
|
21
|
+
require "pakyow/security/csrf/verify_authenticity_token"
|
22
|
+
|
23
|
+
config.security.csrf.protection = {
|
24
|
+
origin: CSRF::VerifySameOrigin.new(
|
25
|
+
origin_whitelist: config.security.csrf.origin_whitelist
|
26
|
+
),
|
27
|
+
|
28
|
+
authenticity: CSRF::VerifyAuthenticityToken.new({}),
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/support/extension"
|
4
|
+
|
5
|
+
module Pakyow
|
6
|
+
module Security
|
7
|
+
module Behavior
|
8
|
+
module Disabling
|
9
|
+
extend Support::Extension
|
10
|
+
|
11
|
+
apply_extension do
|
12
|
+
isolated :Controller do
|
13
|
+
def self.disable_protection(type, only: [], except: [])
|
14
|
+
if type.to_sym == :csrf
|
15
|
+
if only.any? || except.any?
|
16
|
+
Pipelines::CSRF.__pipeline.actions.each do |action|
|
17
|
+
if only.any?
|
18
|
+
skip action.target, only: only
|
19
|
+
end
|
20
|
+
|
21
|
+
if except.any?
|
22
|
+
action action.target, only: except
|
23
|
+
end
|
24
|
+
end
|
25
|
+
else
|
26
|
+
exclude_pipeline Pipelines::CSRF
|
27
|
+
end
|
28
|
+
else
|
29
|
+
raise ArgumentError, "Unknown protection type `#{type}'"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/support/extension"
|
4
|
+
|
5
|
+
require "pakyow/security/helpers/csrf"
|
6
|
+
|
7
|
+
module Pakyow
|
8
|
+
module Security
|
9
|
+
module Behavior
|
10
|
+
module Helpers
|
11
|
+
extend Support::Extension
|
12
|
+
|
13
|
+
apply_extension do
|
14
|
+
register_helper :passive, Security::Helpers::CSRF
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/support/extension"
|
4
|
+
|
5
|
+
require "pakyow/security/errors"
|
6
|
+
|
7
|
+
module Pakyow
|
8
|
+
module Security
|
9
|
+
module Behavior
|
10
|
+
module Insecure
|
11
|
+
extend Support::Extension
|
12
|
+
|
13
|
+
apply_extension do
|
14
|
+
handle InsecureRequest, as: 403 do
|
15
|
+
trigger(403)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/support/extension"
|
4
|
+
|
5
|
+
module Pakyow
|
6
|
+
module Security
|
7
|
+
module Behavior
|
8
|
+
module Pipeline
|
9
|
+
extend Support::Extension
|
10
|
+
|
11
|
+
apply_extension do
|
12
|
+
require "pakyow/security/pipelines/csrf"
|
13
|
+
|
14
|
+
isolated :Controller do
|
15
|
+
include_pipeline Pipelines::CSRF
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pakyow/support/message_verifier"
|
4
|
+
|
5
|
+
require "pakyow/security/base"
|
6
|
+
|
7
|
+
module Pakyow
|
8
|
+
module Security
|
9
|
+
module CSRF
|
10
|
+
# Protects against Cross-Site Forgery Requests (CSRF).
|
11
|
+
# https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet
|
12
|
+
#
|
13
|
+
# Requires a valid token be passed as a request parameter. The token consists
|
14
|
+
# of a client id (unique to the request) and a digest generated from the
|
15
|
+
# client id and the server id stored in the session.
|
16
|
+
#
|
17
|
+
# @see Pakyow::Support::MessageVerifier
|
18
|
+
#
|
19
|
+
class VerifyAuthenticityToken < Base
|
20
|
+
def allowed?(connection)
|
21
|
+
connection.verifier.verify(connection.params[connection.app.config.security.csrf.param])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|