rad_ext 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.
- data/Rakefile +12 -0
- data/lib/rad/configurators/abstract.rb +22 -0
- data/lib/rad/configurators/runtime.rb +2 -0
- data/lib/rad/configurators/web.rb +32 -0
- data/lib/rad/controller/forgery_protector.rb +31 -0
- data/lib/rad/processors/ajax_helper.rb +16 -0
- data/lib/rad/processors/ensure_no_www.rb +30 -0
- data/lib/rad_ext/configurators.rb +13 -0
- data/lib/rad_ext/extensions/i18n/locales/ru/pluralization.rb +62 -0
- data/lib/rad_ext/extensions/i18n.rb +16 -0
- data/lib/rad_ext/extensions/prepare_model.rb +16 -0
- data/lib/rad_ext/extensions/user_error.rb +12 -0
- data/lib/rad_ext/extensions.rb +7 -0
- data/lib/rad_ext/gems.rb +7 -0
- data/lib/rad_ext/profiles/web_ext.rb +70 -0
- data/lib/rad_ext/protect_from_forgery.rb +56 -0
- data/lib/rad_ext/require.rb +5 -0
- data/lib/rad_ext/spec.rb +4 -0
- data/lib/rad_ext/tasks.rb +18 -0
- data/lib/rad_ext/utils/cli_helper.rb +34 -0
- data/lib/rad_ext.rb +18 -0
- data/readme.md +12 -0
- data/spec/extensions/i18n_spec/locales/en/general.yml +5 -0
- data/spec/extensions/i18n_spec/locales/ru/general.yml +7 -0
- data/spec/extensions/i18n_spec.rb +32 -0
- data/spec/extensions/prepare_model_spec.rb +36 -0
- data/spec/extensions/user_error_spec.rb +36 -0
- data/spec/processors/protect_from_forgery_spec.rb +188 -0
- data/spec/spec_helper/web_profile.rb +3 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/spec_helper_with_rad.rb +4 -0
- metadata +110 -0
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
class Abstract
|
2
|
+
def initialize dir
|
3
|
+
@dir = File.expand_path(dir)
|
4
|
+
end
|
5
|
+
|
6
|
+
# def config options = {}
|
7
|
+
# rad.config.merge_config! "#{dir}/config/config.yml", options
|
8
|
+
# end
|
9
|
+
|
10
|
+
def routes
|
11
|
+
routes_file = "#{dir}/config/routes.rb"
|
12
|
+
load routes_file if File.exist? routes_file
|
13
|
+
end
|
14
|
+
|
15
|
+
def locales
|
16
|
+
I18n.load_path += Dir["#{dir}/config/locales/**/*.{rb,yml}"]
|
17
|
+
I18n.load_path += Dir["#{dir}/config/locales/*.{rb,yml}"]
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
attr_reader :dir #, :after_config, :after_environment
|
22
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Web < Abstract
|
2
|
+
def asset_paths *relative_paths
|
3
|
+
relative_paths = relative_paths.first if relative_paths.first.is_a? Array
|
4
|
+
relative_paths.each do |relative_path|
|
5
|
+
path = "#{dir}/#{relative_path}"
|
6
|
+
rad.assets.paths << path unless rad.assets.paths.include? path
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def load_paths *relative_paths
|
11
|
+
relative_paths = relative_paths.first if relative_paths.first.is_a? Array
|
12
|
+
relative_paths.each do |relative_path|
|
13
|
+
path = "#{dir}/#{relative_path}"
|
14
|
+
$LOAD_PATH << path unless $LOAD_PATH.include? path
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def template_paths *relative_paths
|
19
|
+
rad.template
|
20
|
+
|
21
|
+
relative_paths = relative_paths.first if relative_paths.first.is_a? Array
|
22
|
+
relative_paths.each do |relative_path|
|
23
|
+
path = "#{dir}/#{relative_path}"
|
24
|
+
rad.template.paths << path unless rad.template.paths.include? path
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def autoload_paths *relative_paths
|
29
|
+
relative_paths = relative_paths.first if relative_paths.first.is_a? Array
|
30
|
+
relative_paths.each{|d| autoload_dir "#{dir}/#{d}", true}
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module ForgeryProtector
|
2
|
+
BROWSER_GENERATED_TYPES = %w(
|
3
|
+
text/html
|
4
|
+
text/plain
|
5
|
+
application/x-www-form-urlencoded
|
6
|
+
multipart/form-data
|
7
|
+
).to_set
|
8
|
+
|
9
|
+
BROWSER_GENERATED_FORMATS = %w(html js)
|
10
|
+
|
11
|
+
protected
|
12
|
+
def protect_from_forgery
|
13
|
+
request = workspace.request
|
14
|
+
if request.session
|
15
|
+
sat = request.session['authenticity_token']
|
16
|
+
content_type = request.content_type
|
17
|
+
format = workspace.params.format
|
18
|
+
|
19
|
+
allow = (
|
20
|
+
request.get? or
|
21
|
+
(content_type.present? and !BROWSER_GENERATED_TYPES.include?(content_type.downcase)) or
|
22
|
+
(format.present? and !BROWSER_GENERATED_FORMATS.include?(format)) or
|
23
|
+
(sat.present? and sat == params.authenticity_token)
|
24
|
+
)
|
25
|
+
|
26
|
+
raise "invalid authenticity token!" unless allow
|
27
|
+
|
28
|
+
@authenticity_token = sat
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Sets format for :js as :html and wraps body into <textarea>
|
2
|
+
# Forms with files can be submitted by ajax only via iframe, and it requires
|
3
|
+
# the response have 'html' encoding and be wrapped into <textarea>
|
4
|
+
class AjaxHelper < Conveyors::Processor
|
5
|
+
def call
|
6
|
+
response = workspace.response.must_be.defined
|
7
|
+
request = workspace.request.must_be.defined
|
8
|
+
|
9
|
+
next_processor.call
|
10
|
+
|
11
|
+
if workspace.params? and workspace.params.format == 'js' and !request.xhr?
|
12
|
+
response.content_type = Mime['html']
|
13
|
+
workspace.content = "<textarea>#{workspace.content}</textarea>"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# enshure domain has no www. except if there's custom subdomain
|
2
|
+
class EnsureNoWww < Conveyors::Processor
|
3
|
+
def call
|
4
|
+
workspace.params.must_be.defined
|
5
|
+
if workspace.params.format == 'html' and url_with_www?
|
6
|
+
redirect_without_www
|
7
|
+
else
|
8
|
+
next_processor.call
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
def uri
|
14
|
+
@uri ||= Uri.parse workspace.request.url
|
15
|
+
end
|
16
|
+
|
17
|
+
def url_with_www?
|
18
|
+
uri.host =~ /^www\./
|
19
|
+
end
|
20
|
+
|
21
|
+
def redirect_without_www
|
22
|
+
uri.host = uri.host.sub(/^www\./, '')
|
23
|
+
url = uri.to_s
|
24
|
+
|
25
|
+
response = workspace.response
|
26
|
+
response.status = 301
|
27
|
+
response.headers['Location'] = url
|
28
|
+
response.body = %(<html><body>You are being <a href="#{url.html_escape}">redirected</a>.</body></html>)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# %w(
|
2
|
+
# abstract
|
3
|
+
# runtime
|
4
|
+
# web
|
5
|
+
# ).each{|f| require "rad_ext/configurators/#{f}"}
|
6
|
+
|
7
|
+
class Micon::Core
|
8
|
+
def configure configurator_name, dir, &block
|
9
|
+
configurator_class = "Rad::Configurators::#{configurator_name.to_s.classify}".constantize
|
10
|
+
configurator = configurator_class.new dir
|
11
|
+
block.call configurator if block
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# {
|
2
|
+
# :'ru' => {
|
3
|
+
# pluralize: lambda { |n|
|
4
|
+
# # Правило плюрализации для русского языка, взято из CLDR, http://unicode.org/cldr/
|
5
|
+
# #
|
6
|
+
# #
|
7
|
+
# # Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
|
8
|
+
# #
|
9
|
+
# # one -> n mod 10 is 1 and n mod 100 is not 11;
|
10
|
+
# # few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
|
11
|
+
# # many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
|
12
|
+
# # other -> everything else
|
13
|
+
# #
|
14
|
+
# # Пример
|
15
|
+
# #
|
16
|
+
# # :one = 1, 21, 31, 41, 51, 61...
|
17
|
+
# # :few = 2-4, 22-24, 32-34...
|
18
|
+
# # :many = 0, 5-20, 25-30, 35-40...
|
19
|
+
# # :other = 1.31, 2.31, 5.31...
|
20
|
+
# modulo10 = n.modulo(10)
|
21
|
+
# modulo100 = n.modulo(100)
|
22
|
+
#
|
23
|
+
# if modulo10 == 1 && modulo100 != 11
|
24
|
+
# :one
|
25
|
+
# elsif (modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)
|
26
|
+
# :few
|
27
|
+
# elsif modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)
|
28
|
+
# :many
|
29
|
+
# else
|
30
|
+
# :other
|
31
|
+
# end
|
32
|
+
# }
|
33
|
+
# }
|
34
|
+
# }
|
35
|
+
|
36
|
+
{
|
37
|
+
ru: {
|
38
|
+
i18n: {
|
39
|
+
plural: {
|
40
|
+
rule: lambda{|n|
|
41
|
+
# Правило плюрализации для русского языка, взято из CLDR, http://unicode.org/cldr/
|
42
|
+
#
|
43
|
+
#
|
44
|
+
# Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
|
45
|
+
#
|
46
|
+
# one -> n mod 10 is 1 and n mod 100 is not 11;
|
47
|
+
# few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
|
48
|
+
# many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
|
49
|
+
# other -> everything else
|
50
|
+
#
|
51
|
+
# Пример
|
52
|
+
#
|
53
|
+
# :one = 1, 21, 31, 41, 51, 61...
|
54
|
+
# :few = 2-4, 22-24, 32-34...
|
55
|
+
# :many = 0, 5-20, 25-30, 35-40...
|
56
|
+
# :other = 1.31, 2.31, 5.31...
|
57
|
+
n % 10 == 1 && n % 100 != 11 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : n % 10 == 0 || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100) ? :many : :other
|
58
|
+
}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Hack, ActiveSupport currently uses differrent version
|
2
|
+
# gem 'i18n', '>= 0.4.1'
|
3
|
+
# require 'i18n'
|
4
|
+
|
5
|
+
require "i18n/backend/pluralization"
|
6
|
+
I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)
|
7
|
+
|
8
|
+
I18n.load_path += Dir["#{__FILE__.dirname}/i18n/locales/*/*.{rb,yml}"]
|
9
|
+
|
10
|
+
|
11
|
+
#
|
12
|
+
# Helpers for Rad
|
13
|
+
#
|
14
|
+
[Rad::Controller::Abstract, Rad::Controller::Context].each do |klass|
|
15
|
+
klass.delegate :t, to: I18n
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Rad::Controller::Abstract::ClassMethods.class_eval do
|
2
|
+
def prepare_model aclass, opt = {}
|
3
|
+
opt = opt.symbolize_keys
|
4
|
+
id = opt.delete(:id) || :id
|
5
|
+
variable = opt.delete(:variable) || aclass.alias.underscore
|
6
|
+
|
7
|
+
finder = opt.delete(:finder) || :find!
|
8
|
+
|
9
|
+
method = "prepare_#{variable}"
|
10
|
+
define_method method do
|
11
|
+
model = aclass.send finder, params[id]
|
12
|
+
instance_variable_set "@#{variable}", model
|
13
|
+
end
|
14
|
+
before method, opt
|
15
|
+
end
|
16
|
+
end
|
data/lib/rad_ext/gems.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#
|
2
|
+
# Router
|
3
|
+
#
|
4
|
+
rad.router.routers = [
|
5
|
+
[:alias_router, Rad::Router::AliasRouter.new],
|
6
|
+
[:basic_router, Rad::Router::BasicRouter.new],
|
7
|
+
[:restful_router, Rad::Router::RestfulRouter.new],
|
8
|
+
[:object_router, Rad::Router::ObjectRouter.new]
|
9
|
+
]
|
10
|
+
|
11
|
+
|
12
|
+
#
|
13
|
+
# Conveyors
|
14
|
+
#
|
15
|
+
rad.conveyors.web do |web|
|
16
|
+
# conveyor
|
17
|
+
web.use Rad::Conveyors::Processors::ConveyorLogger
|
18
|
+
|
19
|
+
# http
|
20
|
+
web.use Rad::Http::Processors::HttpWriter
|
21
|
+
web.use Rad::Http::Processors::PrepareParams
|
22
|
+
web.use Rad::Http::Processors::EvaluateFormat
|
23
|
+
web.use Rad::Http::Processors::HttpLogger
|
24
|
+
|
25
|
+
# forgery protection
|
26
|
+
web.use Rad::Processors::PrepareAutenticityToken
|
27
|
+
|
28
|
+
# ensure no www
|
29
|
+
web.use Rad::Processors::EnsureNoWww
|
30
|
+
|
31
|
+
# ajax
|
32
|
+
web.use Rad::Processors::AjaxHelper
|
33
|
+
|
34
|
+
# html
|
35
|
+
# web.use ScopedParams
|
36
|
+
web.use Rad::Html::Processors::PrepareFlash
|
37
|
+
|
38
|
+
# controller
|
39
|
+
web.use Rad::Controller::Processors::ControllerErrorHandling
|
40
|
+
|
41
|
+
# router
|
42
|
+
web.use Rad::Router::Processors::Router, :class, :method_name
|
43
|
+
|
44
|
+
# controller
|
45
|
+
web.use Rad::Controller::Processors::ControllerLogger
|
46
|
+
web.use Rad::Controller::Processors::ControllerCaller
|
47
|
+
|
48
|
+
web.build!
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
#
|
53
|
+
# RackAdapter
|
54
|
+
#
|
55
|
+
rad.http.initialize_rack do |builder|
|
56
|
+
# CommonLogger, ShowExceptions, Lint
|
57
|
+
builder.use Rack::Lint if rad.development?
|
58
|
+
|
59
|
+
# Static Files
|
60
|
+
if rad.http.static? and rad.http.public_path and rad.development?
|
61
|
+
filters = /^\/.*?\/static\/|^\/static\/|\/favicon/
|
62
|
+
builder.use Rad::Assets::StaticFiles, filters
|
63
|
+
end
|
64
|
+
|
65
|
+
# use Rack::Session::Cookie, key: 'rack.session', domain: 'foo.com', path: '/', expire_after: 2592000, secret: 'change_me'
|
66
|
+
builder.use Rack::Session::Cookie, rad.http.session.stringify_keys if rad.http.session
|
67
|
+
|
68
|
+
# builder.use Rack::CommonLogger
|
69
|
+
builder.use Rack::MethodOverride
|
70
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#
|
2
|
+
# Processor
|
3
|
+
#
|
4
|
+
class Rad::Processors::PrepareAutenticityToken < Rad::Conveyors::Processor
|
5
|
+
def call
|
6
|
+
if rad.http.session
|
7
|
+
request = workspace.request.must_be.defined
|
8
|
+
params = workspace.params.must_be.defined
|
9
|
+
|
10
|
+
token = request.session['authenticity_token']
|
11
|
+
|
12
|
+
if token.blank? and request.get? and
|
13
|
+
token = generate_authenticity_token
|
14
|
+
request.session['authenticity_token'] = token
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
next_processor.call
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
protected
|
23
|
+
def generate_authenticity_token
|
24
|
+
ActiveSupport::SecureRandom.base64(32)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
#
|
30
|
+
# Controller
|
31
|
+
#
|
32
|
+
Rad::Controller::Http.include Rad::Controller::ForgeryProtector
|
33
|
+
|
34
|
+
Rad::Controller::Http::ClassMethods.class_eval do
|
35
|
+
def protect_from_forgery options = {}
|
36
|
+
before :protect_from_forgery, options
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
#
|
42
|
+
# View
|
43
|
+
#
|
44
|
+
Rad::Html::FormHelper.class_eval do
|
45
|
+
def authenticity_token
|
46
|
+
@authenticity_token
|
47
|
+
end
|
48
|
+
|
49
|
+
alias_method :form_tag_without_at, :form_tag
|
50
|
+
def form_tag *args, &b
|
51
|
+
form_tag_without_at *args do
|
52
|
+
concat(hidden_field_tag('authenticity_token', authenticity_token) + "\n") if authenticity_token
|
53
|
+
b.call if b
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/rad_ext/spec.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rake_ext'
|
2
|
+
require 'ruby_ext'
|
3
|
+
|
4
|
+
task :environment do
|
5
|
+
require 'rad'
|
6
|
+
rad.mode = ENV['m'] || ENV['mode'] || ENV['env'] || ENV['environment'] || :development
|
7
|
+
rad.runtime_path = File.expand_path '.'
|
8
|
+
|
9
|
+
require 'rad_ext'
|
10
|
+
rad.web
|
11
|
+
|
12
|
+
require 'rad_ext/utils/cli_helper'
|
13
|
+
|
14
|
+
Rad::CliHelper.use_runtime_path!
|
15
|
+
|
16
|
+
load "./init.rb"
|
17
|
+
rad.environment
|
18
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Rad::CliHelper
|
2
|
+
RUNTIME_DIR = 'runtime'
|
3
|
+
|
4
|
+
class << self
|
5
|
+
inject logger: :logger
|
6
|
+
|
7
|
+
def run_console
|
8
|
+
prepare_running_environment
|
9
|
+
|
10
|
+
require 'irb'
|
11
|
+
IRB.start
|
12
|
+
end
|
13
|
+
|
14
|
+
def run_server
|
15
|
+
app = prepare_running_environment
|
16
|
+
|
17
|
+
rad.http.run app, rad.http.host, rad.http.port
|
18
|
+
end
|
19
|
+
|
20
|
+
def use_runtime_path!
|
21
|
+
runtime_path = "./#{RUNTIME_DIR}"
|
22
|
+
Dir.chdir runtime_path if Dir.exist? runtime_path
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
def prepare_running_environment
|
27
|
+
use_runtime_path!
|
28
|
+
|
29
|
+
require 'rack'
|
30
|
+
app, options = Rack::Builder.parse_file 'config.ru'
|
31
|
+
app
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/rad_ext.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rad'
|
2
|
+
require 'rad_ext/gems'
|
3
|
+
|
4
|
+
lib_dir = __FILE__.dirname
|
5
|
+
autoload_dir lib_dir
|
6
|
+
|
7
|
+
rad.must_not.include :web
|
8
|
+
rad.register :web, depends_on: [:html, :router, :controller, :http] do
|
9
|
+
require 'rad/core_web/_require'
|
10
|
+
require 'rad_ext/require'
|
11
|
+
|
12
|
+
load 'rad_ext/profiles/web_ext.rb'
|
13
|
+
load 'rad/profiles/mailer.rb'
|
14
|
+
|
15
|
+
rad.assets
|
16
|
+
|
17
|
+
true
|
18
|
+
end
|
data/readme.md
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# Extensions for the Rad framework.
|
2
|
+
|
3
|
+
- face - tools for rapid interface creation.
|
4
|
+
- common_interface - common user interface for Rad.
|
5
|
+
- rad_ext - additions and extensions of the Rad.
|
6
|
+
- rad_js - JS plugin for the Rad.
|
7
|
+
- rad - Rapid Application Development tool built upon of the Rad.
|
8
|
+
|
9
|
+
Please go to specs for docs.
|
10
|
+
|
11
|
+
P.S.
|
12
|
+
All these components will be in standalone gems, but right now it's simpler for me to hold all of them in one place.
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "spec_helper_with_rad"
|
4
|
+
|
5
|
+
describe 'I18n' do
|
6
|
+
before :all do
|
7
|
+
rad.web
|
8
|
+
rad.reset :conveyors
|
9
|
+
|
10
|
+
I18n.load_path += Dir["#{spec_dir}/locales/*/*.{rb,yml}"]
|
11
|
+
end
|
12
|
+
|
13
|
+
def t *args
|
14
|
+
I18n.t *args
|
15
|
+
end
|
16
|
+
|
17
|
+
it "basic" do
|
18
|
+
I18n.locale = 'en'
|
19
|
+
t(:name).should == "Name"
|
20
|
+
t(:name).is_a?(String).should be_true
|
21
|
+
|
22
|
+
I18n.locale = 'ru'
|
23
|
+
t(:name).should == "Имя"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "pluggable pluralization" do
|
27
|
+
I18n.locale = 'ru'
|
28
|
+
t(:comments_count, count: 1).should == "1 комментарий"
|
29
|
+
t(:comments_count, count: 2).should == "2 комментария"
|
30
|
+
t(:comments_count, count: 5).should == "5 комментариев"
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "spec_helper_with_rad"
|
2
|
+
|
3
|
+
describe "User Error" do
|
4
|
+
rad.web
|
5
|
+
rad.reset :conveyors
|
6
|
+
isolate :conveyors, before: :all
|
7
|
+
|
8
|
+
before :all do
|
9
|
+
load 'spec_helper/web_profile.rb'
|
10
|
+
|
11
|
+
class ::SomeModel
|
12
|
+
def self.find! id
|
13
|
+
id.should == 'some id'
|
14
|
+
SomeModel.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
after :all do
|
19
|
+
remove_constants %w(SomeModel ControllerSpec)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "user error" do
|
23
|
+
class ::ControllerSpec
|
24
|
+
inherit Rad::Controller::Http
|
25
|
+
|
26
|
+
prepare_model SomeModel, id: :some_model, variable: 'some_model'
|
27
|
+
|
28
|
+
def action
|
29
|
+
@some_model.should_not == nil
|
30
|
+
render inline: 'ok'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
ccall(ControllerSpec, :action, some_model: 'some id').should == 'ok'
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "spec_helper_with_rad"
|
2
|
+
|
3
|
+
describe "User Error" do
|
4
|
+
rad.web
|
5
|
+
rad.reset :conveyors
|
6
|
+
|
7
|
+
isolate :conveyors, before: :all
|
8
|
+
|
9
|
+
before(:all){load 'spec_helper/web_profile.rb'}
|
10
|
+
|
11
|
+
after :all do
|
12
|
+
remove_constants %w(UserErrorSpec)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "user error" do
|
16
|
+
class ::UserErrorSpec
|
17
|
+
inherit Rad::Controller::Http
|
18
|
+
|
19
|
+
def call
|
20
|
+
raise_user_error "some error"
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
def catch_user_error
|
25
|
+
begin
|
26
|
+
yield
|
27
|
+
rescue UserError => ue
|
28
|
+
render inline: "Catched #{ue.message}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
around :catch_user_error
|
32
|
+
end
|
33
|
+
|
34
|
+
ccall(UserErrorSpec, :call).should == "Catched some error"
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require "spec_helper_with_rad"
|
2
|
+
|
3
|
+
describe "Forgery protection" do
|
4
|
+
rad.web
|
5
|
+
rad.reset :conveyors
|
6
|
+
|
7
|
+
with_prepare_params
|
8
|
+
|
9
|
+
isolate :conveyors
|
10
|
+
|
11
|
+
before :all do
|
12
|
+
class ForgerySpecHelper < Rad::Conveyors::Processor
|
13
|
+
def call
|
14
|
+
block = workspace.check_forgery.before_request
|
15
|
+
block.call workspace if block
|
16
|
+
workspace.before_request_done = true
|
17
|
+
|
18
|
+
next_processor.call
|
19
|
+
|
20
|
+
block = workspace.check_forgery.after_request
|
21
|
+
block.call workspace if block
|
22
|
+
workspace.after_request_done = true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
rad.conveyors.web do |web|
|
27
|
+
web.use Rad::Http::Processors::PrepareParams
|
28
|
+
web.use Rad::Http::Processors::EvaluateFormat
|
29
|
+
web.use ForgerySpecHelper
|
30
|
+
web.use Rad::Processors::PrepareAutenticityToken
|
31
|
+
web.use Rad::Controller::Processors::ControllerCaller
|
32
|
+
end
|
33
|
+
|
34
|
+
class ::AnRemote
|
35
|
+
inherit Rad::Controller::Http
|
36
|
+
|
37
|
+
protect_from_forgery only: :protected_method
|
38
|
+
|
39
|
+
def protected_method
|
40
|
+
render inline: 'protected result'
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_without_protection
|
44
|
+
render inline: 'result'
|
45
|
+
end
|
46
|
+
|
47
|
+
def dumb_method; end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
after :all do
|
52
|
+
rad.reset :conveyors
|
53
|
+
remove_constants %w(AnRemote ForgerySpecHelper)
|
54
|
+
end
|
55
|
+
|
56
|
+
before do
|
57
|
+
rad.http.stub(:session).and_return({'key' => 'session_id'})
|
58
|
+
end
|
59
|
+
|
60
|
+
def check_forgery opt
|
61
|
+
workspace = nil
|
62
|
+
|
63
|
+
result = rad.http.call(rad.http.mock_environment, check_forgery: opt.to_openobject) do |c|
|
64
|
+
c.call
|
65
|
+
workspace = rad[:workspace]
|
66
|
+
end
|
67
|
+
|
68
|
+
workspace.before_request_done.should be_true
|
69
|
+
workspace.after_request_done.should be_true
|
70
|
+
workspace
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should set :authenticity_token only for :get and 'html' request" do
|
74
|
+
check_forgery(
|
75
|
+
before_request: lambda{|workspace|
|
76
|
+
workspace.env['REQUEST_METHOD'] = 'GET'
|
77
|
+
workspace.env['CONTENT_TYPE'] = 'text/html'
|
78
|
+
workspace.class = AnRemote
|
79
|
+
workspace.method_name = :dumb_method
|
80
|
+
},
|
81
|
+
after_request: lambda{|workspace|
|
82
|
+
workspace.request.session['authenticity_token'].should_not be_blank
|
83
|
+
}
|
84
|
+
)
|
85
|
+
|
86
|
+
# post
|
87
|
+
check_forgery(
|
88
|
+
before_request: lambda{|workspace|
|
89
|
+
workspace.env['REQUEST_METHOD'] = 'POST'
|
90
|
+
workspace.env['CONTENT_TYPE'] = 'text/html'
|
91
|
+
workspace.class = AnRemote
|
92
|
+
workspace.method_name = :dumb_method
|
93
|
+
},
|
94
|
+
after_request: lambda{|workspace|
|
95
|
+
workspace.request.session['authenticity_token'].should be_blank
|
96
|
+
}
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should check any non :get request with browser's formats for :authenticity_token" do
|
101
|
+
lambda{
|
102
|
+
check_forgery(
|
103
|
+
before_request: lambda{|workspace|
|
104
|
+
workspace.env['REQUEST_METHOD'] = 'POST'
|
105
|
+
workspace.env['CONTENT_TYPE'] = 'text/html'
|
106
|
+
workspace.class = AnRemote
|
107
|
+
workspace.method_name = 'protected_method'
|
108
|
+
}
|
109
|
+
)
|
110
|
+
}.should raise_error(/invalid authenticity token/)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should pass request with correct authenticity_token" do
|
114
|
+
check_forgery(
|
115
|
+
before_request: lambda{|workspace|
|
116
|
+
workspace.env['REQUEST_METHOD'] = 'POST'
|
117
|
+
workspace.env['CONTENT_TYPE'] = 'text/html'
|
118
|
+
workspace.request.session['authenticity_token'] = 'secure token'
|
119
|
+
workspace.params['authenticity_token'] = 'secure token'
|
120
|
+
workspace.class = AnRemote
|
121
|
+
workspace.method_name = 'protected_method'
|
122
|
+
},
|
123
|
+
after_request: lambda{|workspace|
|
124
|
+
workspace.content.should == "protected result"
|
125
|
+
}
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should not check request with non-browser content type" do
|
130
|
+
check_forgery(
|
131
|
+
before_request: lambda{|workspace|
|
132
|
+
workspace.env['REQUEST_METHOD'] = 'POST'
|
133
|
+
workspace.env['CONTENT_TYPE'] = 'non-browser-format'
|
134
|
+
workspace.class = AnRemote
|
135
|
+
workspace.method_name = 'protected_method'
|
136
|
+
},
|
137
|
+
after_request: lambda{|workspace|
|
138
|
+
workspace.content.should == "protected result"
|
139
|
+
}
|
140
|
+
)
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should not check request with non-browser format" do
|
144
|
+
check_forgery(
|
145
|
+
before_request: lambda{|workspace|
|
146
|
+
workspace.env['REQUEST_METHOD'] = 'POST'
|
147
|
+
workspace.env['CONTENT_TYPE'] = 'text/html'
|
148
|
+
workspace.params['format'] = 'json'
|
149
|
+
workspace.class = AnRemote
|
150
|
+
workspace.method_name = 'protected_method'
|
151
|
+
},
|
152
|
+
after_request: lambda{|workspace|
|
153
|
+
workspace.content.should == "protected result"
|
154
|
+
}
|
155
|
+
)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should not protect non protected methods" do
|
159
|
+
check_forgery(
|
160
|
+
before_request: lambda{|workspace|
|
161
|
+
workspace.env['REQUEST_METHOD'] = 'POST'
|
162
|
+
workspace.env['CONTENT_TYPE'] = 'text/html'
|
163
|
+
workspace.class = AnRemote
|
164
|
+
workspace.method_name = 'method_without_protection'
|
165
|
+
},
|
166
|
+
after_request: lambda{|workspace|
|
167
|
+
workspace.content.should == "result"
|
168
|
+
}
|
169
|
+
)
|
170
|
+
end
|
171
|
+
|
172
|
+
# it "OUTDATED should use :session_authenticity_token from params (for flash support)" do
|
173
|
+
# check_forgery(
|
174
|
+
# before_request: lambda{|workspace|
|
175
|
+
# workspace.env['REQUEST_METHOD'] = 'POST'
|
176
|
+
# workspace.params.format = 'text/html'
|
177
|
+
# # workspace.params['session_authenticity_token'] = 'secure token'
|
178
|
+
# workspace.params['authenticity_token'] = 'secure token'
|
179
|
+
# workspace.class = AnRemote
|
180
|
+
# workspace.method_name = 'protected_method'
|
181
|
+
# },
|
182
|
+
# after_request: lambda{|workspace|
|
183
|
+
# workspace.content.should == "protected result"
|
184
|
+
# }
|
185
|
+
# )
|
186
|
+
# end
|
187
|
+
|
188
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rad_ext
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Alexey Petrushin
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-07-04 00:00:00.000000000 +04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: i18n
|
17
|
+
requirement: &2956510 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - =
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.5.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *2956510
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rad_core
|
28
|
+
requirement: &2956210 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *2956210
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rad_assets
|
39
|
+
requirement: &2955960 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
type: :runtime
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *2955960
|
48
|
+
description:
|
49
|
+
email:
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- Rakefile
|
55
|
+
- readme.md
|
56
|
+
- lib/rad/configurators/abstract.rb
|
57
|
+
- lib/rad/configurators/runtime.rb
|
58
|
+
- lib/rad/configurators/web.rb
|
59
|
+
- lib/rad/controller/forgery_protector.rb
|
60
|
+
- lib/rad/processors/ajax_helper.rb
|
61
|
+
- lib/rad/processors/ensure_no_www.rb
|
62
|
+
- lib/rad_ext/configurators.rb
|
63
|
+
- lib/rad_ext/extensions/i18n/locales/ru/pluralization.rb
|
64
|
+
- lib/rad_ext/extensions/i18n.rb
|
65
|
+
- lib/rad_ext/extensions/prepare_model.rb
|
66
|
+
- lib/rad_ext/extensions/user_error.rb
|
67
|
+
- lib/rad_ext/extensions.rb
|
68
|
+
- lib/rad_ext/gems.rb
|
69
|
+
- lib/rad_ext/profiles/web_ext.rb
|
70
|
+
- lib/rad_ext/protect_from_forgery.rb
|
71
|
+
- lib/rad_ext/require.rb
|
72
|
+
- lib/rad_ext/spec.rb
|
73
|
+
- lib/rad_ext/tasks.rb
|
74
|
+
- lib/rad_ext/utils/cli_helper.rb
|
75
|
+
- lib/rad_ext.rb
|
76
|
+
- spec/extensions/i18n_spec/locales/en/general.yml
|
77
|
+
- spec/extensions/i18n_spec/locales/ru/general.yml
|
78
|
+
- spec/extensions/i18n_spec.rb
|
79
|
+
- spec/extensions/prepare_model_spec.rb
|
80
|
+
- spec/extensions/user_error_spec.rb
|
81
|
+
- spec/processors/protect_from_forgery_spec.rb
|
82
|
+
- spec/spec_helper/web_profile.rb
|
83
|
+
- spec/spec_helper.rb
|
84
|
+
- spec/spec_helper_with_rad.rb
|
85
|
+
has_rdoc: true
|
86
|
+
homepage: http://github.com/alexeypetrushin/rad_ext
|
87
|
+
licenses: []
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 1.5.1
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: Extensions for Rad Framework
|
110
|
+
test_files: []
|