http-security 0.1.0

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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +21 -0
  5. data/.yardopts +1 -0
  6. data/ChangeLog.md +17 -0
  7. data/Gemfile +17 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.md +90 -0
  10. data/Rakefile +34 -0
  11. data/http-security.gemspec +23 -0
  12. data/lib/http/security.rb +2 -0
  13. data/lib/http/security/exceptions.rb +8 -0
  14. data/lib/http/security/headers.rb +12 -0
  15. data/lib/http/security/headers/cache_control.rb +36 -0
  16. data/lib/http/security/headers/content_security_policy.rb +71 -0
  17. data/lib/http/security/headers/content_security_policy_report_only.rb +10 -0
  18. data/lib/http/security/headers/pragma.rb +24 -0
  19. data/lib/http/security/headers/public_key_pins.rb +60 -0
  20. data/lib/http/security/headers/public_key_pins_report_only.rb +10 -0
  21. data/lib/http/security/headers/set_cookie.rb +75 -0
  22. data/lib/http/security/headers/strict_transport_security.rb +29 -0
  23. data/lib/http/security/headers/x_content_type_options.rb +24 -0
  24. data/lib/http/security/headers/x_frame_options.rb +39 -0
  25. data/lib/http/security/headers/x_permitted_cross_domain_policies.rb +47 -0
  26. data/lib/http/security/headers/x_xss_protection.rb +34 -0
  27. data/lib/http/security/http_date.rb +13 -0
  28. data/lib/http/security/malformed_header.rb +33 -0
  29. data/lib/http/security/parsers.rb +14 -0
  30. data/lib/http/security/parsers/cache_control.rb +62 -0
  31. data/lib/http/security/parsers/content_security_policy.rb +128 -0
  32. data/lib/http/security/parsers/content_security_policy_report_only.rb +10 -0
  33. data/lib/http/security/parsers/expires.rb +19 -0
  34. data/lib/http/security/parsers/parser.rb +408 -0
  35. data/lib/http/security/parsers/pragma.rb +25 -0
  36. data/lib/http/security/parsers/public_key_pins.rb +43 -0
  37. data/lib/http/security/parsers/public_key_pins_report_only.rb +10 -0
  38. data/lib/http/security/parsers/set_cookie.rb +62 -0
  39. data/lib/http/security/parsers/strict_transport_security.rb +42 -0
  40. data/lib/http/security/parsers/x_content_type_options.rb +19 -0
  41. data/lib/http/security/parsers/x_frame_options.rb +47 -0
  42. data/lib/http/security/parsers/x_permitted_cross_domain_policies.rb +33 -0
  43. data/lib/http/security/parsers/x_xss_protection.rb +27 -0
  44. data/lib/http/security/response.rb +323 -0
  45. data/lib/http/security/version.rb +5 -0
  46. data/spec/data/alexa.csv +100 -0
  47. data/spec/headers/cache_control_spec.rb +40 -0
  48. data/spec/headers/content_security_policy_spec.rb +46 -0
  49. data/spec/headers/pragma_spec.rb +26 -0
  50. data/spec/headers/public_key_pins_spec.rb +68 -0
  51. data/spec/headers/set_cookie_spec.rb +122 -0
  52. data/spec/headers/strict_transport_security_spec.rb +39 -0
  53. data/spec/headers/x_content_type_options_spec.rb +26 -0
  54. data/spec/headers/x_frame_options_spec.rb +86 -0
  55. data/spec/headers/x_permitted_cross_domain_policies_spec.rb +108 -0
  56. data/spec/headers/x_xss_protection_spec.rb +59 -0
  57. data/spec/parsers/cache_control_spec.rb +26 -0
  58. data/spec/parsers/content_security_policy_report_only_spec.rb +48 -0
  59. data/spec/parsers/content_security_policy_spec.rb +74 -0
  60. data/spec/parsers/expires_spec.rb +71 -0
  61. data/spec/parsers/parser_spec.rb +317 -0
  62. data/spec/parsers/pragma_spec.rb +10 -0
  63. data/spec/parsers/public_key_pins_spec.rb +81 -0
  64. data/spec/parsers/set_cookie_spec.rb +55 -0
  65. data/spec/parsers/strict_transport_security_spec.rb +62 -0
  66. data/spec/parsers/x_content_type_options_spec.rb +10 -0
  67. data/spec/parsers/x_frame_options_spec.rb +24 -0
  68. data/spec/parsers/x_permitted_cross_domain_policies_spec.rb +34 -0
  69. data/spec/parsers/x_xss_protection_spec.rb +39 -0
  70. data/spec/response_spec.rb +262 -0
  71. data/spec/spec_helper.rb +13 -0
  72. data/tasks/alexa.rb +40 -0
  73. metadata +171 -0
