File "index.js"
Full Path: /home/siazco/grocery.siazco.se/wp-content/plugins/better-wp-security/core/admin-pages/entries/firewall/components/rule-form/index.js
File size: 11.39 KB
MIME-type: text/x-java
Charset: utf-8
/**
* External dependencies
*/
import { map } from 'lodash';
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import {
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalInputControl as InputControl,
BaseControl,
Flex,
FlexBlock,
FlexItem,
VisuallyHidden,
} from '@wordpress/components';
import { useInstanceId, useViewportMatch } from '@wordpress/compose';
import { closeSmall as removeIcon } from '@wordpress/icons';
/**
* Solid dependencies
*/
import { Heading, TextWeight } from '@ithemes/ui';
/**
* Internal dependencies
*/
import { Select, CreatableSelect } from '@ithemes/security-ui';
import { StyledRuleAction, halfFlexBasis } from './styles';
const DEFAULT = {
inclusive: true,
};
export default function RuleForm( { value, onChange, className } ) {
const id = useInstanceId( RuleForm, 'solid-rule-form' );
const { config = { rules: [ DEFAULT ] } } = value;
const onAndRule = ( after ) => () => {
onChange( {
...value,
config: {
...config,
rules: config.rules.toSpliced( after + 1, 0, DEFAULT ),
},
} );
};
return (
<Flex direction="column" gap={ 4 } align="stretch" expanded={ false } className={ className }>
<InputControl
value={ value.name ?? '' }
onChange={ ( next ) => onChange( { ...value, name: next } ) }
label={ __( 'Rule Name', 'better-wp-security' ) }
required
__next36pxDefaultSize
/>
<Heading level={ 3 } text={ __( 'If incoming requests match…', 'better-wp-security' ) } weight={ TextWeight.HEAVY } />
<Flex direction="column" gap={ 3 } align="stretch" expanded={ false }>
{ config.rules.map( ( rule, i ) => (
<Rule
key={ i }
idx={ i }
value={ rule }
onAndRule={ onAndRule( i ) }
onChange={ ( newRule ) => onChange( {
...value,
config: {
...config,
rules: config.rules.map( ( oldRule, j ) => j === i ? newRule : oldRule ),
},
} ) }
onDelete={ config.rules.length === 1 ? null : () => onChange( {
...value,
config: {
...config,
rules: config.rules.toSpliced( i, 1 ),
},
} ) }
/>
) ) }
</Flex>
<Heading level={ 3 } text={ __( 'Then take action…', 'better-wp-security' ) } weight={ TextWeight.HEAVY } />
<Flex direction="column" gap={ 3 } align="stretch" expanded={ false }>
<BaseControl
id={ id + '__action' }
label={ __( 'Action', 'better-wp-security' ) }
__nextHasNoMarginBottom
>
<Select
inputId={ id + '__action' }
options={ ACTIONS }
value={ ACTIONS.find( ( action ) => action.value === config.type ) }
onChange={ ( next ) => onChange( {
...value,
config: {
...config,
type: next.value,
type_params: '',
},
} ) }
/>
</BaseControl>
{ config.type === 'REDIRECT' && (
<InputControl
value={ config.type_params ?? '' }
onChange={ ( next ) => onChange( {
...value,
config: {
...config,
type_params: next,
},
} ) }
type="url"
label={ __( 'Redirect Location', 'better-wp-security' ) }
__next36pxDefaultSize
/>
) }
</Flex>
</Flex>
);
}
function Rule( { idx, value, onChange, onDelete, onAndRule } ) {
const isLarge = useViewportMatch( 'large' );
const id = useInstanceId( Rule, 'solid-rule-form-rule' );
const selectedField = value.parameter &&
FIELDS.find( ( field ) => isField( value.parameter, field ) );
const allowedOperators = OPERATORS
.filter( ( operator ) => selectedField?.operators === true || selectedField?.operators.includes( operator.value ) );
const selectedOperator = value.match?.type &&
allowedOperators.find( ( operator ) => operator.value === value.match?.type );
return (
<fieldset>
<VisuallyHidden as="legend">
{ sprintf(
/* translators: Which number rule is this in the list. */
__( 'Rule %d', 'better-wp-security' ), idx + 1
) }
</VisuallyHidden>
<Flex
gap={ 1 }
align={ isLarge ? 'start' : 'stretch' }
direction={ isLarge ? 'row' : 'column' }
expanded={ isLarge }
>
<FlexBlock>
<Flex align="start" gap={ 1 }>
<FlexBlock className={ halfFlexBasis }>
<FieldControl id={ id } field={ selectedField } value={ value } onChange={ onChange } />
</FlexBlock>
{ selectedField?.allowSubFields && (
<FlexItem className={ halfFlexBasis }>
<SubFieldControl field={ selectedField } value={ value } onChange={ onChange } />
</FlexItem>
) }
</Flex>
</FlexBlock>
<FlexBlock>
<OperatorControl
id={ id }
operator={ selectedOperator }
allowedOperators={ allowedOperators }
value={ value }
onChange={ onChange }
/>
</FlexBlock>
<FlexBlock>
<ValueControl
id={ id }
field={ selectedField }
operator={ selectedOperator }
value={ value }
onChange={ onChange }
/>
</FlexBlock>
<FlexItem>
<Flex gap={ 1 } justify="start">
<StyledRuleAction
onClick={ onAndRule }
variant="secondary"
text={ __( 'And', 'better-wp-security' ) }
/>
{ onDelete && (
<StyledRuleAction
onClick={ onDelete }
variant="tertiary"
icon={ removeIcon }
label={ __( 'Remove', 'better-wp-security' ) }
/>
) }
</Flex>
</FlexItem>
</Flex>
</fieldset>
);
}
function FieldControl( { id, field, value, onChange } ) {
return (
<BaseControl
id={ id + '__field' }
label={ __( 'Field', 'better-wp-security' ) }
help={ __( 'Select a field to inspect.', 'better-wp-security' ) }
__nextHasNoMarginBottom
>
<Select
inputId={ id + '__field' }
key={ field?.value }
options={ FIELDS }
value={ field }
onChange={ ( next ) => onChange( {
...value,
parameter: next.value,
match: {
type: 'equals',
},
} ) }
isOptionSelected={ ( maybeOption, selected ) => selected.some( ( selectedOption ) => isField( maybeOption.value, selectedOption ) ) }
required
/>
</BaseControl>
);
}
function SubFieldControl( { field, value, onChange } ) {
const { example, sanitize, display } = field.allowSubFields;
return (
<InputControl
label={ __( 'Name', 'better-wp-security' ) }
help={ sprintf(
/* translators: 1. Example value. */
__( 'e.g. %s', 'better-wp-security' ),
example
) }
value={ display( value.parameter?.replace( field.value, '' ) ?? '' ) }
onChange={ ( next ) => onChange( {
...value,
parameter: field.value + sanitize( next ),
} ) }
required
__next36pxDefaultSize
/>
);
}
function OperatorControl( { id, operator, allowedOperators, value, onChange } ) {
return (
<BaseControl
id={ id + '__operator' }
label={ __( 'Operator', 'better-wp-security' ) }
__nextHasNoMarginBottom
>
<Select
inputId={ id + '__operator' }
options={ allowedOperators }
value={ operator }
onChange={ ( next ) => onChange( {
...value,
match: {
...( value.match || {} ),
type: next.value,
value: ( () => {
const current = value.match?.value;
if ( ! current ) {
return next.isList ? [] : '';
}
if ( next.isList ) {
return Array.isArray( current ) ? current : [ current ];
}
return Array.isArray( current ) ? current[ 0 ] : current;
} )(),
},
} ) }
isDisabled={ ! allowedOperators.length }
required
/>
</BaseControl>
);
}
function ValueControl( { id, field, operator, value, onChange } ) {
if ( operator?.isList ) {
return (
<BaseControl
id={ id + '__value' }
label={ __( 'Value', 'better-wp-security' ) }
__nextHasNoMarginBottom
>
<CreatableSelect
inputId={ id + '__value' }
key={ field?.value }
options={ field?.listOptions?.map( ( option ) => ( {
value: option,
label: option,
} ) ) }
value={ value.match?.value?.map( ( option ) => ( {
value: option,
label: option,
} ) ) }
onChange={ ( next ) => onChange( {
...value,
match: {
...( value.match || {} ),
value: map( next, 'value' ),
},
} ) }
isMulti
isClearable
required
/>
</BaseControl>
);
}
if ( field?.listOptions ) {
return (
<BaseControl
id={ id + '__value' }
label={ __( 'Value', 'better-wp-security' ) }
__nextHasNoMarginBottom
>
<CreatableSelect
inputId={ id + '__value' }
key={ field?.value }
options={ field?.listOptions?.map( ( option ) => ( {
value: option,
label: option,
} ) ) }
value={ { value: value.match?.value ?? '', label: value.match?.value ?? '' } }
onChange={ ( next ) => onChange( {
...value,
match: {
...( value.match || {} ),
value: next.value,
},
} ) }
isClearable
required
/>
</BaseControl>
);
}
return (
<InputControl
label={ __( 'Value', 'better-wp-security' ) }
help={ field?.example && sprintf(
/* translators: 1. Example value. */
__( 'e.g. %s', 'better-wp-security' ),
field.example
) }
value={ value.match?.value }
onChange={ ( next ) => onChange( {
...value,
match: {
...( value.match || {} ),
value: next,
},
} ) }
disabled={ ! field }
required
__next36pxDefaultSize
/>
);
}
function isField( value, maybeField ) {
if ( maybeField.value === value ) {
return true;
}
if ( maybeField.allowSubFields && value.startsWith( maybeField.value ) ) {
return true;
}
return false;
}
const FIELDS = [
{
value: 'server.REQUEST_URI',
label: __( 'URI', 'better-wp-security' ),
operators: [ 'equals', 'contains', 'not_contains' ],
example: '/test?param=value',
},
{
value: 'server.REQUEST_METHOD',
label: __( 'Request Method', 'better-wp-security' ),
operators: true,
listOptions: [
'GET',
'HEAD',
'POST',
'PUT',
'PATCH',
'DELETE',
'OPTIONS',
],
},
{
value: 'server.CONTENT_TYPE',
label: __( 'Content Type', 'better-wp-security' ),
operators: true,
},
{
value: 'server.HTTP_',
label: __( 'Header', 'better-wp-security' ),
operators: true,
allowSubFields: {
example: 'user-agent',
sanitize( value ) {
return value.toUpperCase().replace( '-', '_' );
},
display( value ) {
return value.toLowerCase().replace( '_', '-' );
},
},
},
{
value: 'cookie.',
label: __( 'Cookie', 'better-wp-security' ),
operators: true,
allowSubFields: {
example: 'my-cookie',
sanitize( value ) {
return value.replace( '.', '_' );
},
display( value ) {
return value;
},
},
},
{
value: 'server.ip',
label: __( 'IP Address' ),
operators: [ 'equals', 'in_array', 'not_in_array' ],
example: '127.0.0.1',
},
];
const OPERATORS = [
{
value: 'equals',
label: __( 'equals', 'better-wp-security' ),
},
{
value: 'contains',
label: __( 'contains', 'better-wp-security' ),
},
{
value: 'not_contains',
label: __( 'does not contain', 'better-wp-security' ),
},
{
value: 'in_array',
label: __( 'is in', 'better-wp-security' ),
isList: true,
},
{
value: 'not_in_array',
label: __( 'is not in', 'better-wp-security' ),
isList: true,
},
];
const ACTIONS = [
{
value: 'BLOCK',
label: __( 'Block', 'better-wp-security' ),
},
{
value: 'REDIRECT',
label: __( 'Redirect', 'better-wp-security' ),
},
{
value: 'LOG',
label: __( 'Log only', 'better-wp-security' ),
},
{
value: 'WHITELIST',
label: __( 'Allow', 'better-wp-security' ),
},
];