angular/angular.js

View on GitHub
src/ng/directive/ngCsp.js

Summary

Maintainability
A
0 mins
Test Coverage
'use strict';

/**
 * @ngdoc directive
 * @name ngCsp
 *
 * @restrict A
 * @element ANY
 * @description
 *
 * AngularJS has some features that can conflict with certain restrictions that are applied when using
 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules.
 *
 * If you intend to implement CSP with these rules then you must tell AngularJS not to use these
 * features.
 *
 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps.
 *
 *
 * The following default rules in CSP affect AngularJS:
 *
 * * The use of `eval()`, `Function(string)` and similar functions to dynamically create and execute
 * code from strings is forbidden. AngularJS makes use of this in the {@link $parse} service to
 * provide a 30% increase in the speed of evaluating AngularJS expressions. (This CSP rule can be
 * disabled with the CSP keyword `unsafe-eval`, but it is generally not recommended as it would
 * weaken the protections offered by CSP.)
 *
 * * The use of inline resources, such as inline `<script>` and `<style>` elements, are forbidden.
 * This prevents apps from injecting custom styles directly into the document. AngularJS makes use of
 * this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}). To make these
 * directives work when a CSP rule is blocking inline styles, you must link to the `angular-csp.css`
 * in your HTML manually. (This CSP rule can be disabled with the CSP keyword `unsafe-inline`, but
 * it is generally not recommended as it would weaken the protections offered by CSP.)
 *
 * If you do not provide `ngCsp` then AngularJS tries to autodetect if CSP is blocking dynamic code
 * creation from strings (e.g., `unsafe-eval` not specified in CSP header) and automatically
 * deactivates this feature in the {@link $parse} service. This autodetection, however, triggers a
 * CSP error to be logged in the console:
 *
 * ```
 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of
 * script in the following Content Security Policy directive: "default-src 'self'". Note that
 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
 * ```
 *
 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp`
 * directive on an element of the HTML document that appears before the `<script>` tag that loads
 * the `angular.js` file.
 *
 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.*
 *
 * You can specify which of the CSP related AngularJS features should be deactivated by providing
 * a value for the `ng-csp` attribute. The options are as follows:
 *
 * * no-inline-style: this stops AngularJS from injecting CSS styles into the DOM
 *
 * * no-unsafe-eval: this stops AngularJS from optimizing $parse with unsafe eval of strings
 *
 * You can use these values in the following combinations:
 *
 *
 * * No declaration means that AngularJS will assume that you can do inline styles, but it will do
 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous
 * versions of AngularJS.
 *
 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell AngularJS to deactivate both inline
 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous
 * versions of AngularJS.
 *
 * * Specifying only `no-unsafe-eval` tells AngularJS that we must not use eval, but that we can
 * inject inline styles. E.g. `<body ng-csp="no-unsafe-eval">`.
 *
 * * Specifying only `no-inline-style` tells AngularJS that we must not inject styles, but that we can
 * run eval - no automatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">`
 *
 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells AngularJS that we must not inject
 * styles nor use eval, which is the same as an empty: ng-csp.
 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">`
 *
 * @example
 *
 * This example shows how to apply the `ngCsp` directive to the `html` tag.
   ```html
     <!doctype html>
     <html ng-app ng-csp>
     ...
     ...
     </html>
   ```

  <!-- Note: the `.csp` suffix in the example name triggers CSP mode in our http server! -->
  <example name="example.csp" module="cspExample" ng-csp="true">
    <file name="index.html">
      <div ng-controller="MainController as ctrl">
        <div>
          <button ng-click="ctrl.inc()" id="inc">Increment</button>
          <span id="counter">
            {{ctrl.counter}}
          </span>
        </div>

        <div>
          <button ng-click="ctrl.evil()" id="evil">Evil</button>
          <span id="evilError">
            {{ctrl.evilError}}
          </span>
        </div>
      </div>
    </file>
    <file name="script.js">
       angular.module('cspExample', [])
         .controller('MainController', function MainController() {
            this.counter = 0;
            this.inc = function() {
              this.counter++;
            };
            this.evil = function() {
              try {
                eval('1+2'); // eslint-disable-line no-eval
              } catch (e) {
                this.evilError = e.message;
              }
            };
          });
    </file>
    <file name="protractor.js" type="protractor">
      var util, webdriver;

      var incBtn = element(by.id('inc'));
      var counter = element(by.id('counter'));
      var evilBtn = element(by.id('evil'));
      var evilError = element(by.id('evilError'));

      function getAndClearSevereErrors() {
        return browser.manage().logs().get('browser').then(function(browserLog) {
          return browserLog.filter(function(logEntry) {
            return logEntry.level.value > webdriver.logging.Level.WARNING.value;
          });
        });
      }

      function clearErrors() {
        getAndClearSevereErrors();
      }

      function expectNoErrors() {
        getAndClearSevereErrors().then(function(filteredLog) {
          expect(filteredLog.length).toEqual(0);
          if (filteredLog.length) {
            console.log('browser console errors: ' + util.inspect(filteredLog));
          }
        });
      }

      function expectError(regex) {
        getAndClearSevereErrors().then(function(filteredLog) {
          var found = false;
          filteredLog.forEach(function(log) {
            if (log.message.match(regex)) {
              found = true;
            }
          });
          if (!found) {
            throw new Error('expected an error that matches ' + regex);
          }
        });
      }

      beforeEach(function() {
        util = require('util');
        webdriver = require('selenium-webdriver');
      });

      // For now, we only test on Chrome,
      // as Safari does not load the page with Protractor's injected scripts,
      // and Firefox webdriver always disables content security policy (#6358)
      if (browser.params.browser !== 'chrome') {
        return;
      }

      it('should not report errors when the page is loaded', function() {
        // clear errors so we are not dependent on previous tests
        clearErrors();
        // Need to reload the page as the page is already loaded when
        // we come here
        browser.driver.getCurrentUrl().then(function(url) {
          browser.get(url);
        });
        expectNoErrors();
      });

      it('should evaluate expressions', function() {
        expect(counter.getText()).toEqual('0');
        incBtn.click();
        expect(counter.getText()).toEqual('1');
        expectNoErrors();
      });

      it('should throw and report an error when using "eval"', function() {
        evilBtn.click();
        expect(evilError.getText()).toMatch(/Content Security Policy/);
        expectError(/Content Security Policy/);
      });
    </file>
  </example>
  */

// `ngCsp` is not implemented as a proper directive any more, because we need it be processed while
// we bootstrap the app (before `$parse` is instantiated). For this reason, we just have the `csp()`
// fn that looks for the `ng-csp` attribute anywhere in the current doc.