Skip to content

Commit

Permalink
Jetpack: Add block and allow list toggles to WAF settings (#38267)
Browse files Browse the repository at this point in the history
* Add individual controls for toggling the IP block and allow lists.

* Add block and allow list toggles

* Add changelog entry

* Reorganize

* Further rearranging

* Fixes and improvements

* Ensure allow list toggle is off when main settings are disabled

* Fix disabled logic

* Remove isModuleFound import

* Remove unnecessary logic

* Remove missed logic

---------

Co-authored-by: Nate Weller <[email protected]>
  • Loading branch information
dkmyta and nateweller authored Jul 19, 2024
1 parent aef6b3a commit 187b7a0
Show file tree
Hide file tree
Showing 11 changed files with 369 additions and 329 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
}

.dops-card {
padding-right: rem( 48px );
padding-right: rem( 72px );
}

.jp-support-info {
Expand Down
248 changes: 248 additions & 0 deletions projects/plugins/jetpack/_inc/client/security/allowList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import { ToggleControl } from '@automattic/jetpack-components';
import { __, _x, sprintf } from '@wordpress/i18n';
import Button from 'components/button';
import { FormFieldset } from 'components/forms';
import { createNotice, removeNotice } from 'components/global-notices/state/notices/actions';
import { withModuleSettingsFormHelpers } from 'components/module-settings/with-module-settings-form-helpers';
import SettingsCard from 'components/settings-card';
import SettingsGroup from 'components/settings-group';
import { includes } from 'lodash';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import QueryWafSettings from '../components/data/query-waf-bootstrap-path';
import Textarea from '../components/textarea';
import { updateWafSettings } from '../state/waf/actions';
import { getWafSettings, isFetchingWafSettings, isUpdatingWafSettings } from '../state/waf/reducer';

const AllowList = class extends Component {
/**
* Get options for initial state.
*
* @returns {object}
*/
state = {
ipAllowListEnabled: this.props.settings?.ipAllowListEnabled,
ipAllowList: this.props.settings?.ipAllowList,
};

/**
* Keep the form values in sync with updates to the settings prop.
*
* @param {object} prevProps - Next render props.
*/
componentDidUpdate = prevProps => {
// Sync the form values with the settings prop.
if ( this.props.settings !== prevProps.settings ) {
this.setState( {
...this.state,
ipAllowListEnabled: this.props.settings?.ipAllowListEnabled,
ipAllowList: this.props.settings?.ipAllowList,
} );
}
};

/**
* Handle settings updates.
*
* @returns {void}
*/
onSubmit = () => {
this.props.removeNotice( 'module-setting-update' );
this.props.removeNotice( 'module-setting-update-success' );

this.props.createNotice( 'is-info', __( 'Updating settings…', 'jetpack' ), {
id: 'module-setting-update',
} );

this.props
.updateWafSettings( this.state )
.then( () => {
this.props.removeNotice( 'module-setting-update' );
this.props.createNotice( 'is-success', __( 'Updated Settings.', 'jetpack' ), {
id: 'module-setting-update-success',
} );
} )
.catch( error => {
this.props.removeNotice( 'module-setting-update' );
this.props.createNotice(
'is-error',
sprintf(
/* translators: placeholder is an error code or an error message. */
__( 'Error updating settings. %s', 'jetpack' ),
error.message || error.code
),
{
id: 'module-setting-update',
}
);
} );
};

/**
* Toggle IP allow list.
*/
toggleIpAllowList = () => {
this.setState(
{ ...this.state, ipAllowListEnabled: ! this.state.ipAllowListEnabled },
this.onSubmit
);
};

/**
* Handle IP allow list change.
*
* @param {Event} event - = The event object.
*/
handleIpAllowListChange = event => {
this.setState( { ...this.state, ipAllowList: event?.target?.value } );
};

currentIpIsSafelisted = () => {
// get current IP allow list in textarea from this.state.ipAllowList;
return !! includes( this.state.ipAllowList, this.props.currentIp );
};

addToSafelist = () => {
const newAllowList =
this.state.ipAllowList +
( 0 >= this.state.ipAllowList.length ? '' : '\n' ) +
this.props.currentIp;

// Update the allow list
this.setState( { ...this.state, ipAllowList: newAllowList } );
};

render() {
const isWafActive = this.props.getOptionValue( 'waf' );
const isProtectActive = this.props.getOptionValue( 'protect' );
const wafUnavailableInOfflineMode = this.props.isUnavailableInOfflineMode( 'waf' );
const protectUnavailableInOfflineMode = this.props.isUnavailableInOfflineMode( 'protect' );
const baseInputDisabledCase =
( ! isWafActive && ! isProtectActive ) ||
( wafUnavailableInOfflineMode && protectUnavailableInOfflineMode ) ||
this.props.isFetchingWafSettings ||
this.props.isSavingAnyOption( [ 'waf' ] );

const moduleHeader = (
<div className="waf__header">
<span>{ _x( 'Always allowed IP addresses', 'Settings header', 'jetpack' ) }</span>
</div>
);

return (
<SettingsCard
{ ...this.props }
header={ moduleHeader }
module="waf"
onSubmit={ this.onSubmit }
hideButton={ true }
>
{ ( isWafActive || isProtectActive ) && <QueryWafSettings /> }
<SettingsGroup
disableInOfflineMode
support={ {
text: "Adding an IP address to the allow list will prevent it from being blocked by Jetpack's firewall and brute force protection features.",
link: this.props.getModule( 'waf' ).learn_more_button,
} }
>
<FormFieldset>
<div className="waf__settings__toggle-setting">
<ToggleControl
checked={
( isWafActive || isProtectActive ) && this.props.settings?.ipAllowListEnabled
}
toggling={
this.props.isUpdatingWafSettings &&
this.state.ipAllowListEnabled !== this.props.settings?.ipAllowListEnabled
}
disabled={ baseInputDisabledCase }
onChange={ this.toggleIpAllowList }
label={
<span className="jp-form-toggle-explanation">
{ __(
"Prevent Jetpack's security features from blocking specific IP addresses",
'jetpack'
) }
</span>
}
/>
<div className="waf__settings__ips">
<Textarea
disabled={
baseInputDisabledCase ||
this.props.isUpdatingWafSettings ||
! this.props.settings?.ipAllowListEnabled
}
name="ipAllowList"
placeholder={ __( 'Example:', 'jetpack' ) + '\n12.12.12.1\n12.12.12.2' }
value={ this.state.ipAllowList }
onChange={ this.handleIpAllowListChange }
/>
<div className="allow-list-button-container">
{ this.props.currentIp && (
<div className="current-ip">
<div className="jp-form-label-wide">
{ sprintf(
/* translators: placeholder is an IP address. */
__( 'Your current IP: %s', 'jetpack' ),
this.props.currentIp
) }
</div>
{
<Button
rna
compact
disabled={
this.props.isUpdatingWafSettings ||
! this.props.settings?.ipAllowListEnabled ||
this.currentIpIsSafelisted() ||
this.props.isSavingAnyOption( [ 'jetpack_waf_ip_allow_list' ] )
}
onClick={ this.addToSafelist }
>
{ __( 'Add to Allow List', 'jetpack' ) }
</Button>
}
</div>
) }
<Button
primary
rna
compact
type="button"
className="waf__settings__ips__save-button"
disabled={
this.state.ipAllowList === this.props.settings?.ipAllowList ||
( this.props.isUpdatingWafSettings &&
this.state.ipAllowList !== this.props.settings?.ipAllowList )
}
onClick={ this.onSubmit }
>
{ __( 'Save allow list', 'jetpack' ) }
</Button>
</div>
</div>
</div>
</FormFieldset>
</SettingsGroup>
</SettingsCard>
);
}
};

export default connect(
state => {
return {
isFetchingSettings: isFetchingWafSettings( state ),
isUpdatingWafSettings: isUpdatingWafSettings( state ),
settings: getWafSettings( state ),
};
},
dispatch => {
return {
updateWafSettings: newSettings => dispatch( updateWafSettings( newSettings ) ),
createNotice: ( type, message, props ) => dispatch( createNotice( type, message, props ) ),
removeNotice: notice => dispatch( removeNotice( notice ) ),
};
}
)( withModuleSettingsFormHelpers( AllowList ) );
2 changes: 2 additions & 0 deletions projects/plugins/jetpack/_inc/client/security/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { isModuleFound } from 'state/search';
import { getSettings } from 'state/settings';
import { siteHasFeature } from 'state/site';
import { isPluginActive, isPluginInstalled } from 'state/site/plugins';
import AllowList from './allowList';
import Antispam from './antispam';
import BackupsScan from './backups-scan';
import { JetpackBackup } from './jetpack-backup';
Expand Down Expand Up @@ -111,6 +112,7 @@ export class Security extends Component {
) }
{ foundWaf && <Waf { ...commonProps } /> }
{ foundProtect && <Protect { ...commonProps } /> }
{ ( foundWaf || foundProtect ) && <AllowList { ...commonProps } /> }
{ foundSso && <SSO { ...commonProps } /> }
</div>
);
Expand Down
Loading

0 comments on commit 187b7a0

Please sign in to comment.