Skip to content

Commit

Permalink
API-5937: Content Security Policy improvements (#442)
Browse files Browse the repository at this point in the history
* API-5937: Content Security Policy improvements

* API-5937: Add missing .url

* API-5937: Upgrade to CSP 3 only

* API-5937: Fix JavaScript null property error

* API-5937: Update sbt-plugin
  • Loading branch information
johnsgp authored Mar 23, 2023
1 parent 7847d8e commit 65edcc4
Show file tree
Hide file tree
Showing 15 changed files with 60 additions and 54 deletions.
20 changes: 10 additions & 10 deletions app/assets/javascripts/expand-endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
var lastPart = url.split("#").pop();
var endpoint = document.getElementById(lastPart + "-details");
if (endpoint != null) {
var endpoint = document.getElementById(lastPart + "-details");
var att = document.createAttribute("open");
att.value = "";
endpoint.setAttributeNode(att);
Expand Down Expand Up @@ -37,10 +36,8 @@
var clickedEndpoint = e.target.id
var parentEndpoint = document.getElementById(clickedEndpoint + "-details");
if (parentEndpoint.hasAttribute("open")) {
var parentEndpoint = document.getElementById(clickedEndpoint + "-details");
parentEndpoint.removeAttribute("open");
} else {
var parentEndpoint = document.getElementById(clickedEndpoint + "-details");
var att = document.createAttribute("open");
att.value = "";
parentEndpoint.setAttributeNode(att);
Expand Down Expand Up @@ -70,14 +67,17 @@
window.addEventListener('load', function () {
expandEnpoint()

var allLinks = document.querySelector("#endpoints").querySelectorAll('a[href^="#"]');
for (var i = 0; i < allLinks.length; i++) {
allLinks[i].addEventListener('click', function (e) {
e.preventDefault();
document.querySelector(this.getAttribute('href')).scrollIntoView({
behavior: 'smooth'
var endpoints = document.querySelector("#endpoints")
if (endpoints) {
var allLinks = endpoints.querySelectorAll('a[href^="#"]');
for (var i = 0; i < allLinks.length; i++) {
allLinks[i].addEventListener('click', function (e) {
e.preventDefault();
document.querySelector(this.getAttribute('href')).scrollIntoView({
behavior: 'smooth'
});
});
});
}
}
});
})();
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ <h2 class="govuk-heading-m">Application-restricted endpoints</h2>
generate an access token.
</p>
<p class="govuk-body">
If the endpoint requires a <a class="govuk-link" href="@controllers.routes.DocumentationController.referenceGuidePage()#scopes">scope</a>,
If the endpoint requires a <a class="govuk-link" href="@controllers.routes.DocumentationController.referenceGuidePage().url#scopes">scope</a>,
your application must include this scope when creating the access token.
</p>
<p class="govuk-body">Read the <a class="govuk-link" href="@controllers.routes.ApiDocumentationController.apiIndexPage(None, None, None)">API documentation</a> for authorisation rules for specific API endpoints.</p>
Expand Down Expand Up @@ -117,7 +117,7 @@ <h4 class="govuk-heading-s">1. Generate an access token</h4>
<tr class="govuk-table__row">
<td class="govuk-table__cell"><code class="code--slim">scope</code></td>
<td class="govuk-table__cell">A space-delimited list of
<a class="govuk-link" href="@controllers.routes.DocumentationController.referenceGuidePage()#scopes">scopes</a>
<a class="govuk-link" href="@controllers.routes.DocumentationController.referenceGuidePage().url#scopes">scopes</a>
you want permission to access. A scope is optional for some
endpoints. If a scope is required, check the API documentation for details.
</td>
Expand Down Expand Up @@ -207,7 +207,7 @@ <h4 class="govuk-heading-s">1. Generate an access token</h4>
<h4 class="govuk-heading-s">2. Call an API</h4>
<p class="govuk-body">You can now call an API using the <code class="code--slim">access_token</code> we issued. Do this with an
Authorization header containing this <code class="code--slim">access_token</code> as an OAuth 2.0 Bearer
Token with the correct API <a class="govuk-link" href="@controllers.routes.DocumentationController.referenceGuidePage()#scopes">scope</a>.
Token with the correct API <a class="govuk-link" href="@controllers.routes.DocumentationController.referenceGuidePage().url#scopes">scope</a>.
</p>
<p class="govuk-caption-m">Example request</p>
<pre class="code--block">curl -X GET @{applicationConfig.sandboxApiBaseUrl}/hello/application \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ <h2 class="govuk-heading-m">User-restricted endpoints</h2>
HMRC on their behalf, without sharing their access credentials.
</p>

<p class="govuk-body">The end user authenticates directly with us using their Government Gateway account, and grants authority for specific <a class="govuk-link" href="@controllers.routes.DocumentationController.referenceGuidePage()#scopes">scopes</a>.</p>
<p class="govuk-body">The end user authenticates directly with us using their Government Gateway account, and grants authority for specific <a class="govuk-link" href="@controllers.routes.DocumentationController.referenceGuidePage().url#scopes">scopes</a>.</p>

<p class="govuk-body">We then issue an OAuth 2.0 <a class="govuk-link" href="https://tools.ietf.org/html/rfc6749#section-1.4" rel="noopener noreferrer">access token</a> that’s specific to the end user. Your application passes the access token in subsequent API requests to user-restricted endpoints.</p>

Expand Down Expand Up @@ -91,7 +91,7 @@ <h4 class="govuk-heading-s">1. Request authorisation</h4>
<li>We prompt the user to sign in using their Government Gateway account.</li>
<li>The user is taken through <a class="govuk-link" href="@controllers.routes.AuthorisationController.authorisation2SVPage().url">2-Step Verification (2SV)</a>.</li>
<li>The user may be asked to confirm their identity. This depends on the user type, the specific API scopes being requested and whether or not the user has previously confirmed their identity.</li>
<li>The user is asked to grant your application the authority to access certain <a class="govuk-link" href="https://developer.service.hmrc.gov.uk/api-documentation/docs/reference-guide#scopes">scopes</a>.</li>
<li>The user is asked to grant your application the authority to access certain <a class="govuk-link" href="@controllers.routes.DocumentationController.referenceGuidePage().url#scopes">scopes</a>.</li>
</ol>

<p class="govuk-body">The following diagram illustrates the process:</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,23 @@
* limitations under the License.
*@

@import views.html.helper.CSPNonce

@this()

@(uri: String)
@(uri: String)(implicit requestHeader: RequestHeader)

<div id="redoc"></div>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="@controllers.routes.Assets.versioned("css/redoc-fixes.css")">
<script src='@controllers.routes.Assets.versioned("javascripts/redoc/redoc.standalone.js")' charset="UTF-8"> </script>
<script src='@controllers.routes.Assets.versioned("javascripts/redoc/redoc-fixes.js")' charset="UTF-8"> </script>
<script @CSPNonce.attr src='@controllers.routes.Assets.versioned("javascripts/redoc/redoc.standalone.js")' charset="UTF-8"> </script>
<script @CSPNonce.attr src='@controllers.routes.Assets.versioned("javascripts/redoc/redoc-fixes.js")' charset="UTF-8"> </script>

@*
To fix this issue:
https://github.com/Redocly/redoc/issues/1108#issuecomment-585990742
*@
<script>
<script @CSPNonce.attr>
var options = {
theme: {
spacing: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
* limitations under the License.
*@

@import views.html.helper.CSPNonce

@this()

@(serviceName: String, version: String, apiName: String)
@(serviceName: String, version: String, apiName: String)(implicit requestHeader: RequestHeader)

<head>
<title>@apiName - HMRC Developer Hub - GOV.UK</title>
Expand All @@ -25,14 +27,14 @@
<div id="redoc"></div>

<link rel="stylesheet" href="@controllers.routes.Assets.versioned("css/redoc-fixes.css")">
<script src='@controllers.routes.Assets.versioned("javascripts/redoc/redoc.standalone.js")' charset="UTF-8"> </script>
<script src='@controllers.routes.Assets.versioned("javascripts/redoc/redoc-fixes.js")' charset="UTF-8"> </script>
<script @CSPNonce.attr src='@controllers.routes.Assets.versioned("javascripts/redoc/redoc.standalone.js")' charset="UTF-8"> </script>
<script @CSPNonce.attr src='@controllers.routes.Assets.versioned("javascripts/redoc/redoc-fixes.js")' charset="UTF-8"> </script>

@*
To fix this issue:
https://github.com/Redocly/redoc/issues/1108#issuecomment-585990742
*@
<script>
<script @CSPNonce.attr>
var options = {
theme: {
spacing: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ <h3 class="govuk-heading-s">Other test data</h3>
requests and then read back that same data through retrieval (GET) requests.
</p>
<p class="govuk-body">Some stateful APIs have an accompanying test support API for setting up test data, which you can find on our
<a class="govuk-link" href="https://developer.service.hmrc.gov.uk/api-documentation/docs/api">API documentation page</a>.
<a class="govuk-link" href="@controllers.routes.ApiDocumentationController.apiIndexPage(None, None, None).url">API documentation page</a>.
For example, the Individual Tax API only supports retrieval (GET) requests so the accompanying Individual PAYE
Test Support API can create (POST) test data for it.
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@
@import uk.gov.hmrc.apidocumentation.views.helpers._
@import uk.gov.hmrc.apidocumentation.services._
@import uk.gov.hmrc.apidocumentation.config.ApplicationConfig
@import views.html.helper.CSPNonce

@this(applicationConfig: ApplicationConfig)

@(viewModel: ViewModel, maybeVersion: Option[ExtendedAPIVersion], maybeApi: Option[ExtendedAPIDefinition], loggedIn: Boolean = false)
@(viewModel: ViewModel, maybeVersion: Option[ExtendedAPIVersion], maybeApi: Option[ExtendedAPIDefinition], loggedIn: Boolean = false)(implicit requestHeader: RequestHeader)

@renderGetApiVersionForm = {
@for(api <- maybeApi) {
Expand Down Expand Up @@ -146,4 +147,4 @@ <h2 id="read-more-title" class="govuk-heading-m">
</p>
</div>
</div>
<script src='@routes.Assets.versioned("javascripts/expand-endpoint.js")' type="text/javascript"></script>
<script @CSPNonce.attr src='@routes.Assets.versioned("javascripts/expand-endpoint.js")' type="text/javascript"></script>
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
@import uk.gov.hmrc.apidocumentation.views.templates.FooterLinks

@import play.twirl.api.HtmlFormat
@import views.html.helper.CSPNonce

@this(
govukTemplate: GovukTemplate,
Expand All @@ -45,16 +46,16 @@
additionalTopContent: Option[Html] = None,
fullWidthContent: Boolean = false,
bodyEndBlock: Option[Html] = None
)(mainContent: Html = HtmlFormat.empty)(implicit applicationConfig: ApplicationConfig, messages: Messages, request: play.api.mvc.Request[Any])
)(mainContent: Html = HtmlFormat.empty)(implicit applicationConfig: ApplicationConfig, messages: Messages, request: play.api.mvc.Request[Any], requestHeader: RequestHeader)

@head = {
<link href='@routes.Assets.versioned("css/main.css")' media="screen" rel="stylesheet" type="text/css" />
@hmrcTrackingConsentSnippet()
}

@scripts = {
<script src='@routes.Assets.versioned("lib/govuk-frontend/govuk/all.js")'></script>
<script>window.GOVUKFrontend.initAll();</script>
<script @CSPNonce.attr src='@routes.Assets.versioned("lib/govuk-frontend/govuk/all.js")'></script>
<script @CSPNonce.attr>window.GOVUKFrontend.initAll();</script>
}


Expand All @@ -65,7 +66,7 @@
}

@scriptElem = {
<script src='@routes.Assets.versioned("javascripts/apis-app.min.js")' type="text/javascript"></script>
<script @CSPNonce.attr src='@routes.Assets.versioned("javascripts/apis-app.min.js")' type="text/javascript"></script>
}

@footerBlock = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
@import uk.gov.hmrc.apidocumentation.services._
@import uk.gov.hmrc.apidocumentation.config.ApplicationConfig
@import uk.gov.hmrc.apidocumentation.models.apispecification.DocumentationItem
@import views.html.helper.CSPNonce

@this(applicationConfig: ApplicationConfig)

@(markdownBlocks: List[DocumentationItem], title: String, maybeVersion: Option[ExtendedAPIVersion], maybeApi: Option[ExtendedAPIDefinition], loggedIn: Boolean = false)
@(markdownBlocks: List[DocumentationItem], title: String, maybeVersion: Option[ExtendedAPIVersion], maybeApi: Option[ExtendedAPIDefinition], loggedIn: Boolean = false
)(implicit requestHeader: RequestHeader)

@renderGetApiVersionForm = {
@for(api <- maybeApi) {
Expand Down Expand Up @@ -138,4 +140,4 @@ <h2 id="read-more-title" class="govuk-heading-m">
}
</div>
</div>
<script src='@routes.Assets.versioned("javascripts/expand-endpoint.js")' type="text/javascript"></script>
<script @CSPNonce.attr src='@routes.Assets.versioned("javascripts/expand-endpoint.js")' type="text/javascript"></script>
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
@import uk.gov.hmrc.apidocumentation.views.html.include
@import uk.gov.hmrc.apidocumentation.views.html.partials
@import uk.gov.hmrc.apidocumentation.views.html.templates._
@import views.html.helper.CSPNonce

@this(
govukTemplate: GovukTemplate,
Expand All @@ -44,17 +45,17 @@
navLinks: Seq[NavLink] = Seq.empty,
leftNav: Option[Html] = None,
feedbackBanner: Option[FeedbackBanner] = Some(GenericFeedbackBanner)
)(contentBlock: Html)(implicit applicationConfig: ApplicationConfig, messages: Messages, request: play.api.mvc.Request[Any])
)(contentBlock: Html)(implicit applicationConfig: ApplicationConfig, messages: Messages, request: play.api.mvc.Request[Any], requestHeader: RequestHeader)

@head = {
@hmrcTrackingConsentSnippet()
<link href='@controllers.routes.Assets.versioned("css/main.css")' media="screen" rel="stylesheet" type="text/css" />
}

@scripts = {
<script src='@controllers.routes.Assets.versioned("lib/govuk-frontend/govuk/all.js")'></script>
<script src='@routes.Assets.versioned("javascripts/feedback.js")' type="text/javascript"></script>
<script>window.GOVUKFrontend.initAll();</script>
<script @CSPNonce.attr src='@controllers.routes.Assets.versioned("lib/govuk-frontend/govuk/all.js")'></script>
<script @CSPNonce.attr src='@routes.Assets.versioned("javascripts/feedback.js")' type="text/javascript"></script>
<script @CSPNonce.attr>window.GOVUKFrontend.initAll();</script>
}


Expand Down Expand Up @@ -103,7 +104,7 @@
@siteHelper()
</div>
</div>
<script src='@routes.Assets.versioned("javascripts/nav-scroll.js")' type="text/javascript"></script>
<script @CSPNonce.attr src='@routes.Assets.versioned("javascripts/nav-scroll.js")' type="text/javascript"></script>
</div>
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
@import uk.gov.hmrc.apidocumentation.views.templates.FooterLinks
@import uk.gov.hmrc.govukfrontend.views.viewmodels.pagelayout.PageLayout
@import uk.gov.hmrc.apidocumentation.views.html.FullWidthContent
@import views.html.helper.CSPNonce

@this(
govukTemplate: GovukTemplate,
Expand All @@ -46,8 +47,8 @@
}

@scripts = {
<script src='@controllers.routes.Assets.versioned("lib/govuk-frontend/govuk/all.js")'></script>
<script>window.GOVUKFrontend.initAll();</script>
<script @CSPNonce.attr src='@controllers.routes.Assets.versioned("lib/govuk-frontend/govuk/all.js")'></script>
<script @CSPNonce.attr>window.GOVUKFrontend.initAll();</script>
}

@bodyEndDefault = {
Expand Down
7 changes: 1 addition & 6 deletions conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,7 @@ platform {
}
}

play.filters.csp.directives.default-src = "'unsafe-inline' 'self' localhost:* https://www.google-analytics.com"
play.filters.csp.directives.script-src = "'unsafe-inline' 'self' localhost:12345 localhost:9032 https://www.google-analytics.com https://www.googletagmanager.com https://tagmanager.google.com"
play.filters.csp.directives.style-src = "'unsafe-inline' 'self' localhost:9032 https://tagmanager.google.com https://fonts.googleapis.com"
play.filters.csp.directives.img-src = "data: 'self' localhost:9032 https://ssl.gstatic.com https://www.gstatic.com https://www.google-analytics.com https://www.googletagmanager.com"
play.filters.csp.directives.font-src = "'self' https://ssl.gstatic.com https://www.gstatic.com https://fonts.gstatic.com https://fonts.googleapis.com"
play.filters.csp.directives.worker-src = "'self' blob:"
play.filters.csp.directives.script-src = ${play.filters.csp.nonce.pattern} "'strict-dynamic' 'unsafe-inline' https: http:"

retryCount = 3
retryDelayMilliseconds = 500
Expand Down
6 changes: 3 additions & 3 deletions project/AppDependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ object AppDependencies {
def apply(): Seq[ModuleID] = compile ++ test

lazy val playJsonVersion = "2.9.2"
lazy val bootstrapVersion = "7.12.0"
lazy val bootstrapVersion = "7.14.0"
lazy val seleniumVersion = "4.2.0"

lazy val compile = Seq(
ws,
caffeine,
"uk.gov.hmrc" %% "bootstrap-frontend-play-28" % bootstrapVersion,
"uk.gov.hmrc" %% "http-metrics" % "2.7.0",
"uk.gov.hmrc" %% "play-partials" % "8.3.0-play-28",
"uk.gov.hmrc" %% "play-frontend-hmrc" % "6.2.0-play-28",
"uk.gov.hmrc" %% "play-partials" % "8.4.0-play-28",
"uk.gov.hmrc" %% "play-frontend-hmrc" % "7.0.0-play-28",
"org.typelevel" %% "cats-core" % "2.6.1",
"org.commonjava.googlecode.markdown4j" % "markdown4j" % "2.2-cj-1.1",
"com.typesafe.play" %% "play-json" % playJsonVersion,
Expand Down
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ resolvers += Resolver.url("HMRC-open-artefacts-ivy", url("https://open.artefacts

addSbtPlugin("uk.gov.hmrc" % "sbt-auto-build" % "3.9.0")
addSbtPlugin("uk.gov.hmrc" % "sbt-distributables" % "2.2.0")
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.18")
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.19")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.3")
addSbtPlugin("org.irundaia.sbt" % "sbt-sassify" % "1.5.1")
addSbtPlugin("net.ground5hark.sbt" % "sbt-concat" % "0.2.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ package uk.gov.hmrc.apidocumentation.views
import java.util.Locale

import org.scalatest.OptionValues
import org.scalatestplus.play.guice.GuiceOneAppPerSuite
import org.scalatestplus.play.WsScalaTestClient
import org.scalatestplus.play.guice.GuiceOneAppPerSuite

import play.api.i18n._
import play.api.mvc.{AnyContent, Request}
import play.api.mvc.request.RequestAttrKey
import play.api.test.FakeRequest

import uk.gov.hmrc.apidocumentation.common.utils.AsyncHmrcSpec

trait CommonViewSpec extends AsyncHmrcSpec with OptionValues with WsScalaTestClient with GuiceOneAppPerSuite {
implicit val messagesProvider: MessagesProvider = MessagesImpl(Lang(Locale.ENGLISH), new DefaultMessagesApi())
implicit val request = mock[Request[AnyContent]]

when(request.uri).thenReturn("/fake/uri")
implicit val request = FakeRequest("GET", "/fake/uri").addAttr(RequestAttrKey.CSPNonce, "fake-nonce")
}

0 comments on commit 65edcc4

Please sign in to comment.