Skip to content

Commit

Permalink
NEW SudoModePasswordField component
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed Feb 3, 2025
1 parent db4002f commit 6313c22
Show file tree
Hide file tree
Showing 12 changed files with 226 additions and 3 deletions.
4 changes: 2 additions & 2 deletions client/dist/js/bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion client/dist/styles/bundle.css

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions client/src/boot/registerComponents.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import PopoverOptionSet from 'components/PopoverOptionSet/PopoverOptionSet';
import ToastsContainer from 'containers/ToastsContainer/ToastsContainer';
import ListboxField from 'components/ListboxField/ListboxField';
import SearchableDropdownField from 'components/SearchableDropdownField/SearchableDropdownField';
import SudoModePasswordField from 'components/SudoModePasswordField/SudoModePasswordField';

export default () => {
Injector.component.registerMany({
Expand Down Expand Up @@ -106,5 +107,6 @@ export default () => {
ToastsContainer,
ListboxField,
SearchableDropdownField,
SudoModePasswordField,
});
};
2 changes: 2 additions & 0 deletions client/src/bundles/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import 'expose-loader?exposes=withDragDropContext!lib/withDragDropContext';
import 'expose-loader?exposes=withRouter!lib/withRouter';
import 'expose-loader?exposes=ssUrlLib!lib/urls';
import 'expose-loader?exposes=SearchableDropdownField!components/SearchableDropdownField/SearchableDropdownField';
import 'expose-loader?exposes=SudoModePasswordField!components/SudoModePasswordField/SudoModePasswordField';

// Legacy CMS
import '../legacy/jquery.changetracker';
Expand Down Expand Up @@ -113,6 +114,7 @@ import '../legacy/ConfirmedPasswordField';
import '../legacy/SelectionGroup';
import '../legacy/DateField';
import '../legacy/ToggleCompositeField';
import '../legacy/SudoModePasswordField/SudoModePasswordFieldEntwine';
import '../legacy/TreeDropdownField/TreeDropdownFieldEntwine';
import '../legacy/UsedOnTable/UsedOnTableEntwine';
import '../legacy/DatetimeField';
Expand Down
109 changes: 109 additions & 0 deletions client/src/components/SudoModePasswordField/SudoModePasswordField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import backend from 'lib/Backend';
import classNames from 'classnames';
import Config from 'lib/Config';
import qs from 'qs';
// import i18n from 'i18n';
// import PropTypes from 'prop-types';
import React, { createRef, useState } from 'react';
// import url from 'url';
import Button from 'components/Button/Button';

function SudoModePasswordField(props) {
const {
className
} = props;

const passwordFieldRef = createRef();
const [responseMessage, setResponseMessage] = useState('');

function reloadPage() {
// Add a ?reload=1 query parameter to the URL to force the browser to reload the page
// window.location.reload() does not work as expected
const query = qs.parse(window.location.search, { ignoreQueryPrefix: true });
const reload = query.reload ? parseInt(query.reload, 10) + 1 : 1;
const hrefNoQuery = window.location.href.split('?')[0];
window.location.href = hrefNoQuery + qs.stringify({ ...query, reload }, { addQueryPrefix: true });
}

async function handleClick() {
const fetcher = backend.createEndpointFetcher({
url: `/${Config.getSection('SilverStripe\\Admin\\SudoModeController').url}/activate`,
method: 'post',
payloadFormat: 'urlencoded',
responseFormat: 'json',
});
const data = {
Password: passwordFieldRef.current.value,
};
const headers = {
'X-SecurityID': Config.get('SecurityID'),
};
const responseJson = await fetcher(data, headers);
if (responseJson.result) {
reloadPage();
} else {
setResponseMessage(responseJson.message);
}
}

function handleKeyDown(evt) {
if (evt.key === 'Enter') {
handleClick();
}
}

// todo i18n _t(), etc
const message = 'This section is protected and is currently in read only mode. Please enter your password to make changes.';

return <div className="panel panel--padded flexbox-area-grow">
<div
className={classNames([
'sudo-mode-password-field',
'alert',
'alert-info',
className,
])}
>
<p>{message}</p>
<div className="sudo-mode-password-field__container">
<div className="sudo-mode-password-field__input-container">
<input
className="form-control no-change-track"
type="password"
ref={passwordFieldRef}
/* todo i18n */
placeholder="Your password"
onKeyDown={(evt) => handleKeyDown(evt)}
/>
</div>
<div>
<Button
className="form-control"
color="info"
onClick={() => handleClick()}
>
Submit
</Button>
</div>
</div>
{{ responseMessage } &&
<p className="sudo-mode-password-field__message">{responseMessage}</p>
}
</div>
</div>;
}

// SearchableDropdownField.propTypes = {
// disabled: PropTypes.bool.isRequired,
// name: PropTypes.string.isRequired,
// };

// SearchableDropdownField.defaultProps = {
// disabled: false,
// SelectComponent: Select,
// };

export { SudoModePasswordField as Component };

// export default fieldHolder(SudoModePasswordField);
export default SudoModePasswordField;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.sudo-mode-password-field {

&__container {
display: flex;
}

&__input-container {
padding-right: 10px;
width: 100%;
max-width: 350px;
}

&__message {
margin-top: 10px;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* global window */
import jQuery from 'jquery';
import React from 'react';
import { createRoot } from 'react-dom/client';
// import { schemaMerge } from 'lib/schemaFieldValues';
import { loadComponent } from 'lib/Injector';

jQuery.entwine('ss', ($) => {
$('.js-injector-boot .SudoModePasswordField').entwine({
Value: null,
Timer: null,
Component: null,
ReactRoot: null,

onmatch() {
this._super();

const cmsContent = this.closest('.cms-content').attr('id');
const context = (cmsContent)
? { context: cmsContent }
: {};

const SudoModePasswordField = loadComponent('SudoModePasswordField', context);
this.setComponent(SudoModePasswordField);

// const state = this.data('state') || {};
// // const schema = this.data('schema') || {};

// this.setValue(state.value ? Number(state.value) : '');

this.refresh();
},

onunmatch() {
this._super();
const root = this.getReactRoot();
if (root) {
root.unmount();
this.setReactRoot(null);
}
},

refresh() {
// const props = this.getAttributes();
const props = {};
const SudoModePasswordField = this.getComponent();

let root = this.getReactRoot();
if (!root) {
root = createRoot(this[0]);
}
root.render(
<SudoModePasswordField
{...props}
// onChange={onChange}
// value={this.getValue()}
// noHolder
/>
);
this.setReactRoot(root);
},

// getAttributes() {
// const state = this.data('state');
// const schema = this.data('schema');
// return schemaMerge(schema, state);
// },
});
});
1 change: 1 addition & 0 deletions client/src/styles/bundle.scss
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
@import "../components/Search/SearchForm";
@import "../components/Search/SearchToggle";
@import "../components/Search/SearchBox";
@import "../components/SudoModePasswordField/SudoModePasswordField";
@import "../components/Tabs/Tabs";
@import "../components/Tag/Tag";
@import "../components/Tag/TagList";
Expand Down
9 changes: 9 additions & 0 deletions code/CMSProfileController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
use SilverStripe\Security\Member;
use SilverStripe\Security\Permission;
use SilverStripe\Security\Security;
use SilverStripe\ORM\ValidationResult;
use Exception;

class CMSProfileController extends LeftAndMain
{
Expand Down Expand Up @@ -57,6 +59,13 @@ public function getEditForm($id = null, $fields = null)

$form->addExtraClass('member-profile-form root-form cms-edit-form center fill-height');

// tree_class must be chanaged to model_class for CMS 6
$dataClass = $this->config()->get('tree_class');
if (!$dataClass) {
// Safety fallback for CMS 6 where tree_class was changed to model_class
throw new Exception('CMSProfileController::tree_class not set');
}

return $form;
}

Expand Down
6 changes: 6 additions & 0 deletions code/LeftAndMain.php
Original file line number Diff line number Diff line change
Expand Up @@ -1540,6 +1540,12 @@ public function getEditForm($id = null, $fields = null)
$readonlyFields = $form->Fields()->makeReadonly();
$form->setFields($readonlyFields);
}

// Sudo mode
if (is_a($record, DataObject::class) && $record->getRequireSudoMode()) {
$form->requireSudoMode();
}

return $form;
}

Expand Down
7 changes: 7 additions & 0 deletions code/ModelAdmin.php
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,13 @@ public function getEditForm($id = null, $fields = null)
$form->setFormAction($editFormAction);
$form->setAttribute('data-pjax-fragment', 'CurrentForm');

// Sudo mode
$list = $this->getList();
$record = Injector::inst()->create($list->dataClass());
if ($record->getRequireSudoMode()) {
$form->requireSudoMode();
}

$this->extend('updateEditForm', $form);

return $form;
Expand Down
2 changes: 2 additions & 0 deletions code/SecurityAdmin.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class SecurityAdmin extends ModelAdmin implements PermissionProvider
],
];

// protected bool $requireSudoMode = true;

/**
* We have to add both the model tab reference and the class name as keys for the importers because ModelAdmin
* currently checks for $importers[$modelClass] in some places and $importers[$this->modelTab] in others.
Expand Down

0 comments on commit 6313c22

Please sign in to comment.