tynn 1.0.0.rc2 → 1.0.0.rc3
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/README.md +1 -3
- data/lib/tynn.rb +61 -102
- data/lib/tynn/all_methods.rb +11 -9
- data/lib/tynn/environment.rb +13 -13
- data/lib/tynn/force_ssl.rb +55 -0
- data/lib/tynn/hmote.rb +34 -0
- data/lib/tynn/hsts.rb +64 -0
- data/lib/tynn/json.rb +34 -30
- data/lib/tynn/matchers.rb +50 -48
- data/lib/tynn/not_found.rb +11 -9
- data/lib/tynn/protection.rb +29 -18
- data/lib/tynn/render.rb +24 -23
- data/lib/tynn/response.rb +138 -126
- data/lib/tynn/secure_headers.rb +29 -30
- data/lib/tynn/session.rb +62 -64
- data/lib/tynn/settings.rb +28 -0
- data/lib/tynn/static.rb +23 -20
- data/lib/tynn/test.rb +30 -31
- data/lib/tynn/version.rb +1 -1
- data/test/default_headers_test.rb +14 -0
- data/test/environment_test.rb +14 -18
- data/test/force_ssl_test.rb +32 -0
- data/test/hmote_test.rb +78 -0
- data/test/hsts_test.rb +29 -0
- data/test/protection_test.rb +0 -30
- data/test/render_test.rb +14 -1
- data/test/secure_headers_test.rb +8 -1
- metadata +15 -22
- data/lib/tynn/erubis.rb +0 -17
- data/lib/tynn/ssl.rb +0 -73
- data/test/erubis_test.rb +0 -20
- data/test/ssl_test.rb +0 -82
data/lib/tynn/secure_headers.rb
CHANGED
@@ -1,45 +1,44 @@
|
|
1
1
|
class Tynn
|
2
|
-
# Adds security related HTTP headers.
|
2
|
+
# Public: Adds security related HTTP headers.
|
3
3
|
#
|
4
|
-
#
|
5
|
-
# require "tynn"
|
6
|
-
# require "tynn/secure_headers"
|
4
|
+
# Examples
|
7
5
|
#
|
8
|
-
#
|
9
|
-
#
|
6
|
+
# require "tynn"
|
7
|
+
# require "tynn/secure_headers"
|
8
|
+
#
|
9
|
+
# Tynn.helpers(Tynn::SecureHeaders)
|
10
10
|
#
|
11
11
|
# This helper applies the following headers:
|
12
12
|
#
|
13
|
-
# *
|
14
|
-
#
|
13
|
+
# *X-Content-Type-Options:* <tt>"nosniff"</tt>
|
14
|
+
#
|
15
|
+
# Prevents IE and Chrome from
|
16
|
+
# {content type sniffing}[https://msdn.microsoft.com/library/gg622941(v=vs.85).aspx]
|
17
|
+
#
|
18
|
+
# *X-Frame-Options:* <tt>"SAMEORIGIN"</tt>
|
19
|
+
#
|
20
|
+
# Provides {Clickjacking}[https://www.owasp.org/index.php/Clickjacking]
|
21
|
+
# protection.
|
15
22
|
#
|
16
|
-
# *
|
17
|
-
# protection. Check the [X-Frame-Options draft][x-frame-options] for
|
18
|
-
# more information.
|
23
|
+
# *X-Permitted-Cross-Domain-Policies:* <tt>"none"</tt>
|
19
24
|
#
|
20
|
-
#
|
21
|
-
# access to data. Check this [article][pcdp] for more information.
|
25
|
+
# Restricts Adobe Flash Player's access to data.
|
22
26
|
#
|
23
|
-
# *
|
24
|
-
# into IE, Chrome and Safari. This filter is usually enabled by default,
|
25
|
-
# the use of this header is to re-enable it if it was disabled by the user.
|
27
|
+
# *X-XSS-Protection:* <tt>"1; mode=block"</tt>
|
26
28
|
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
# [x-frame-options]: https://tools.ietf.org/html/draft-ietf-websec-x-frame-options-02
|
31
|
-
# [xss]: https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
|
29
|
+
# Enables the XSS protection filter built into IE, Chrome and Safari.
|
30
|
+
# This filter is usually enabled by default, the use of this header
|
31
|
+
# is to re-enable it if it was disabled by the user.
|
32
32
|
#
|
33
33
|
module SecureHeaders
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
return super.merge(HEADERS)
|
34
|
+
# Internal: Sets the default HTTP secure headers.
|
35
|
+
def self.setup(app)
|
36
|
+
app.settings[:default_headers].update(
|
37
|
+
"X-Content-Type-Options" => "nosniff",
|
38
|
+
"X-Frame-Options" => "SAMEORIGIN",
|
39
|
+
"X-Permitted-Cross-Domain-Policies" => "none",
|
40
|
+
"X-XSS-Protection" => "1; mode=block"
|
41
|
+
)
|
43
42
|
end
|
44
43
|
end
|
45
44
|
end
|
data/lib/tynn/session.rb
CHANGED
@@ -1,87 +1,85 @@
|
|
1
1
|
class Tynn
|
2
|
-
# Adds simple cookie based session management.
|
3
|
-
#
|
4
|
-
# by unauthorized means.
|
2
|
+
# Public: Adds simple cookie based session management. You can pass a secret
|
3
|
+
# token to sign the cookie data, thus unauthorized means can't alter it.
|
5
4
|
#
|
6
|
-
#
|
7
|
-
# require "tynn"
|
8
|
-
# require "tynn/session"
|
5
|
+
# Examples
|
9
6
|
#
|
10
|
-
#
|
7
|
+
# require "tynn"
|
8
|
+
# require "tynn/session"
|
11
9
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
10
|
+
# Tynn.helpers(Tynn::Session, secret: "__change_me__")
|
11
|
+
#
|
12
|
+
# Tynn.define do
|
13
|
+
# root do
|
14
|
+
# res.write(sprintf("hei %s", session[:username]))
|
15
|
+
# end
|
16
16
|
#
|
17
|
-
#
|
18
|
-
#
|
17
|
+
# on(:username) do |username|
|
18
|
+
# session[:username] = username
|
19
|
+
# end
|
19
20
|
# end
|
20
|
-
# end
|
21
|
-
# ```
|
22
21
|
#
|
23
22
|
# The following command generates a cryptographically secure secret ready
|
24
23
|
# to use:
|
25
24
|
#
|
26
|
-
#
|
27
|
-
# $ ruby -r securerandom -e "puts SecureRandom.hex(64)"
|
28
|
-
# ```
|
25
|
+
# $ ruby -r securerandom -e "puts SecureRandom.hex(64)"
|
29
26
|
#
|
30
27
|
# It's important to keep the token secret. Knowing the token allows an
|
31
28
|
# attacker to tamper the data. So, it's recommended to load the token
|
32
29
|
# from the environment.
|
33
30
|
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
# Under the hood, Tynn::Session uses the
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
# [cookie-secure]: https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#Secure_Attribute
|
65
|
-
# [rack-session]: http://www.rubydoc.info/gems/rack/Rack/Session/Cookie
|
31
|
+
# Examples
|
32
|
+
#
|
33
|
+
# Tynn.helpers(Tynn::Session, secret: ENV["SESSION_SECRET"])
|
34
|
+
#
|
35
|
+
# Under the hood, Tynn::Session uses the +Rack::Session::Cookie+ middleware.
|
36
|
+
# Thus, supports all the options available for this middleware:
|
37
|
+
#
|
38
|
+
# key - The name of the cookie. Defaults to <tt>"rack.session"</tt>.
|
39
|
+
#
|
40
|
+
# httponly - If +true+, sets the +HttpOnly+ flag. This mitigates the
|
41
|
+
# risk of client side scripting accessing the cookie. Defaults
|
42
|
+
# to +true+.
|
43
|
+
#
|
44
|
+
# secure - If +true+, sets the +Secure+ flag. This tells the browser
|
45
|
+
# to only transmit the cookie over HTTPS. Defaults to `false`.
|
46
|
+
#
|
47
|
+
# expire_after - The lifespan of the cookie. If +nil+, the session cookie
|
48
|
+
# is temporary and is no retained after the browser is
|
49
|
+
# closed. Defaults to +nil+.
|
50
|
+
#
|
51
|
+
# Examples
|
52
|
+
#
|
53
|
+
# Tynn.helpers(
|
54
|
+
# Tynn::Session,
|
55
|
+
# key: "app",
|
56
|
+
# secret: ENV["SESSION_SECRET"],
|
57
|
+
# expire_after: 36_000, # seconds
|
58
|
+
# httponly: true,
|
59
|
+
# secure: true
|
60
|
+
# )
|
66
61
|
#
|
67
62
|
module Session
|
68
|
-
|
63
|
+
# Internal: Configures Rack::Session::Cookie middleware.
|
64
|
+
def self.setup(app, options = {})
|
65
|
+
defaults = { secure: app.settings[:ssl] }
|
69
66
|
|
70
|
-
|
71
|
-
app.use(Rack::Session::Cookie, options)
|
67
|
+
app.use(Rack::Session::Cookie, defaults.merge(options))
|
72
68
|
end
|
73
69
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
70
|
+
module InstanceMethods
|
71
|
+
# Public: Returns the session hash.
|
72
|
+
#
|
73
|
+
# Examples
|
74
|
+
#
|
75
|
+
# session # => {}
|
76
|
+
#
|
77
|
+
# session[:foo] = "foo"
|
78
|
+
# session[:foo] # => "foo"
|
79
|
+
#
|
80
|
+
def session
|
81
|
+
return env["rack.session".freeze]
|
82
|
+
end
|
85
83
|
end
|
86
84
|
end
|
87
85
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Tynn
|
2
|
+
module Settings # :nodoc: all
|
3
|
+
def self.deepclone(hash)
|
4
|
+
default_proc, hash.default_proc = hash.default_proc, nil
|
5
|
+
|
6
|
+
return Marshal.load(Marshal.dump(hash))
|
7
|
+
ensure
|
8
|
+
hash.default_proc = default_proc
|
9
|
+
end
|
10
|
+
|
11
|
+
module InstanceMethods # :nodoc: all
|
12
|
+
def settings
|
13
|
+
return self.class.settings
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods # :nodoc: all
|
18
|
+
def inherited(subclass)
|
19
|
+
subclass.settings.replace(Tynn::Settings.deepclone(settings))
|
20
|
+
subclass.settings.default_proc = proc { |h, k| h[k] = settings[k] }
|
21
|
+
end
|
22
|
+
|
23
|
+
def settings
|
24
|
+
return @settings ||= {}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/tynn/static.rb
CHANGED
@@ -1,33 +1,36 @@
|
|
1
1
|
class Tynn
|
2
|
-
# Adds support for static files (javascript files, images,
|
2
|
+
# Public: Adds support for static files (javascript files, images,
|
3
|
+
# stylesheets, etc).
|
3
4
|
#
|
4
|
-
#
|
5
|
-
# require "tynn"
|
6
|
-
# require "tynn/static"
|
5
|
+
# Examples
|
7
6
|
#
|
8
|
-
#
|
9
|
-
#
|
7
|
+
# require "tynn"
|
8
|
+
# require "tynn/static"
|
10
9
|
#
|
11
|
-
#
|
12
|
-
# `public` in the current directory (e.g. `public/js/*`, `public/css/*`). You
|
13
|
-
# can change the default by passing the `:root` option.
|
10
|
+
# Tynn.helpers(Tynn::Static, ["/js", "/css"])
|
14
11
|
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
12
|
+
# By default, serves all requests beginning with the given paths from
|
13
|
+
# the folder +public+ in the current directory (e.g. +public/js/*+,
|
14
|
+
# +public/css/*+). You can change the default by passing the +:root+
|
15
|
+
# option.
|
18
16
|
#
|
19
|
-
#
|
20
|
-
# Thus, supports all the options available for this middleware.
|
17
|
+
# Examples
|
21
18
|
#
|
22
|
-
#
|
23
|
-
# Tynn.helpers(Tynn::Static, ["/js", "/css"], index: "index.html")
|
24
|
-
# ```
|
19
|
+
# Tynn.helpers(Tynn::Static, ["/js", "/css"], root: "assets")
|
25
20
|
#
|
26
|
-
#
|
21
|
+
# Under the hood, it uses the +Rack::Static+ middleware. Thus,
|
22
|
+
# supports all the options available by the middleware. Check
|
23
|
+
# {Rack::Static}[http://www.rubydoc.info/gems/rack/Rack/Static]
|
24
|
+
# for more information.
|
25
|
+
#
|
26
|
+
# Examples
|
27
|
+
#
|
28
|
+
# Tynn.helpers(Tynn::Static, ["/js", "/css"], index: "index.html")
|
27
29
|
#
|
28
30
|
module Static
|
29
|
-
|
30
|
-
|
31
|
+
# Internal: Configures Rack::Static middleware.
|
32
|
+
def self.setup(app, urls, opts = {})
|
33
|
+
options = opts.dup
|
31
34
|
|
32
35
|
options[:urls] ||= urls
|
33
36
|
options[:root] ||= File.expand_path("public", Dir.pwd)
|
data/lib/tynn/test.rb
CHANGED
@@ -1,50 +1,49 @@
|
|
1
1
|
require "rack/test"
|
2
2
|
|
3
3
|
class Tynn
|
4
|
-
# A simple helper class
|
5
|
-
# to your application.
|
4
|
+
# Public: A simple helper class to simulate requests to your application.
|
6
5
|
#
|
7
|
-
#
|
8
|
-
# require "tynn"
|
9
|
-
# require "tynn/test"
|
6
|
+
# Examples
|
10
7
|
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# res.write("hei")
|
14
|
-
# end
|
15
|
-
# end
|
8
|
+
# require "tynn"
|
9
|
+
# require "tynn/test"
|
16
10
|
#
|
17
|
-
#
|
18
|
-
#
|
11
|
+
# Tynn.define do
|
12
|
+
# root do
|
13
|
+
# res.write("hei")
|
14
|
+
# end
|
15
|
+
# end
|
19
16
|
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# ```
|
17
|
+
# app = Tynn::Test.new
|
18
|
+
# app.get("/")
|
23
19
|
#
|
24
|
-
#
|
25
|
-
#
|
20
|
+
# 200 == app.res.status # => true
|
21
|
+
# "hei" == app.res.body # => true
|
26
22
|
#
|
27
|
-
#
|
23
|
+
# In order to use this plugin, you need to install
|
24
|
+
# {rack-test}[https://rubygems.org/gems/rack-test].
|
28
25
|
#
|
29
26
|
class Test
|
30
27
|
include Rack::Test::Methods
|
31
28
|
|
32
|
-
#
|
29
|
+
# Internal: Returns the application class that handles the
|
30
|
+
# mock requests. Required by Rack::Test::Methods.
|
31
|
+
attr_reader :app
|
32
|
+
|
33
|
+
# Public: Initializes a new Tynn::Test object.
|
33
34
|
#
|
34
|
-
#
|
35
|
-
# class API < Tynn
|
36
|
-
# end
|
35
|
+
# app - The application class to test (default: Tynn).
|
37
36
|
#
|
38
|
-
#
|
39
|
-
# app.get("/json")
|
40
|
-
# ```
|
37
|
+
# Examples
|
41
38
|
#
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
39
|
+
# class API < Tynn
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# app = Tynn::Test.new(API)
|
43
|
+
# app.get("/json")
|
44
|
+
#
|
45
|
+
def initialize(app = Tynn)
|
46
|
+
@app = app
|
48
47
|
end
|
49
48
|
|
50
49
|
alias_method :res, :last_response
|
data/lib/tynn/version.rb
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
test "default headers" do
|
2
|
+
Tynn.set(:default_headers, "Content-Type" => "text/plain")
|
3
|
+
|
4
|
+
Tynn.define do
|
5
|
+
root do
|
6
|
+
res.write("hei")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
app = Tynn::Test.new(Tynn)
|
11
|
+
app.get("/")
|
12
|
+
|
13
|
+
assert_equal "text/plain", app.res.headers["Content-Type"]
|
14
|
+
end
|
data/test/environment_test.rb
CHANGED
@@ -1,31 +1,27 @@
|
|
1
1
|
require_relative "../lib/tynn/environment"
|
2
2
|
|
3
|
-
test "default" do
|
4
|
-
Tynn.helpers(Tynn::Environment)
|
5
|
-
|
6
|
-
assert_equal(:development, Tynn.environment)
|
7
|
-
end
|
8
|
-
|
9
|
-
test "helpers" do
|
10
|
-
Tynn.helpers(Tynn::Environment)
|
11
|
-
|
12
|
-
assert Tynn.development?
|
13
|
-
assert !Tynn.test?
|
14
|
-
assert !Tynn.production?
|
15
|
-
end
|
16
|
-
|
17
3
|
test "use RACK_ENV by default" do
|
18
|
-
|
4
|
+
begin
|
5
|
+
old, ENV["RACK_ENV"] = ENV["RACK_ENV"], "production"
|
6
|
+
|
7
|
+
Tynn.helpers(Tynn::Environment)
|
19
8
|
|
20
|
-
|
9
|
+
assert_equal(:production, Tynn.environment)
|
21
10
|
|
22
|
-
|
11
|
+
assert !Tynn.development?
|
12
|
+
assert !Tynn.test?
|
13
|
+
assert Tynn.production?
|
23
14
|
|
24
|
-
|
15
|
+
ensure
|
16
|
+
ENV["RACK_ENV"] = old
|
17
|
+
end
|
25
18
|
end
|
26
19
|
|
27
20
|
test "use custom value" do
|
28
21
|
Tynn.helpers(Tynn::Environment, env: "development")
|
29
22
|
|
30
23
|
assert_equal(:development, Tynn.environment)
|
24
|
+
assert Tynn.development?
|
25
|
+
assert !Tynn.test?
|
26
|
+
assert !Tynn.production?
|
31
27
|
end
|