pakyow-routing 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|