Add CSP whitelist to the security settings
closes CORE-2109 Test Plan: - Enable the Content Security Policy feature flag - Go to account settings, security tab - Use the Add Domain form to add a domain - It should appear on the whitelist - Flip the CSP switch to enabled Change-Id: Iaea578c52e9b8bb6541a4e13edc6a1702e907cdf Reviewed-on: https://gerrit.instructure.com/174980 Tested-by: Jenkins QA-Review: Tucker Mcknight <tmcknight@instructure.com> Product-Review: Clay Diffrient <cdiffrient@instructure.com> Reviewed-by: Brent Burgoyne <bburgoyne@instructure.com>
This commit is contained in:
parent
a2a7de4bc1
commit
3938ab7550
|
@ -1,5 +1,45 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`addDomainAction creates an ADD_DOMAIN action when passed a boolean value 1`] = `
|
||||
Object {
|
||||
"payload": "instructure.com",
|
||||
"type": "ADD_DOMAIN",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`addDomainAction creates an ADD_DOMAIN_OPTIMISTIC action when optimistic option is given 1`] = `
|
||||
Object {
|
||||
"payload": "instructure.com",
|
||||
"type": "ADD_DOMAIN_OPTIMISTIC",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`addDomainAction creates an error action if passed a non-string value 1`] = `
|
||||
Object {
|
||||
"error": true,
|
||||
"payload": [Error: Can only set to String values],
|
||||
"type": "ADD_DOMAIN",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`addDomainBulkAction creates a ADD_DOMAIN action when passed an value 1`] = `
|
||||
Object {
|
||||
"payload": Array [
|
||||
"instructure.com",
|
||||
"canvaslms.com",
|
||||
],
|
||||
"type": "ADD_DOMAIN_BULK",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`addDomainBulkAction creates an error action if passed a non-Array value 1`] = `
|
||||
Object {
|
||||
"error": true,
|
||||
"payload": [Error: Can only set to an array of strings],
|
||||
"type": "ADD_DOMAIN_BULK",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`setCspEnabledAction creates a SET_CSP_ENABLED action when passed a boolean value 1`] = `
|
||||
Object {
|
||||
"payload": true,
|
||||
|
|
|
@ -107,3 +107,100 @@ describe('getCspEnabled', () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('addDomainAction', () => {
|
||||
it('creates an ADD_DOMAIN action when passed a boolean value', () => {
|
||||
expect(Actions.addDomainAction('instructure.com')).toMatchSnapshot()
|
||||
})
|
||||
it('creates an error action if passed a non-string value', () => {
|
||||
expect(Actions.addDomainAction(true)).toMatchSnapshot()
|
||||
})
|
||||
it('creates an ADD_DOMAIN_OPTIMISTIC action when optimistic option is given', () => {
|
||||
expect(Actions.addDomainAction('instructure.com', {optimistic: true})).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('addDomain', () => {
|
||||
it('dispatches an optimistic action followed by the final result', () => {
|
||||
const thunk = Actions.addDomain('account', 1, 'instructure.com')
|
||||
const fakeDispatch = jest.fn()
|
||||
const fakeAxios = {
|
||||
post: jest.fn(() => ({
|
||||
then(func) {
|
||||
const fakeResponse = {}
|
||||
func(fakeResponse)
|
||||
}
|
||||
}))
|
||||
}
|
||||
thunk(fakeDispatch, null, {axios: fakeAxios})
|
||||
expect(fakeDispatch).toHaveBeenNthCalledWith(1, {
|
||||
payload: 'instructure.com',
|
||||
type: 'ADD_DOMAIN_OPTIMISTIC'
|
||||
})
|
||||
expect(fakeDispatch).toHaveBeenNthCalledWith(2, {
|
||||
payload: 'instructure.com',
|
||||
type: 'ADD_DOMAIN'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('addDomainBulkAction', () => {
|
||||
it('creates a ADD_DOMAIN action when passed an value', () => {
|
||||
expect(Actions.addDomainBulkAction(['instructure.com', 'canvaslms.com'])).toMatchSnapshot()
|
||||
})
|
||||
it('creates an error action if passed a non-Array value', () => {
|
||||
expect(Actions.addDomainBulkAction('instructure.com')).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('getCurrentWhitelist', () => {
|
||||
it('dispatches a bulk domain action using the effective_whitelist when the whitelist is enabled', () => {
|
||||
const thunk = Actions.getCurrentWhitelist('account', 1)
|
||||
const fakeDispatch = jest.fn()
|
||||
const fakeGetState = () => ({enabled: true})
|
||||
const fakeAxios = {
|
||||
get: jest.fn(() => ({
|
||||
then(func) {
|
||||
const fakeResponse = {
|
||||
data: {
|
||||
enabled: true,
|
||||
current_account_whitelist: ['instructure.com', 'canvaslms.com'],
|
||||
effective_whitelist: ['bridgelms.com']
|
||||
}
|
||||
}
|
||||
func(fakeResponse)
|
||||
}
|
||||
}))
|
||||
}
|
||||
thunk(fakeDispatch, fakeGetState, {axios: fakeAxios})
|
||||
expect(fakeDispatch).toHaveBeenCalledWith({
|
||||
payload: ['bridgelms.com'],
|
||||
type: 'ADD_DOMAIN_BULK'
|
||||
})
|
||||
})
|
||||
|
||||
it('dispatches a bulk domain action using the current_account_whitelist when the whitelist is disabled', () => {
|
||||
const thunk = Actions.getCurrentWhitelist('account', 1)
|
||||
const fakeDispatch = jest.fn()
|
||||
const fakeGetState = () => ({enabled: false})
|
||||
const fakeAxios = {
|
||||
get: jest.fn(() => ({
|
||||
then(func) {
|
||||
const fakeResponse = {
|
||||
data: {
|
||||
enabled: false,
|
||||
current_account_whitelist: ['instructure.com', 'canvaslms.com'],
|
||||
effective_whitelist: ['bridgelms.com']
|
||||
}
|
||||
}
|
||||
func(fakeResponse)
|
||||
}
|
||||
}))
|
||||
}
|
||||
thunk(fakeDispatch, fakeGetState, {axios: fakeAxios})
|
||||
expect(fakeDispatch).toHaveBeenCalledWith({
|
||||
payload: ['instructure.com', 'canvaslms.com'],
|
||||
type: 'ADD_DOMAIN_BULK'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -16,8 +16,14 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {cspEnabled} from '../reducers'
|
||||
import {SET_CSP_ENABLED, SET_CSP_ENABLED_OPTIMISTIC} from '../actions'
|
||||
import {cspEnabled, whitelistedDomains} from '../reducers'
|
||||
import {
|
||||
SET_CSP_ENABLED,
|
||||
SET_CSP_ENABLED_OPTIMISTIC,
|
||||
ADD_DOMAIN,
|
||||
ADD_DOMAIN_OPTIMISTIC,
|
||||
ADD_DOMAIN_BULK
|
||||
} from '../actions'
|
||||
|
||||
describe('cspEnabled', () => {
|
||||
const testMatrix = [
|
||||
|
@ -31,3 +37,37 @@ describe('cspEnabled', () => {
|
|||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('whitelistedDomains', () => {
|
||||
const testMatrix = [
|
||||
[{type: ADD_DOMAIN, payload: 'instructure.com'}, [], ['instructure.com']],
|
||||
[{type: ADD_DOMAIN_OPTIMISTIC, payload: 'instructure.com'}, [], ['instructure.com']],
|
||||
[
|
||||
{type: ADD_DOMAIN_BULK, payload: ['instructure.com', 'bridgelms.com']},
|
||||
['canvaslms.com'],
|
||||
['canvaslms.com', 'instructure.com', 'bridgelms.com']
|
||||
]
|
||||
]
|
||||
it.each(testMatrix)(
|
||||
'with %p action and %p payload the whitelistedDomains state becomes %p',
|
||||
(action, initialState, expectedState) => {
|
||||
expect(whitelistedDomains(initialState, action)).toEqual(expectedState)
|
||||
}
|
||||
)
|
||||
|
||||
it('does not allow duplicate domains with ADD_DOMAIN actions', () => {
|
||||
const action = {type: ADD_DOMAIN, payload: 'instructure.com'}
|
||||
const initialState = ['instructure.com', 'canvaslms.com']
|
||||
expect(whitelistedDomains(initialState, action)).toEqual(['instructure.com', 'canvaslms.com'])
|
||||
})
|
||||
|
||||
it('does not allow duplicates domains with ADD_DOMAIN_BULK actions', () => {
|
||||
const action = {type: ADD_DOMAIN_BULK, payload: ['instructure.com', 'bridgelms.com']}
|
||||
const initialState = ['instructure.com', 'canvaslms.com']
|
||||
expect(whitelistedDomains(initialState, action)).toEqual([
|
||||
'instructure.com',
|
||||
'canvaslms.com',
|
||||
'bridgelms.com'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
|
|
@ -62,3 +62,66 @@ export function getCspEnabled(context, contextId) {
|
|||
dispatch(setCspEnabledAction(response.data.enabled))
|
||||
})
|
||||
}
|
||||
|
||||
export const ADD_DOMAIN = 'ADD_DOMAIN'
|
||||
export const ADD_DOMAIN_BULK = 'ADD_DOMAIN_BULK'
|
||||
export const ADD_DOMAIN_OPTIMISTIC = 'ADD_DOMAIN_OPTIMISTIC'
|
||||
|
||||
export function addDomainAction(domain, opts = {}) {
|
||||
const type = opts.optimistic ? ADD_DOMAIN_OPTIMISTIC : ADD_DOMAIN
|
||||
if (typeof domain !== 'string') {
|
||||
return {
|
||||
type,
|
||||
payload: new Error('Can only set to String values'),
|
||||
error: true
|
||||
}
|
||||
}
|
||||
return {
|
||||
type,
|
||||
payload: domain
|
||||
}
|
||||
}
|
||||
|
||||
export function addDomainBulkAction(domains) {
|
||||
if (!Array.isArray(domains)) {
|
||||
return {
|
||||
type: ADD_DOMAIN_BULK,
|
||||
payload: new Error('Can only set to an array of strings'),
|
||||
error: true
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: ADD_DOMAIN_BULK,
|
||||
payload: domains
|
||||
}
|
||||
}
|
||||
|
||||
export function addDomain(context, contextId, domain) {
|
||||
context = pluralize(context)
|
||||
return (dispatch, getState, {axios}) => {
|
||||
dispatch(addDomainAction(domain, {optimistic: true}))
|
||||
return axios
|
||||
.post(`/api/v1/${context}/${contextId}/csp_settings/domains`, {
|
||||
domain
|
||||
})
|
||||
.then(() => {
|
||||
// This isn't really necessary but since the whitelist is unique,
|
||||
// it doesn't hurt.
|
||||
dispatch(addDomainAction(domain))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function getCurrentWhitelist(context, contextId) {
|
||||
context = pluralize(context)
|
||||
return (dispatch, getState, {axios}) => {
|
||||
const {enabled} = getState()
|
||||
return axios.get(`/api/v1/${context}/${contextId}/csp_settings`).then(response => {
|
||||
dispatch(
|
||||
addDomainBulkAction(
|
||||
response.data[enabled ? 'effective_whitelist' : 'current_account_whitelist']
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,8 +24,10 @@ import Heading from '@instructure/ui-elements/lib/components/Heading'
|
|||
import Text from '@instructure/ui-elements/lib/components/Text'
|
||||
import View from '@instructure/ui-layout/lib/components/View'
|
||||
import Checkbox from '@instructure/ui-forms/lib/components/Checkbox'
|
||||
import Grid, {GridCol, GridRow} from '@instructure/ui-layout/lib/components/Grid'
|
||||
|
||||
import {getCspEnabled, setCspEnabled} from '../actions'
|
||||
import {getCspEnabled, setCspEnabled, getCurrentWhitelist} from '../actions'
|
||||
import {ConnectedWhitelist} from './Whitelist'
|
||||
|
||||
export class SecurityPanel extends Component {
|
||||
static propTypes = {
|
||||
|
@ -33,7 +35,8 @@ export class SecurityPanel extends Component {
|
|||
contextId: string.isRequired,
|
||||
cspEnabled: bool.isRequired,
|
||||
getCspEnabled: func.isRequired,
|
||||
setCspEnabled: func.isRequired
|
||||
setCspEnabled: func.isRequired,
|
||||
getCurrentWhitelist: func.isRequired
|
||||
}
|
||||
|
||||
handleCspToggleChange = e => {
|
||||
|
@ -42,6 +45,7 @@ export class SecurityPanel extends Component {
|
|||
|
||||
componentDidMount() {
|
||||
this.props.getCspEnabled(this.props.context, this.props.contextId)
|
||||
this.props.getCurrentWhitelist(this.props.context, this.props.contextId)
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -60,12 +64,23 @@ export class SecurityPanel extends Component {
|
|||
)}
|
||||
</Text>
|
||||
</View>
|
||||
<Checkbox
|
||||
variant="toggle"
|
||||
label={I18n.t('Enable Content Security Policy')}
|
||||
onChange={this.handleCspToggleChange}
|
||||
checked={this.props.cspEnabled}
|
||||
/>
|
||||
<Grid>
|
||||
<GridRow>
|
||||
<GridCol>
|
||||
<Checkbox
|
||||
variant="toggle"
|
||||
label={I18n.t('Enable Content Security Policy')}
|
||||
onChange={this.handleCspToggleChange}
|
||||
checked={this.props.cspEnabled}
|
||||
/>
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
<GridRow>
|
||||
<GridCol>
|
||||
<ConnectedWhitelist context={this.props.context} contextId={this.props.contextId} />
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
</Grid>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -80,7 +95,8 @@ function mapStateToProps(state, ownProps) {
|
|||
|
||||
const mapDispatchToProps = {
|
||||
getCspEnabled,
|
||||
setCspEnabled
|
||||
setCspEnabled,
|
||||
getCurrentWhitelist
|
||||
}
|
||||
|
||||
export const ConnectedSecurityPanel = connect(
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright (C) 2018 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, {Component} from 'react'
|
||||
import I18n from 'i18n!security_panel'
|
||||
import {connect} from 'react-redux'
|
||||
import {arrayOf, func, oneOf, string} from 'prop-types'
|
||||
import Heading from '@instructure/ui-elements/lib/components/Heading'
|
||||
import TextInput from '@instructure/ui-forms/lib/components/TextInput'
|
||||
import Flex, {FlexItem} from '@instructure/ui-layout/lib/components/Flex'
|
||||
import Button from '@instructure/ui-buttons/lib/components/Button'
|
||||
import IconPlus from '@instructure/ui-icons/lib/Solid/IconPlus'
|
||||
import Table from '@instructure/ui-elements/lib/components/Table'
|
||||
import ScreenReaderContent from '@instructure/ui-a11y/lib/components/ScreenReaderContent'
|
||||
import isValidDomain from 'is-valid-domain'
|
||||
|
||||
import {addDomain} from '../actions'
|
||||
|
||||
const PROTOCOL_REGEX = /^(?:(ht|f)tp(s?)\:\/\/)?/
|
||||
|
||||
export class Whitelist extends Component {
|
||||
static propTypes = {
|
||||
addDomain: func.isRequired,
|
||||
context: oneOf(['course', 'account']).isRequired,
|
||||
contextId: string.isRequired,
|
||||
whitelistedDomains: arrayOf(string).isRequired
|
||||
}
|
||||
|
||||
state = {
|
||||
addDomainInputValue: '',
|
||||
errors: []
|
||||
}
|
||||
|
||||
validateInput = input => {
|
||||
const domainOnly = input.replace(PROTOCOL_REGEX, '')
|
||||
return isValidDomain(domainOnly)
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
if (this.validateInput(this.state.addDomainInputValue)) {
|
||||
this.setState(curState => {
|
||||
this.props.addDomain(this.props.context, this.props.contextId, curState.addDomainInputValue)
|
||||
return {
|
||||
errors: [],
|
||||
addDomainInputValue: ''
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
errors: [
|
||||
{
|
||||
text: I18n.t('Invalid domain'),
|
||||
type: 'error'
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Heading margin="small 0" level="h4" as="h3" border="bottom">
|
||||
{I18n.t('Whitelist (%{count}/%{max})', {
|
||||
count: this.props.whitelistedDomains.length,
|
||||
max: 100
|
||||
})}
|
||||
</Heading>
|
||||
<form
|
||||
onSubmit={e => {
|
||||
e.preventDefault()
|
||||
this.handleSubmit()
|
||||
}}
|
||||
>
|
||||
<Flex>
|
||||
<FlexItem grow shrink padding="0 medium 0 0">
|
||||
<TextInput
|
||||
label={I18n.t('Add Domain')}
|
||||
placeholder="http://somedomain.com"
|
||||
value={this.state.addDomainInputValue}
|
||||
messages={this.state.errors}
|
||||
onChange={e => {
|
||||
this.setState({addDomainInputValue: e.currentTarget.value})
|
||||
}}
|
||||
/>
|
||||
</FlexItem>
|
||||
<FlexItem align={this.state.errors.length ? 'center' : 'end'}>
|
||||
<Button type="submit" margin="0 x-small 0 0" icon={IconPlus}>
|
||||
{I18n.t('Domain')}
|
||||
</Button>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
</form>
|
||||
<Table caption={<ScreenReaderContent>{I18n.t('Whitelisted Domains')}</ScreenReaderContent>}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Domain Name</th>
|
||||
<th scope="col">
|
||||
<ScreenReaderContent>Actions</ScreenReaderContent>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.props.whitelistedDomains.map(domain => (
|
||||
<tr key={domain}>
|
||||
<td>{domain}</td>
|
||||
<td />
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {...ownProps, whitelistedDomains: state.whitelistedDomains}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
addDomain
|
||||
}
|
||||
|
||||
export const ConnectedWhitelist = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Whitelist)
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (C) 2018 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import {fireEvent} from 'react-testing-library'
|
||||
import {ConnectedWhitelist} from '../Whitelist'
|
||||
import {renderWithRedux} from './utils'
|
||||
|
||||
describe('ConnectedWhitelist', () => {
|
||||
it('renders items on the whitelist after they are added', () => {
|
||||
const {getByLabelText, getByText, container} = renderWithRedux(
|
||||
<ConnectedWhitelist context="account" contextId="1" />
|
||||
)
|
||||
|
||||
const domainInput = getByLabelText('Add Domain')
|
||||
fireEvent.input(domainInput, {target: {value: 'instructure.com'}})
|
||||
|
||||
const button = container.querySelector('button')
|
||||
fireEvent.click(button)
|
||||
|
||||
const domainCellEntry = getByText('instructure.com')
|
||||
expect(domainCellEntry).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows an error message when an invalid domain is entered', () => {
|
||||
const {getByLabelText, getByText, container} = renderWithRedux(
|
||||
<ConnectedWhitelist context="account" contextId="1" />
|
||||
)
|
||||
|
||||
const domainInput = getByLabelText('Add Domain')
|
||||
fireEvent.input(domainInput, {target: {value: 'fake'}})
|
||||
|
||||
const button = container.querySelector('button')
|
||||
fireEvent.click(button)
|
||||
|
||||
const errorMessage = getByText('Invalid domain')
|
||||
expect(errorMessage).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows the correct count for the whitelist', () => {
|
||||
const {getByLabelText, getByText, container} = renderWithRedux(
|
||||
<ConnectedWhitelist context="account" contextId="1" />
|
||||
)
|
||||
|
||||
const domainInput = getByLabelText('Add Domain')
|
||||
fireEvent.input(domainInput, {target: {value: 'instructure.com'}})
|
||||
|
||||
const button = container.querySelector('button')
|
||||
fireEvent.click(button)
|
||||
|
||||
const countString = getByText('Whitelist (1/100)')
|
||||
expect(countString).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('clears the input box after a successful submisssion', () => {
|
||||
const {getByLabelText, container} = renderWithRedux(
|
||||
<ConnectedWhitelist context="account" contextId="1" />
|
||||
)
|
||||
|
||||
const domainInput = getByLabelText('Add Domain')
|
||||
fireEvent.input(domainInput, {target: {value: 'instructure.com'}})
|
||||
|
||||
const button = container.querySelector('button')
|
||||
fireEvent.click(button)
|
||||
|
||||
expect(domainInput.getAttribute('value')).toBe('')
|
||||
})
|
||||
})
|
|
@ -17,7 +17,13 @@
|
|||
*/
|
||||
|
||||
import {combineReducers} from 'redux'
|
||||
import {SET_CSP_ENABLED, SET_CSP_ENABLED_OPTIMISTIC} from './actions'
|
||||
import {
|
||||
SET_CSP_ENABLED,
|
||||
SET_CSP_ENABLED_OPTIMISTIC,
|
||||
ADD_DOMAIN,
|
||||
ADD_DOMAIN_OPTIMISTIC,
|
||||
ADD_DOMAIN_BULK
|
||||
} from './actions'
|
||||
|
||||
export function cspEnabled(state = false, action) {
|
||||
switch (action.type) {
|
||||
|
@ -29,6 +35,24 @@ export function cspEnabled(state = false, action) {
|
|||
}
|
||||
}
|
||||
|
||||
export function whitelistedDomains(state = [], action) {
|
||||
switch (action.type) {
|
||||
case ADD_DOMAIN:
|
||||
case ADD_DOMAIN_OPTIMISTIC: {
|
||||
const domains = new Set(state)
|
||||
domains.add(action.payload)
|
||||
return Array.from(domains)
|
||||
}
|
||||
case ADD_DOMAIN_BULK: {
|
||||
const domains = new Set(state.concat(action.payload))
|
||||
return Array.from(domains)
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
cspEnabled
|
||||
cspEnabled,
|
||||
whitelistedDomains
|
||||
})
|
||||
|
|
|
@ -22,7 +22,8 @@ import rootReducer from './reducers'
|
|||
import axios from 'axios'
|
||||
|
||||
export const defaultState = {
|
||||
cspEnabled: false
|
||||
cspEnabled: false,
|
||||
whitelistedDomains: []
|
||||
}
|
||||
|
||||
export function configStore(initialState, options = {}) {
|
||||
|
|
|
@ -66,6 +66,7 @@
|
|||
"ic-tabs": "0.1.3",
|
||||
"immutability-helper": "^2",
|
||||
"immutable": "^3.8.2",
|
||||
"is-valid-domain": "^0.0.6",
|
||||
"jquery": "https://github.com/ryankshaw/jquery.git#a755a3e9c99d5a70d8ea570836f94ae1ba56046d",
|
||||
"jquery-getscrollbarwidth": "^1.0.0",
|
||||
"jquery-ui-touch-punch": "^0.2.3",
|
||||
|
|
|
@ -9835,6 +9835,11 @@ is-utf8@^0.2.0, is-utf8@^0.2.1:
|
|||
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
||||
integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
|
||||
|
||||
is-valid-domain@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/is-valid-domain/-/is-valid-domain-0.0.6.tgz#97f8ec909cffd21f795667029587d73354fd6412"
|
||||
integrity sha512-XXiNRcLcNKeb0LB3PzB39gJa8QiA+6nnc4NX9zNvFQcaITWU+64hfVqaVppbSd3tSVlJttW6sINkX3xLKPax7A==
|
||||
|
||||
is-whitespace-character@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz#ede53b4c6f6fb3874533751ec9280d01928d03ed"
|
||||
|
|
Loading…
Reference in New Issue