@@ -0,0 +1,10 @@
1
+ require 'http/security/headers/public_key_pins'
2
+
3
+ module HTTP
4
+ module Security
5
+ module Headers
6
+ class PublicKeyPinsReportOnly < PublicKeyPins
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,75 @@
1
+ module HTTP
2
+ module Security
3
+ module Headers
4
+ class SetCookie
5
+
6
+ include Enumerable
7
+
8
+ # @return [Array<Cookie>]
9
+ attr_reader :cookies
10
+
11
+ class Cookie
12
+
13
+ attr_reader :cookie
14
+
15
+ attr_reader :path
16
+
17
+ attr_reader :domain
18
+
19
+ attr_reader :expires
20
+
21
+ def initialize(directives={})
22
+ @cookie = directives[:cookie]
23
+ @path = directives[:path]
24
+ @domain = directives[:domain]
25
+ @expires = directives[:expires]
26
+ @secure = directives[:secure]
27
+ @http_only = directives[:http_only]
28
+ end
29
+
30
+ def name
31
+ @cookie.keys.first
32
+ end
33
+
34
+ def value
35
+ @cookie.values.first
36
+ end
37
+
38
+ def secure?
39
+ !!@secure
40
+ end
41
+
42
+ def http_only?
43
+ !!@http_only
44
+ end
45
+
46
+ def to_s
47
+ str = "#{name}=#{value}"
48
+
49
+ str << "; Path=#{@path}" if @path
50
+ str << "; Domain=#{@domain}" if @domain
51
+ str << "; Expires=#{@expires.httpdate}" if @expires
52
+ str << "; Secure" if @secure
53
+ str << "; HttpOnly" if @http_only
54
+
55
+ return str
56
+ end
57
+
58
+ end
59
+
60
+ def initialize(cookies=[])
61
+ @cookies = cookies.map { |cookie| Cookie.new(cookie) }
62
+ end
63
+
64
+ def each(&block)
65
+ @cookies.each(&block)
66
+ end
67
+
68
+ def to_s
69
+ @cookies.map(&:to_s).join(', ')
70
+ end
71
+
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,29 @@
1
+ module HTTP
2
+ module Security
3
+ module Headers
4
+ class StrictTransportSecurity
5
+
6
+ attr_reader :max_age
7
+
8
+ def initialize(directives={})
9
+ @max_age = directives[:max_age]
10
+ @include_sub_domains = directives[:includesubdomains]
11
+ end
12
+
13
+ def include_sub_domains?
14
+ !!@include_sub_domains
15
+ end
16
+
17
+ def to_s
18
+ directives = []
19
+
20
+ directives << "max-age=#{@max_age}" if @max_age
21
+ directives << "includeSubDomains" if @include_sub_domains
22
+
23
+ return directives.join('; ')
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ module HTTP
2
+ module Security
3
+ module Headers
4
+ class XContentTypeOptions
5
+
6
+ def initialize(directives={})
7
+ @no_sniff = directives[:nosniff]
8
+ end
9
+
10
+ def no_sniff?
11
+ !!@no_sniff
12
+ end
13
+
14
+ def to_s
15
+ str = ''
16
+ str << "nosniff" if @no_sniff
17
+
18
+ return str
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,39 @@
1
+ module HTTP
2
+ module Security
3
+ module Headers
4
+ class XFrameOptions
5
+
6
+ attr_reader :allow_from
7
+
8
+ def initialize(directives={})
9
+ @deny = directives[:deny]
10
+ @same_origin = directives[:sameorigin]
11
+ @allow_from = directives[:allow_from]
12
+ @allow_all = directives[:allowall]
13
+ end
14
+
15
+ def deny?
16
+ !!@deny
17
+ end
18
+
19
+ def same_origin?
20
+ !!@same_origin
21
+ end
22
+
23
+ def allow_all?
24
+ !!@allow_all
25
+ end
26
+
27
+ def to_s
28
+ if @deny then 'DENY'
29
+ elsif @same_origin then 'SAMEORIGIN'
30
+ elsif @allow_from then "ALLOW-FROM #{@allow_from}"
31
+ elsif @allow_all then 'ALLOWALL'
32
+ else ''
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,47 @@
1
+ module HTTP
2
+ module Security
3
+ module Headers
4
+ class XPermittedCrossDomainPolicies
5
+
6
+ def initialize(directives={})
7
+ @none = directives[:none]
8
+ @master_only = directives[:master_only]
9
+ @by_content_type = directives[:by_content_type]
10
+ @by_ftp_filename = directives[:by_ftp_filename]
11
+ @all = directives[:all]
12
+ end
13
+
14
+ def none?
15
+ !!@none
16
+ end
17
+
18
+ def master_only?
19
+ !!@master_only
20
+ end
21
+
22
+ def by_content_type?
23
+ !!@by_content_type
24
+ end
25
+
26
+ def by_ftp_filename?
27
+ !!@by_ftp_filename
28
+ end
29
+
30
+ def all?
31
+ !!@all
32
+ end
33
+
34
+ def to_s
35
+ if @none then 'none'
36
+ elsif @master_only then 'master-only'
37
+ elsif @by_content_type then 'by-content-type'
38
+ elsif @by_ftp_filename then 'by-ftp-filename'
39
+ elsif @all then 'all'
40
+ else ''
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,34 @@
1
+ module HTTP
2
+ module Security
3
+ module Headers
4
+ class XXSSProtection
5
+
6
+ attr_reader :mode
7
+
8
+ attr_reader :report
9
+
10
+ def initialize(directives={})
11
+ @enabled = directives[:enabled]
12
+ @mode = directives[:mode]
13
+ @report = directives[:report]
14
+ end
15
+
16
+ def enabled?
17
+ !!@enabled
18
+ end
19
+
20
+ def to_s
21
+ str = if @enabled then '1'
22
+ else '0'
23
+ end
24
+
25
+ str << "; mode=#{@mode}" if @mode
26
+ str << "; report=#{@report}" if @report
27
+
28
+ return str
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ require 'date'
2
+
3
+ module HTTP
4
+ module Security
5
+ class HTTPDate < Date
6
+
7
+ def to_s
8
+ httpdate
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,33 @@
1
+ module HTTP
2
+ module Security
3
+ class MalformedHeader
4
+
5
+ # Raw value of the header.
6
+ #
7
+ # @return [String]
8
+ attr_reader :value
9
+
10
+ # Cause of the parser failure.
11
+ #
12
+ # @return [Parslet::Cause]
13
+ attr_reader :cause
14
+
15
+ #
16
+ # Initializes the malformed header.
17
+ #
18
+ # @param [String] value
19
+ # The raw header value.
20
+ #
21
+ # @param [Parslet::Cause] cause
22
+ # The cause of the parser failure.
23
+ #
24
+ def initialize(value,cause)
25
+ @value = value
26
+ @cause = cause
27
+ end
28
+
29
+ alias value to_s
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ require 'http/security/parsers/cache_control'
2
+ require 'http/security/parsers/content_security_policy'
3
+ require 'http/security/parsers/content_security_policy_report_only'
4
+ require 'http/security/parsers/expires'
5
+ require 'http/security/parsers/pragma'
6
+ require 'http/security/parsers/public_key_pins'
7
+ require 'http/security/parsers/public_key_pins_report_only'
8
+ require 'http/security/parsers/strict_transport_security'
9
+ require 'http/security/parsers/set_cookie'
10
+ require 'http/security/parsers/public_key_pins'
11
+ require 'http/security/parsers/x_content_type_options'
12
+ require 'http/security/parsers/x_frame_options'
13
+ require 'http/security/parsers/x_permitted_cross_domain_policies'
14
+ require 'http/security/parsers/x_xss_protection'
@@ -0,0 +1,62 @@
1
+ require 'http/security/parsers/parser'
2
+
3
+ module HTTP
4
+ module Security
5
+ module Parsers
6
+ class CacheControl < Parser
7
+ # Cache-Control
8
+ # Syntax:
9
+ # Cache-Control = "Cache-Control" ":" 1#cache-directive
10
+ # cache-directive = cache-response-directive
11
+ # cache-response-directive =
12
+ # "public" ; Section 14.9.1
13
+ # | "private" [ "=" <"> 1#field-name <"> ] ; Section 14.9.1
14
+ # | "no-cache" [ "=" <"> 1#field-name <"> ]; Section 14.9.1
15
+ # | "no-store" ; Section 14.9.2
16
+ # | "no-transform" ; Section 14.9.5
17
+ # | "must-revalidate" ; Section 14.9.4
18
+ # | "proxy-revalidate" ; Section 14.9.4
19
+ # | "max-age" "=" delta-seconds ; Section 14.9.3
20
+ # | "s-maxage" "=" delta-seconds ; Section 14.9.3
21
+ # | cache-extension ; Section 14.9.6
22
+ # cache-extension = token [ "=" ( token | quoted-string ) ]
23
+ rule(:cache_control) do
24
+ (
25
+ cache_control_values >> (comma >> cache_control_values).repeat
26
+ ).as(:directives)
27
+ end
28
+ root :cache_control
29
+
30
+ rule(:cache_control_values) do
31
+ cc_public |
32
+ cc_private |
33
+ no_cache |
34
+ no_store |
35
+ no_transform |
36
+ must_revalidate |
37
+ max_age |
38
+ s_maxage |
39
+ only_if_cached |
40
+ header_extension
41
+ end
42
+
43
+ #"private" [ "=" <"> 1#field-name <"> ];
44
+ rule(:cc_public) do
45
+ stri("public").as(:key) >> (equals >> field_name.as(:value)).maybe
46
+ end
47
+
48
+ #"private" [ "=" <"> 1#field-name <"> ];
49
+ rule(:cc_private) do
50
+ stri("private").as(:key) >> (equals >> field_name.as(:value)).maybe
51
+ end
52
+
53
+ field_directive_rule :no_cache, 'no-cache'
54
+ directive_rule :no_store, 'no-store'
55
+ directive_rule :no_transform, 'no-transform'
56
+ directive_rule :must_revalidate, 'must-revalidate'
57
+ directive_rule :only_if_cached, 'only-if-cached'
58
+
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,128 @@
1
+ require 'http/security/parsers/parser'
2
+
3
+ module HTTP
4
+ module Security
5
+ module Parsers
6
+ class ContentSecurityPolicy < Parser
7
+ # Content-Security-Policy
8
+ # Syntax:
9
+ # Content-Security-Policy =
10
+ # policy-token = [ directive-token *( ";" [ directive-token ] ) ]
11
+ # directive-token = *WSP [ directive-name [ WSP directive-value ] ]
12
+ # directive-name = 1*( ALPHA / DIGIT / "-" )
13
+ # directive-value = *( WSP / <VCHAR except ";" and ","> )
14
+ #
15
+ # Parsing Policies:
16
+ # To parse the policy policy, the user agent MUST use an algorithm equivalent to the following:
17
+ # 1. Let the set of directives be the empty set.
18
+ # 2. For each non-empty token returned by strictly splitting the string policy on the character U+003B SEMICOLON (;):
19
+ # 1. Skip whitespace.
20
+ # 2. Collect a sequence of characters that are not space characters. The collected characters are the directive name.
21
+ # 3. If there are characters remaining in token, skip ahead exactly one character (which must be a space character).
22
+ # 4. The remaining characters in token (if any) are the directive value.
23
+ # 5. If the set of directives already contains a directive whose name is a case insensitive match for directive name,
24
+ # ignore this instance of the directive and continue to the next token.
25
+ # 6. Add a directive to the set of directives with name directive name and value directive value.
26
+ # 3. Return the set of directives.
27
+ rule(:csp_pattern) do
28
+ (
29
+ csp_entry >> ( str(";") >> wsp >> csp_entry ).repeat(0) >> semicolon.maybe
30
+ ).as(:directives)
31
+ end
32
+ root :csp_pattern
33
+
34
+ rule(:csp_entry) do
35
+ (csp_directive.as(:key) >> wsp >> source_list.as(:value)) |
36
+ report_uri |
37
+ sandbox
38
+ end
39
+
40
+ rule(:csp_directive) do
41
+ stri("default-src") |
42
+ stri("script-src") |
43
+ stri("object-src") |
44
+ stri("style-src") |
45
+ stri("img-src") |
46
+ stri("media-src") |
47
+ stri("frame-src") |
48
+ stri("font-src") |
49
+ stri("connect-src")
50
+ end
51
+
52
+ # Source list
53
+ # Syntax:
54
+ # source-list = *WSP [ source-expression *( 1*WSP source-expression ) *WSP ]
55
+ # / *WSP "'none'" *WSP
56
+ # source-expression = scheme-source / host-source / keyword-source
57
+ # scheme-source = scheme ":"
58
+ # host-source = [ scheme "://" ] host [ port ]
59
+ # ext-host-source = host-source "/" *( <VCHAR except ";" and ","> )
60
+ # ; ext-host-source is reserved for future use.
61
+ # keyword-source = "'self'" / "'unsafe-inline'" / "'unsafe-eval'"
62
+ # scheme = <scheme production from RFC 3986>
63
+ # host = "*" / [ "*." ] 1*host-char *( "." 1*host-char )
64
+ # host-char = ALPHA / DIGIT / "-"
65
+ # port = ":" ( 1*DIGIT / "*" )
66
+ rule(:source_list) do
67
+ (wsp? >> stri("'none'") >> wsp?) |
68
+ (wsp? >> source_expression >> (wsp >> source_expression).repeat(0))
69
+ end
70
+
71
+ rule(:source_expression) do
72
+ scheme_source | host_source | keyword_source
73
+ end
74
+
75
+ rule(:csp_vchar) do
76
+ match["\x20-\x2b"] |
77
+ match["#{Regexp.escape("\x2d")}-\x3a"] |
78
+ match["\x3c-\x7f"]
79
+ end
80
+
81
+ rule(:scheme_source) do
82
+ (scheme >> str("://")).absent? >> scheme >> str(":")
83
+ end
84
+
85
+ rule(:host_source) do
86
+ (scheme >> str("://")).maybe >> csp_host >> port.maybe
87
+ end
88
+
89
+ rule(:csp_host) do
90
+ (str("*.").maybe >> host_char.repeat(1) >> ( str(".") >> host_char.repeat(1) ).repeat(0)) |
91
+ str("*")
92
+ end
93
+
94
+ rule(:host_char) do
95
+ alnum | str("-")
96
+ end
97
+
98
+ rule(:keyword_source) do
99
+ stri("'self'") | stri("'unsafe-inline'") | stri("'unsafe-eval'")
100
+ end
101
+
102
+ rule(:port) do
103
+ str(":") >> digits.as(:port)
104
+ end
105
+
106
+ rule(:ext_host_source) do
107
+ (scheme >> str("://")).maybe >> csp_host >> ext_host_source.maybe >> port.maybe
108
+ end
109
+
110
+ # report-uri
111
+ # directive-name = "report-uri"
112
+ # directive-value = uri-reference *( 1*WSP uri-reference )
113
+ # uri-reference = <URI-reference from RFC 3986>
114
+ rule(:report_uri) do
115
+ stri("report-uri").as(:key) >> wsp.repeat(1) >> (uri >> (wsp.repeat(1) >> uri).repeat(0)).as(:values)
116
+ end
117
+
118
+ # sandbox (Optional)
119
+ # directive-name = "sandbox"
120
+ # directive-value = token *( 1*WSP token )
121
+ # token = <token from RFC 2616>
122
+ rule(:sandbox) do
123
+ stri("sandbox").as(:key) >> wsp.repeat(1) >> (token >> (wsp.repeat(1) >> token).repeat(0)).as(:value)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end