Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SRI #301

Merged
merged 2 commits into from
May 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Development

- Adds SRI to js and css assets ([PR #301](https://github.com/alphagov/govuk_template/pull/301)). This requires `sprockets-rails` >= 3.0 in the projects using this gem.

# 0.20.1
- Fix invalid html from Apple touch icons syntax ([PR #300]https://github.com/alphagov/govuk_template/pull/300) and
update icon sizes to match Apple specs
Expand Down
50 changes: 50 additions & 0 deletions build_tools/compiler/template_processor.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
require 'erb'
require 'active_support/core_ext/hash'
require 'active_support/core_ext/array'

module Compiler
class TemplateProcessor
Expand Down Expand Up @@ -39,5 +41,53 @@ def asset_path(file, options={})
def method_missing(name, *args)
puts "#{name} #{args.inspect}"
end

def stylesheet_link_tag(*sources)
options = exclude_sri_fields(sources.extract_options!)
sources.uniq.map { |source|
link_options = {
"rel" => "stylesheet",
"media" => "screen",
"href" => asset_path(source)
}.merge!(options)
tag(:link, tag_options(link_options))
}.join("\n")
end

def javascript_include_tag(*sources)
options = exclude_sri_fields(sources.extract_options!)
sources.uniq.map { |source|
script_options = {
"src" => asset_path(source)
}.merge!(options)
content_tag(:script, tag_options(script_options))
}.join("\n")
end

def exclude_sri_fields(options)
options.stringify_keys.except("integrity", "crossorigin")
end

def content_tag(name, options = nil)
"<#{name}#{options}></#{name}>"
end

def tag(name, options)
"<#{name}#{options}/>"
end

def tag_options(options)
return if options.empty?
output = "".dup
sep = " "
options.each_pair do |key, value|
if !value.nil?
output << sep
output << %(#{key}="#{value}")
end
end
output unless output.empty?
end

end
end
17 changes: 17 additions & 0 deletions docs/using-with-rails.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,20 @@ Or to add content to `<head>`, for stylesheets or similar:
```

Check out the [full list of blocks](template-blocks.md) you can use to customise the template.

## SRI

`govuk_template` >= 20.0.0 can be used together with `sprockets-rails` >= 3.0.0 in order to make use of the SRI

You can read more about SRI [here](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity).

SRI will add an `integrity` attribute on your script tags:

`<script src="https://example.com/example.css"
integrity="sha384oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8w"
crossorigin="anonymous"></script>`

The example above is generated automatically by sprockets-rails in your project if the integrity option is set to true:

`<%= stylesheet_script_tag 'example', integrity: true %>`

9 changes: 5 additions & 4 deletions source/views/layouts/govuk_template.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@
<meta charset="utf-8" />
<title><%= content_for?(:page_title) ? yield(:page_title) : "GOV.UK - The best place to find government services and information" %></title>

<!--[if gt IE 8]><!--><link href="<%= asset_path "govuk-template.css" %>" media="screen" rel="stylesheet" /><!--<![endif]-->
<!--[if gt IE 8]><!--><%= stylesheet_link_tag "govuk-template.css", integrity: true, crossorigin: "anonymous" %><!--<![endif]-->
<!--[if IE 6]><link href="<%= asset_path "govuk-template-ie6.css" %>" media="screen" rel="stylesheet" /><![endif]-->
<!--[if IE 7]><link href="<%= asset_path "govuk-template-ie7.css" %>" media="screen" rel="stylesheet" /><![endif]-->
<!--[if IE 8]><link href="<%= asset_path "govuk-template-ie8.css" %>" media="screen" rel="stylesheet" /><![endif]-->
<link href="<%= asset_path "govuk-template-print.css" %>" media="print" rel="stylesheet" />
<%= stylesheet_link_tag "govuk-template-print.css", media: "print", integrity: true, crossorigin: "anonymous" %>

<!--[if IE 8]><link href="<%= asset_path "fonts-ie8.css" %>" media="all" rel="stylesheet" /><![endif]-->
<!--[if gte IE 9]><!--><link href="<%= asset_path "fonts.css" %>" media="all" rel="stylesheet" /><!--<![endif]-->
<!--[if lt IE 9]><script src="<%= asset_path "ie.js" %>"></script><![endif]-->
<!--[if gte IE 9]><!--><%= stylesheet_link_tag "fonts.css", media: "all", integrity: true, crossorigin: "anonymous" %><!--<![endif]-->
<!--[if gte IE 9]><!--><%= stylesheet_link_tag "fonts.css", media: "all", integrity: true, crossorigin: "anonymous" %><!--<![endif]-->
<!--[if lt IE 9]><%= javascript_include_tag "ie.js", integrity: true, crossorigin: "anonymous" %><![endif]-->

<link rel="shortcut icon" href="<%= asset_path 'favicon.ico' %>" type="image/x-icon" />
<%# the colour used for mask-icon is the standard palette $black from
Expand Down
1 change: 1 addition & 0 deletions spec/build_tools/compiler/django_processor_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def valid_sections
let(:file) {"some/file.erb"}
subject {described_class.new(file)}

it_behaves_like "a processor"

describe "#handle_yield" do
valid_sections.each do |key, content|
Expand Down
1 change: 1 addition & 0 deletions spec/build_tools/compiler/ejs_processor_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def valid_sections
let(:file) {"some/file.erb"}
subject {described_class.new(file)}

it_behaves_like "a processor"

describe "#handle_yield" do
valid_sections.each do |key, content|
Expand Down
1 change: 1 addition & 0 deletions spec/build_tools/compiler/jinja_processor_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def valid_sections
let(:file) {"some/file.erb"}
subject {described_class.new(file)}

it_behaves_like "a processor"

describe "#handle_yield" do
valid_sections.each do |key, content|
Expand Down
11 changes: 11 additions & 0 deletions spec/build_tools/compiler/liquid_processor_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require 'spec_helper'
require File.join(PROJECT_ROOT, 'build_tools/compiler/liquid_processor.rb')

describe Compiler::LiquidProcessor do

let(:file) {"some/file.erb"}
subject {described_class.new(file)}

it_behaves_like "a processor"

end
11 changes: 11 additions & 0 deletions spec/build_tools/compiler/mustache_inheritance_processor_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require 'spec_helper'
require File.join(PROJECT_ROOT, 'build_tools/compiler/mustache_inheritance_processor.rb')

describe Compiler::MustacheInheritanceProcessor do

let(:file) {"some/file.erb"}
subject {described_class.new(file)}

it_behaves_like "a processor"

end
3 changes: 1 addition & 2 deletions spec/build_tools/compiler/mustache_processor_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ def valid_sections
end

describe Compiler::MustacheProcessor do

let(:file) {"some/file.erb"}
subject {described_class.new(file)}

it_behaves_like "a processor"

describe "#handle_yield" do
valid_sections.each do |key, content|
Expand Down Expand Up @@ -64,5 +64,4 @@ def valid_sections
end
end
end

end
11 changes: 11 additions & 0 deletions spec/build_tools/compiler/plain_processor_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require 'spec_helper'
require File.join(PROJECT_ROOT, 'build_tools/compiler/plain_processor.rb')

describe Compiler::PlainProcessor do

let(:file) {"some/file.erb"}
subject {described_class.new(file)}

it_behaves_like "a processor"

end
2 changes: 2 additions & 0 deletions spec/build_tools/compiler/play_processor_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def expected_parameter_names
describe Compiler::PlayProcessor do
subject { described_class.new("dummy filename") }

it_behaves_like "a processor"

describe "top_of_page" do
it "declares all of the template parameters" do
expected_parameter_names.each do |parameter_name|
Expand Down
80 changes: 80 additions & 0 deletions spec/support/examples/processor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
require 'spec_helper'
require 'set'

shared_examples_for "a processor" do
let(:html_erb_file) { "a/file.css" }
let(:processor) { described_class.new(html_erb_file) }

describe "convert rails tags into html" do

let(:css_source) { "govuk-template.css" }
let(:js_source) { "ie.js" }

describe "#stylesheet_link_tag" do
let(:css_options) { {"media" => "print"} }
let(:sri_attributes) { {"integrity" => true, "crossorigin" => "anonymous"} }

it "should parse the stylesheet tag" do
expect(processor.stylesheet_link_tag(css_source)).to eql("<link rel=\"stylesheet\" media=\"screen\" href=\"#{processor.asset_path(css_source)}\"/>")
end

context "if css file is for print" do
it "should parse the stylesheet tag and extra options" do
expect(processor.stylesheet_link_tag(css_source, css_options)).to eql("<link rel=\"stylesheet\" media=\"print\" href=\"#{processor.asset_path(css_source)}\"/>")
end
end

context "if sri attributes are present, it should ignore them" do
it "should parse the stylesheet tag without the integrity attribute" do
expect(processor.stylesheet_link_tag(css_source, sri_attributes)).to eql("<link rel=\"stylesheet\" media=\"screen\" href=\"#{processor.asset_path(css_source)}\"/>")
end
end
end

describe "#javascript_include_tag" do
let(:js_options) { {"charset" => "UTF-8"} }
let(:sri_attributes) { {"integrity" => true, "crossorigin" => "anonymous"} }

it "should parse the javascript tag" do
expect(processor.javascript_include_tag(js_source)).to eql("<script src=\"#{processor.asset_path(js_source)}\"></script>")
end

it "should parse the javascript tag and extra options" do
expect(processor.javascript_include_tag(js_source, js_options)).to eql("<script src=\"#{processor.asset_path(js_source)}\" charset=\"UTF-8\"></script>")
end

it "if sri attributes are present, it should ignore them" do
expect(processor.javascript_include_tag(js_source, sri_attributes)).to eql("<script src=\"#{processor.asset_path(js_source)}\"></script>")
end
end

describe "#content_tag" do
it "should return the correct html script tag" do
expect(processor.content_tag(:script, " src=\"#{processor.asset_path(js_source)}\"")).to eql("<script src=\"#{processor.asset_path(js_source)}\"></script>")
end
end

describe "#tag" do
it "should return the correct html link tag" do
expect(processor.tag(:link, " rel=\"stylesheet\" media=\"screen\" href=\"#{processor.asset_path(css_source)}\"")).to eql("<link rel=\"stylesheet\" media=\"screen\" href=\"#{processor.asset_path(css_source)}\"/>")
end
end

describe "#tag_options" do
let(:options) { {"rel"=>"stylesheet", "media"=>"screen", "href"=>processor.asset_path(css_source) } }

it "flattens the hash into a string of quoted html attributes" do
expect(processor.tag_options(options)).to eql(" rel=\"stylesheet\" media=\"screen\" href=\"#{processor.asset_path(css_source)}\"")
end
end

describe "#exclude_sri_fields" do
let(:options) { {"rel"=>"stylesheet", "media"=>"screen", "href"=>processor.asset_path(css_source), "integrity" => true, "crossorigin" => "anonymous" } }

it "should remove the integrity and crossorigin keys from the hash" do
expect(processor.exclude_sri_fields(options)).to eql({"href" => processor.asset_path(css_source), "media" => "screen", "rel" => "stylesheet"})
end
end

end
end
6 changes: 6 additions & 0 deletions spec/support/uses_of_yield.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ def asset_path(*args)
def method_missing(name, *args)
puts "#{name} #{args.inspect}"
end

def stylesheet_link_tag(*sources)
end

def javascript_include_tag(*sources)
end
end

# return an array of unique values passed to yield in the templates
Expand Down