Class: BetterContentSecurityPolicy::ContentSecurityPolicy

Inherits:
Object
  • Object
show all
Defined in:
lib/better_content_security_policy/content_security_policy.rb

Overview

DSL for building a Content Security Policy. An instance of this class will be available in your controllers and views. You can call a method multiple times to add additional rules to the policy.

Constant Summary collapse

DIRECTIVES =
%w[
  base-uri
  child-src
  connect-src
  default-src
  font-src
  form-action
  frame-ancestors
  frame-src
  img-src
  manifest-src
  media-src
  object-src
  prefetch-src
  require-trusted-types-for
  script-src
  script-src-attr
  script-src-elem
  style-src
  style-src-attr
  style-src-elem
  trusted-types
  worker-src
].freeze
SCHEME_SOURCES =
%w[
  blob
  data
  filesystem
  http
  https
  mediastream
  ws
  wss
].freeze
QUOTED_SOURCES =
%w[
  none
  self
  unsafe-eval
  unsafe-hashes
  unsafe-inline
  allow-duplicates
  report-sample
  script
  strict-dynamic
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeContentSecurityPolicy

Returns a new instance of ContentSecurityPolicy.



58
59
60
# File 'lib/better_content_security_policy/content_security_policy.rb', line 58

def initialize
  @directives = {}
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(directive_sym, *args) ⇒ Object

Handles directive methods, such as #script_src and #style_src. Can be called multiple times to add additional sources.



72
73
74
75
76
77
# File 'lib/better_content_security_policy/content_security_policy.rb', line 72

def method_missing(directive_sym, *args)
  directive = directive_sym.to_s.downcase
  @directives[directive] ||= []
  @directives[directive] += args.flatten.compact.map(&:to_s)
  @directives[directive]
end

Instance Attribute Details

#directivesObject

Returns the value of attribute directives.



56
57
58
# File 'lib/better_content_security_policy/content_security_policy.rb', line 56

def directives
  @directives
end

#report_onlyObject

Returns the value of attribute report_only.



56
57
58
# File 'lib/better_content_security_policy/content_security_policy.rb', line 56

def report_only
  @report_only
end

#report_uriObject

Returns the value of attribute report_uri.



56
57
58
# File 'lib/better_content_security_policy/content_security_policy.rb', line 56

def report_uri
  @report_uri
end

Instance Method Details

#csp_source(dsl_source) ⇒ Object

Converts sources from our Ruby DSL (camelcase) into proper Content-Security-Policy sources. (kebab-case, trailing colon, wrapped in single quotes, etc.) A few examples: data => data: http => http: self => ‘self’ unsafe_eval => ‘unsafe-eval’ example.com => example.com



90
91
92
93
94
95
96
97
98
99
# File 'lib/better_content_security_policy/content_security_policy.rb', line 90

def csp_source(dsl_source)
  return "#{dsl_source}:" if SCHEME_SOURCES.include?(dsl_source)

  kebab_source = kebab_case(dsl_source)
  return "'#{kebab_source}'" if QUOTED_SOURCES.include?(kebab_source)
  return "'#{dsl_source}'" if dsl_source.start_with?("nonce-") ||
                              dsl_source.start_with?("sha256-")

  dsl_source
end

#header_nameObject



113
114
115
# File 'lib/better_content_security_policy/content_security_policy.rb', line 113

def header_name
  report_only? ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy"
end

#report_only?Boolean

Returns:

  • (Boolean)


62
63
64
# File 'lib/better_content_security_policy/content_security_policy.rb', line 62

def report_only?
  @report_only
end

#respond_to_missing?(directive, include_all = false) ⇒ Boolean

Returns:

  • (Boolean)


79
80
81
# File 'lib/better_content_security_policy/content_security_policy.rb', line 79

def respond_to_missing?(directive, include_all = false)
  valid_directive?(directive) || super
end

#to_hObject



117
118
119
120
121
122
# File 'lib/better_content_security_policy/content_security_policy.rb', line 117

def to_h
  header_value = to_s
  return {} if header_value.blank?

  { header_name => header_value }
end

#to_sObject



101
102
103
104
105
106
107
108
109
110
111
# File 'lib/better_content_security_policy/content_security_policy.rb', line 101

def to_s
  directive_strings = @directives.uniq.sort.map do |directive, dsl_sources|
    [
      kebab_case(directive),
      dsl_sources.map { |source| csp_source(source) }.join(" ")
    ].join(" ")
  end
  directive_strings << "report-uri #{report_uri}" if report_uri.present?
  directive_strings << ""
  directive_strings.join("; ").strip
end

#valid_directive?(directive) ⇒ Boolean

Returns:

  • (Boolean)


66
67
68
# File 'lib/better_content_security_policy/content_security_policy.rb', line 66

def valid_directive?(directive)
  DIRECTIVES.include?(kebab_case(directive))
end