From c8b2564f805ab0fbcca7962b449e12e259dd3882 Mon Sep 17 00:00:00 2001 From: Richard Taylor Date: Wed, 24 Aug 2016 17:55:22 +0100 Subject: [PATCH] fix(modal): Fix use of inline styles for Content Security Policy ### Summary Inline styles are not allowed under CSP, so this replaces the inline styles used by `$modalStack` with calls to `angular.element.css()` ### Details `$modalStack.open` creates two divs directly with angular.element, and both divs have inline style elements (i.e. `'
'`). If [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/Security/CSP) (CSP) is enabled, these inline styles cannot be applied and cause errors. e.g. in chrome: > [Report Only] Refused to apply inline style because it violates the following Content Security Policy directive: "default-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-xHgdoELGuMXNdMeJ4PQkbzZpH1rlmZwpY3b56d2ZvpI='), or a nonce ('nonce-...') is required to enable inline execution. Note also that 'style-src' was not explicitly set, so 'default-src' is used as a fallback. This is fixed by replacing the inline styles with calls to `angular.element.css()` (which boils down to `HTMLElement.style.foo = bar;`) as this does not trigger the inline styles violation. - For the //faux// modal div, this is simply done immediately. - For the temporary div that is replaced by the `modal-window`, it is a bit more complex: - If you just apply to this temp div using .css() it still fails when the `modal-window` directive replaces it because Angular merges the attributes of the temp div and the new one. The temp div has an inline style (set by .css()), and trying to apply this `style='...'` as an attribute to the new div causes the same inline execution error. - Instead we pass the top position through a custom attribute (`mm-top`), then the `modal-window` applies the value to itself using `element.css()`. ### Setup Browser: Chrome `52.0.2743.116 m` (up to date), plus same result in Firefox, Edge, etc. OS: Windows 10 Server: NodeJS 4.4.3 CSP Headers: `content-security-policy-report-only: default-src 'self'; report-uri /api/v0/csp-report` (This reports but doesn't prevent violations of the policy while debugging rules). ### Notes As per the error reported by Chrome, this could theoretically be worked around by using `'unsafe-inline'` but this is, as named, unsafe. It could also be worked around with a hash for each case but that isn't well supported by browsers yet, and there are a lot of different cases that would need a hash for each one. Nonces are even harder to use with library code. Test Plan: - Run all the unit tests and check they all pass - Test the library in my own project and check no CSP warnings are given - Open and close some modals at various window scroll positions and check they still position correctly --- src/modal/modal.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/modal/modal.js b/src/modal/modal.js index 9e02fb3..0bd0399 100644 --- a/src/modal/modal.js +++ b/src/modal/modal.js @@ -88,7 +88,8 @@ angular.module('mm.foundation.modal', ['mm.foundation.transition']) restrict: 'EA', scope: { index: '@', - animate: '=' + animate: '=', + mmTop: '@' }, replace: true, transclude: true, @@ -96,6 +97,13 @@ angular.module('mm.foundation.modal', ['mm.foundation.transition']) link: function (scope, element, attrs) { scope.windowClass = attrs.windowClass || ''; + // Set the `top` style using element.css to avoid Content Security Policy + // issues when using inline-styles + if (scope.mmTop) { + element.css('top', scope.mmTop); + element.css('visibility', 'visible'); + } + $timeout(function () { // trigger CSS transitions scope.animate = true; @@ -241,18 +249,21 @@ angular.module('mm.foundation.modal', ['mm.foundation.transition']) } // Create a faux modal div just to measure its - // distance to top - var faux = angular.element('
'); + // distance to top. Note that we set the style using element.css() + // rather than using inline style="..." to avoid issue with Content Security Policy + var faux = angular.element('
'); + faux.css("z-index", "-1"); parent.append(faux[0]); cssTop = parseInt($window.getComputedStyle(faux[0]).top) || 0; var openAt = calculateModalTop(faux, cssTop); faux.remove(); - var angularDomEl = angular.element('
') + var angularDomEl = angular.element('
') .attr({ 'window-class': modal.windowClass, 'index': openedWindows.length() - 1, - 'animate': 'animate' + 'animate': 'animate', + 'mm-top': '' + openAt + 'px' // Pass the top position to modal-window directive }); angularDomEl.html(modal.content);