module GovukContentSecurityPolicy
  # Generate a Content Security Policy (CSP) directive.
  #
  # Before making any changes please read our documentation: https://docs.publishing.service.gov.uk/manual/content-security-policy.html
  #
  # If you are making a change here you should consider 2 basic rules of thumb:
  #
  # 1. Are you creating a XSS risk? Adding unsafe-* declarations, allowing data: URLs or being overly permissive (e.g. https) risks these
  # 2. Is this change needed globally, if it's just one or two apps the change should be applied in them directly.

  GOVUK_DOMAINS = [
    "*.publishing.service.gov.uk",
    "*.#{ENV['GOVUK_APP_DOMAIN_EXTERNAL'] || ENV['GOVUK_APP_DOMAIN'] || 'dev.gov.uk'}",
    "www.gov.uk",
    "*.dev.gov.uk",
  ].uniq.freeze

  GOOGLE_ANALYTICS_DOMAINS = %w[www.google-analytics.com
                                ssl.google-analytics.com
                                stats.g.doubleclick.net
                                www.googletagmanager.com
                                www.region1.google-analytics.com
                                region1.google-analytics.com].freeze

  GOOGLE_STATIC_DOMAINS = %w[www.gstatic.com].freeze

  def self.build_policy(policy)
    # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src
    policy.default_src :self

    # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/base-uri
    policy.base_uri :none

    # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/img-src
    # Note: we purposely don't include `data:` here because it produces a security risk.
    policy.img_src :self,
                   *GOVUK_DOMAINS,
                   *GOOGLE_ANALYTICS_DOMAINS, # Tracking pixels
                   # Speedcurve real user monitoring (RUM) - as per: https://support.speedcurve.com/docs/add-rum-to-your-csp
                   "lux.speedcurve.com",
                   # Some content still links to an old domain we used to use
                   "assets.digital.cabinet-office.gov.uk",
                   # Allow YouTube thumbnails
                   "https://img.youtube.com",
                   "https://i.ytimg.com"

    # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
    # Note: we purposely don't include `data:`, `unsafe-inline` or `unsafe-eval` because
    # they are security risks, if you need them for a legacy app please only apply them at
    # an app level.
    policy.script_src :self,
                      *GOOGLE_ANALYTICS_DOMAINS,
                      *GOOGLE_STATIC_DOMAINS,
                      # Allow YouTube Embeds (Govspeak turns YouTube links into embeds)
                      "*.ytimg.com",
                      "www.youtube.com",
                      "www.youtube-nocookie.com"

    # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src
    # Note: we purposely don't include `data:`, `unsafe-inline` or `unsafe-eval` because
    # they are security risks, if you need them for a legacy app please only apply them at
    # an app level.
    policy.style_src :self, *GOOGLE_STATIC_DOMAINS

    # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/font-src
    # Note: we purposely don't include data here because it produces a security risk.
    policy.font_src :self

    # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src
    policy.connect_src :self,
                       *GOVUK_DOMAINS,
                       *GOOGLE_ANALYTICS_DOMAINS,
                       # Speedcurve real user monitoring (RUM) - as per: https://support.speedcurve.com/docs/add-rum-to-your-csp
                       "lux.speedcurve.com"

    # Disallow all <object>, <embed>, and <applet> elements
    #
    # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/object-src
    policy.object_src :none

    # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
    policy.frame_src :self, *GOVUK_DOMAINS, "www.youtube.com", "www.youtube-nocookie.com" # Allow youtube embeds

    # Disallow non-gov.uk domains from embeding a page using <frame>, <iframe>, <object>, or <embed> to prevent clickjacking
    #
    # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors
    policy.frame_ancestors :self, *GOVUK_DOMAINS

    policy.report_uri ENV["GOVUK_CSP_REPORT_URI"] if ENV.include?("GOVUK_CSP_REPORT_URI")
  end

  def self.configure
    Rails.application.config.content_security_policy_report_only = ENV.include?("GOVUK_CSP_REPORT_ONLY")

    # Sets a nonce per request that can be set on script-src and style-src
    # directives depending on the value of Rails.application.config.content_security_policy_nonce_directives
    #
    # Note: if an application needs to set unsafe-inline they will need to
    # unset this generator (by setting this config option to nil in their application)
    Rails.application.config.content_security_policy_nonce_generator = ->(_request) { SecureRandom.base64(16) }

    # This only applies the nonce generator to the script-src directive. We need this to
    # use unsafe-inline for style-src as a nonce will override it.
    #
    # When we want to apply it to style-src we can remove this line as the Rails default
    # is for both script-src and style-src
    Rails.application.config.content_security_policy_nonce_directives = %w[script-src]

    policy = Rails.application.config.content_security_policy(&method(:build_policy))

    # # allow apps to customise the CSP by passing a block e.g:
    # GovukContentSecuirtyPolicy.configure do |policy|
    #   policy.image_src(*policy.image_src, "https://i.ytimg.com")
    # end
    yield(policy) if block_given?

    policy
  end
end