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 CSP Nonce option to Select component #3260

Merged
merged 2 commits into from
Dec 24, 2018

Conversation

Avaq
Copy link
Contributor

@Avaq Avaq commented Dec 7, 2018

Motivation

To close #2917

Changes

 <Select
+  nonce={MY_SECRET_NONCE}
 >
  • The emotion dependency was replaced by create-emotion.
  • An instance of emotion is created in the Select constructor, passing in the nonce if the user provided it.
  • The emotion instance is passed to all components via commonProps.
  • All components were updated to make use of the custom emotion instance instead of the global one.

Checklist

  • ensure all the examples work correctly after your change

  • ensure you do not introduce any new [linting] errors or warnings

  • ensure that you do not introduce any new type errors

    I was unable to run yarn flow:
    $ yarn flow
    yarn run v1.12.3
    $ ./react-select/node_modules/.bin/flow
    events.js:167
          throw er; // Unhandled 'error' event
          ^
    
    Error: spawn ./react-select/node_modules/flow-bin/flow-linux64-v0.72.0/flow ENOENT
  • make sure there's an issue open for any work you take on

  • make sure you do not add regressions

    Two tests were failing before, and after the changes I made (see fix: update broken tests #3253):
    ● should call onChange with `null` on hitting backspace when backspaceRemovesValue is true and isMulti is false
    
      expect(jest.fn()).toHaveBeenCalledWith(expected)
    
      Expected mock function to have been called with:
        {"action": "clear"}
      as argument 2, but it was called with
        {"action": "clear", "name": "test-input-name"}.
    
      Difference:
    
      - Expected
      + Received
    
        Object {
          "action": "clear",
      +   "name": "test-input-name",
        }
    
        1433 |     .find(Control)
        1434 |     .simulate('keyDown', { keyCode: 8, key: 'Backspace' });
      > 1435 |   expect(onChangeSpy).toHaveBeenCalledWith(null, { action: 'clear' });
             |                       ^
        1436 | });
        1437 | 
        1438 | test('should call onChange with an array on hitting backspace when backspaceRemovesValue is true and isMulti is true', () => {
    
        at Object.toHaveBeenCalledWith (src/__tests__/Select.test.js:1435:23)
    
    ● should call onChange with an array on hitting backspace when backspaceRemovesValue is true and isMulti is true
    
      expect(jest.fn()).toHaveBeenCalledWith(expected)
    
      Expected mock function to have been called with:
        {"action": "pop-value"}
      as argument 2, but it was called with
        {"action": "pop-value", "name": "test-input-name", "removedValue": undefined}.
    
      Difference:
    
      - Expected
      + Received
    
        Object {
          "action": "pop-value",
      +   "name": "test-input-name",
      +   "removedValue": undefined,
        }
    
        1450 |     .find(Control)
        1451 |     .simulate('keyDown', { keyCode: 8, key: 'Backspace' });
      > 1452 |   expect(onChangeSpy).toHaveBeenCalledWith([], { action: 'pop-value' });
             |                       ^
        1453 | });
        1454 | 
        1455 | test('multi select > clicking on X next to option will call onChange with all options other that the clicked option', () => {
    
        at Object.toHaveBeenCalledWith (src/__tests__/Select.test.js:1452:23)
  • include tests with your changes
    I'm not sure how to test this change

  • check that the coverage hasn't dropped

    Coverage increased marginally:

    Before

    --------------------------------------|----------|----------|----------|----------|-------------------|
    File                                  |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
    --------------------------------------|----------|----------|----------|----------|-------------------|
    All files                             |    81.66 |    51.13 |    78.26 |     82.3 |                   |
     react-select                         |      100 |      100 |      100 |      100 |                   |
      enzymeAdapter.setup.js              |      100 |      100 |      100 |      100 |                   |
     react-select/src                     |    87.08 |     66.1 |    73.86 |    88.12 |                   |
      Async.js                            |    90.57 |    73.91 |    73.33 |    93.75 |         71,89,104 |
      AsyncCreatable.js                   |      100 |      100 |      100 |      100 |                   |
      Creatable.js                        |    65.12 |    60.71 |    71.43 |    66.67 |... 40,142,145,148 |
      Select.js                           |    92.08 |     66.5 |    78.64 |    93.31 |... 1457,1459,1460 |
      builtins.js                         |      100 |      100 |      100 |      100 |                   |
      diacritics.js                       |      100 |      100 |      100 |      100 |                   |
      filters.js                          |      100 |       50 |      100 |      100 |    28,29,30,34,38 |
      index.js                            |      100 |      100 |      100 |      100 |                   |
      stateManager.js                     |    88.24 |      100 |    81.82 |    88.24 |             46,49 |
      styles.js                           |     12.5 |        0 |        0 |     12.5 |... ,97,98,101,105 |
      theme.js                            |      100 |      100 |      100 |      100 |                   |
      types.js                            |      100 |      100 |      100 |      100 |                   |
      utils.js                            |     62.5 |    56.82 |    47.62 |    63.01 |... 19,241,253,267 |
     react-select/src/__tests__           |     94.6 |    75.76 |    93.64 |    94.63 |                   |
      Async.test.js                       |    63.33 |      100 |    61.54 |    63.33 |... 62,163,164,165 |
      AsyncCreatable.test.js              |      100 |      100 |      100 |      100 |                   |
      Creatable.test.js                   |      100 |      100 |      100 |      100 |                   |
      Select.test.js                      |    96.48 |    68.18 |     95.2 |    96.61 |... 2308,2309,2310 |
      StateManaged.test.js                |      100 |       75 |      100 |      100 |               181 |
      constants.js                        |      100 |      100 |      100 |      100 |                   |
     react-select/src/accessibility       |      100 |    83.33 |       80 |      100 |                   |
      index.js                            |      100 |    83.33 |       80 |      100 |            4,6,10 |
     react-select/src/animated            |    53.66 |        0 |    33.33 |       50 |                   |
      Input.js                            |    66.67 |      100 |       50 |    66.67 |                23 |
      MultiValue.js                       |    66.67 |      100 |       50 |    66.67 |                15 |
      Placeholder.js                      |    66.67 |        0 |       50 |       50 |                 9 |
      SingleValue.js                      |    66.67 |      100 |       50 |       50 |                10 |
      ValueContainer.js                   |    66.67 |      100 |       50 |       50 |                 9 |
      index.js                            |      100 |      100 |      100 |      100 |                   |
      transitions.js                      |     12.5 |        0 |        0 |     12.5 |... ,92,94,103,107 |
     react-select/src/components          |    77.73 |    38.52 |    92.86 |    76.96 |                   |
      Control.js                          |      100 |      100 |      100 |      100 |                   |
      Group.js                            |      100 |      100 |      100 |      100 |                   |
      Input.js                            |      100 |       75 |      100 |      100 |                22 |
      Menu.js                             |    57.39 |    13.48 |    76.47 |    57.27 |... 17,518,522,525 |
      MultiValue.js                       |      100 |       50 |      100 |      100 |             40,50 |
      Option.js                           |      100 |      100 |      100 |      100 |                   |
      Placeholder.js                      |      100 |      100 |      100 |      100 |                   |
      SingleValue.js                      |      100 |       50 |      100 |      100 |                21 |
      containers.js                       |      100 |      100 |      100 |      100 |                   |
      index.js                            |      100 |      100 |      100 |      100 |                   |
      indicators.js                       |      100 |    83.33 |      100 |      100 |           163,214 |
     react-select/src/internal            |    16.13 |     2.94 |    27.27 |    15.74 |                   |
      A11yText.js                         |      100 |      100 |      100 |      100 |                   |
      DummyInput.js                       |      100 |      100 |      100 |      100 |                   |
      NodeResolver.js                     |        0 |      100 |        0 |        0 |          13,16,19 |
      ScrollBlock.js                      |       40 |     12.5 |    33.33 |     37.5 |    25,26,31,32,57 |
      ScrollCaptor.js                     |     5.36 |     2.08 |     8.33 |     5.77 |... 17,118,122,126 |
      index.js                            |      100 |      100 |      100 |      100 |                   |
      react-fast-compare.js               |    17.65 |     2.17 |      100 |    17.07 |... 70,78,84,85,88 |
     react-select/src/internal/ScrollLock |     7.14 |     5.66 |        0 |     7.41 |                   |
      constants.js                        |      100 |      100 |      100 |      100 |                   |
      index.js                            |     4.55 |     6.38 |        0 |     4.76 |... 27,128,133,142 |
      utils.js                            |        0 |        0 |        0 |        0 |... 14,15,16,17,24 |
    --------------------------------------|----------|----------|----------|----------|-------------------|
    

    After

    --------------------------------------|----------|----------|----------|----------|-------------------|
    File                                  |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
    --------------------------------------|----------|----------|----------|----------|-------------------|
    All files                             |    81.85 |    51.57 |    78.31 |    82.59 |                   |
     react-select                         |      100 |      100 |      100 |      100 |                   |
      enzymeAdapter.setup.js              |      100 |      100 |      100 |      100 |                   |
     react-select/src                     |     87.5 |    66.04 |    74.01 |    88.83 |                   |
      Async.js                            |    90.57 |    73.91 |    73.33 |    93.75 |         71,89,104 |
      AsyncCreatable.js                   |      100 |      100 |      100 |      100 |                   |
      Creatable.js                        |    65.12 |    60.71 |    71.43 |    66.67 |... 40,142,145,148 |
      Select.js                           |    92.64 |    66.41 |    78.85 |    94.29 |... 1468,1470,1471 |
      builtins.js                         |      100 |      100 |      100 |      100 |                   |
      diacritics.js                       |      100 |      100 |      100 |      100 |                   |
      filters.js                          |      100 |       50 |      100 |      100 |    28,29,30,34,38 |
      index.js                            |      100 |      100 |      100 |      100 |                   |
      stateManager.js                     |    88.24 |      100 |    81.82 |    88.24 |             46,49 |
      styles.js                           |     12.5 |        0 |        0 |     12.5 |... ,97,98,101,105 |
      theme.js                            |      100 |      100 |      100 |      100 |                   |
      types.js                            |      100 |      100 |      100 |      100 |                   |
      utils.js                            |     62.5 |    56.82 |    47.62 |    63.01 |... 19,241,253,267 |
     react-select/src/__tests__           |     94.6 |    75.76 |    93.64 |    94.63 |                   |
      Async.test.js                       |    63.33 |      100 |    61.54 |    63.33 |... 62,163,164,165 |
      AsyncCreatable.test.js              |      100 |      100 |      100 |      100 |                   |
      Creatable.test.js                   |      100 |      100 |      100 |      100 |                   |
      Select.test.js                      |    96.48 |    68.18 |     95.2 |    96.61 |... 2308,2309,2310 |
      StateManaged.test.js                |      100 |       75 |      100 |      100 |               181 |
      constants.js                        |      100 |      100 |      100 |      100 |                   |
     react-select/src/accessibility       |      100 |    83.33 |       80 |      100 |                   |
      index.js                            |      100 |    83.33 |       80 |      100 |            4,6,10 |
     react-select/src/animated            |    53.66 |        0 |    33.33 |       50 |                   |
      Input.js                            |    66.67 |      100 |       50 |    66.67 |                23 |
      MultiValue.js                       |    66.67 |      100 |       50 |    66.67 |                15 |
      Placeholder.js                      |    66.67 |        0 |       50 |       50 |                 9 |
      SingleValue.js                      |    66.67 |      100 |       50 |       50 |                10 |
      ValueContainer.js                   |    66.67 |      100 |       50 |       50 |                 9 |
      index.js                            |      100 |      100 |      100 |      100 |                   |
      transitions.js                      |     12.5 |        0 |        0 |     12.5 |... ,92,94,103,107 |
     react-select/src/components          |    77.83 |    41.96 |    92.86 |    77.07 |                   |
      Control.js                          |      100 |      100 |      100 |      100 |                   |
      Group.js                            |      100 |      100 |      100 |      100 |                   |
      Input.js                            |      100 |       75 |      100 |      100 |                23 |
      Menu.js                             |    57.39 |    13.48 |    76.47 |    57.27 |... 17,518,522,525 |
      MultiValue.js                       |      100 |    66.67 |      100 |      100 |             39,49 |
      Option.js                           |      100 |      100 |      100 |      100 |                   |
      Placeholder.js                      |      100 |      100 |      100 |      100 |                   |
      SingleValue.js                      |      100 |       50 |      100 |      100 |                20 |
      containers.js                       |      100 |      100 |      100 |      100 |                   |
      index.js                            |      100 |      100 |      100 |      100 |                   |
      indicators.js                       |      100 |    88.89 |      100 |      100 |           155,200 |
     react-select/src/internal            |    16.13 |     2.94 |    27.27 |    15.74 |                   |
      A11yText.js                         |      100 |      100 |      100 |      100 |                   |
      DummyInput.js                       |      100 |      100 |      100 |      100 |                   |
      NodeResolver.js                     |        0 |      100 |        0 |        0 |          13,16,19 |
      ScrollBlock.js                      |       40 |     12.5 |    33.33 |     37.5 |    26,27,32,33,58 |
      ScrollCaptor.js                     |     5.36 |     2.08 |     8.33 |     5.77 |... 17,118,122,126 |
      index.js                            |      100 |      100 |      100 |      100 |                   |
      react-fast-compare.js               |    17.65 |     2.17 |      100 |    17.07 |... 70,78,84,85,88 |
     react-select/src/internal/ScrollLock |     7.14 |     5.66 |        0 |     7.41 |                   |
      constants.js                        |      100 |      100 |      100 |      100 |                   |
      index.js                            |     4.55 |     6.38 |        0 |     4.76 |... 27,128,133,142 |
      utils.js                            |        0 |        0 |        0 |        0 |... 14,15,16,17,24 |
    --------------------------------------|----------|----------|----------|----------|-------------------|
    
  • update the documentation to reflect potential new features
    I would like to ask for pointers as to which sections of the documentation this might have affected.


I will test this new feature in my own project, and I'll let you know how that goes.

@Avaq
Copy link
Contributor Author

Avaq commented Dec 7, 2018

These changes appear to have solved part of the issue - most CSS is now applied, and many CSP warnings went away, but some CSS, specifically CSS related to the aria labels, is still being blocked because the nonce doesn't propagate all the way down. I'm investigating the cause.

src/Select.js Outdated
@@ -369,6 +372,8 @@ export default class Select extends Component<Props, State> {
const selectValue = cleanValue(value);
const menuOptions = this.buildMenuOptions(props, selectValue);

this.emotion = createEmotion(props.nonce ? { nonce: props.nonce } : {});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend adding also key: 'react-select' and caching created instances per nonce to share them between instances

@Avaq
Copy link
Contributor Author

Avaq commented Dec 11, 2018

My colleague @dicearr investigated and fixed the last remaining CSP issues. He described his findings here: wearereasonablepeople#1 (comment). He also implemented momoization of the emotion instances, as requested by @Aaroniussorry, GitHub autocompleted the wrong name @Andarist.

src/Select.js Outdated
@@ -372,7 +373,8 @@ export default class Select extends Component<Props, State> {
const selectValue = cleanValue(value);
const menuOptions = this.buildMenuOptions(props, selectValue);

this.emotion = createEmotion(props.nonce ? { nonce: props.nonce } : {});
this.createEmotion = memoize(createEmotion);
this.emotion = this.createEmotion(props.nonce ? { nonce: props.nonce } : {});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@emotion/memoize has a simple POJO cache under the hood, it uses first argument as cache key, so u cant quite memoize based on objects - https://github.com/emotion-js/emotion/blob/fc156cf7214fda37fac3ce654569a4e17965d7e4/packages/memoize/src/index.js

Also - you have to create memoized version in the top level scope to keep cached emotion's "global", instead of caching them per <Select/> instance

correct way of reusing this package would look smth like this

const getEmotion = memoize(nonce => createEmotion(nonce ? { nonce } : {}))

// and in constructor
this.emotion = getEmotion(props.nonce)

package.json Outdated
"classnames": "^2.2.5",
"create-emotion": "^10.0.4",
"emotion": "^9.1.2",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might not have a direct dependency on emotion anymore.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this should be removed

@Avaq
Copy link
Contributor Author

Avaq commented Dec 11, 2018

I re-checked all the checks in the PR description.

@Avaq
Copy link
Contributor Author

Avaq commented Dec 20, 2018

I squashed the fixup commits. I also had a look at the documentation. It seems the nonce property and its description are already included, because the documentation is generated from the flow types.

@Avaq
Copy link
Contributor Author

Avaq commented Dec 21, 2018

I rebased on master and all tests now pass.

@Avaq
Copy link
Contributor Author

Avaq commented Dec 21, 2018

I checked the "Add tests" box in the PR description to indicate that I think I'm done, although specific tests for the nonce property were not added. However, the snapshots were updated to include the emotion property now passed to all components.

Copy link
Collaborator

@gwyneplaine gwyneplaine left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is great, thanks @Avaq lgtm

@PlopTheReal
Copy link

Hey,
It seems this is not working anymore.
Is there is a way to provide a nonce down to emotion styles?

@Avaq
Copy link
Contributor Author

Avaq commented Feb 15, 2021

The work done here caused problems down the line, so it was reverted. See #3346

@Methuselah96
Copy link
Collaborator

See #4283 (comment) for a way to include a Nonce in v4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[V2] Request/Bug: Needs CSP Considerations
6 participants