Skip to content

Commit

Permalink
Save user preferences to database
Browse files Browse the repository at this point in the history
  • Loading branch information
its-a-feature committed Nov 15, 2024
1 parent 05ed8b1 commit 51cf1c9
Show file tree
Hide file tree
Showing 36 changed files with 635 additions and 500 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.3.1-rc23] - 2024-11-14

### Changed

- Added a new field to file browser responses, `set_as_user_output` (bool) to indicate that this structured data should be turned into JSON and set as user_output data by Mythic
- This allows agents to send file browser data once, but get it counted for both the file browser and user_output
- Added endpoints to leverage/set user preferences

## [3.3.1-rc22] -2024-11-6

### Changed
Expand Down
8 changes: 8 additions & 0 deletions MythicReactUI/CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.55] - 2024-11-14

### Changed

- Updated the string display dialog for browser script tables to match other text fields
- Updated user preferences to be stored outside of local storage
- Updated Tooltip from MUI to react-tooltip to speed things up a bit

## [0.2.54] - 2024-11-8

### Changed
Expand Down
21 changes: 21 additions & 0 deletions MythicReactUI/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions MythicReactUI/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"react-scrollbar-size": "^5.0.0",
"react-split": "^2.0.14",
"react-toastify": "^9.1.3",
"react-tooltip": "^5.28.0",
"react-virtualized": "^9.22.5",
"react-virtualized-auto-sizer": "^1.0.24",
"react-window": "^1.8.10",
Expand Down
20 changes: 20 additions & 0 deletions MythicReactUI/src/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@ import {snackActions} from "./components/utilities/Snackbar";

export const meState = makeVar({loggedIn:false, user: null, access_token: null, refresh_token: null});
export const menuOpen = makeVar(false);
export const operatorSettingDefaults = {
fontSize: 12,
fontFamily: "Verdana",
topColor: "#3c4d67",
showMedia: true,
hideUsernames: false,
showIP: false,
showHostname: false,
showCallbackGroups: false,
useDisplayParamsForCLIHistory: true,
interactType: "interact",
callbacks_table_columns: ["Interact", "Host", "Domain", "User", "Description", "Last Checkin", "Agent", "IP", "PID"],
callbacks_table_filters: {},
autoTaskLsOnEmptyDirectories: false,
["experiment-responseStreamLimit"]: 50,

}
export const mePreferences = makeVar(operatorSettingDefaults);


export const successfulLogin = (data) => {
localStorage.setItem("access_token", data.access_token);
Expand Down Expand Up @@ -41,6 +60,7 @@ export const FailedRefresh = () =>{
refresh_token: null,
user: null
});
mePreferences(operatorSettingDefaults);
snackActions.clearAll();
restartWebsockets();
}
Expand Down
64 changes: 42 additions & 22 deletions MythicReactUI/src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ import { SingleTaskView } from './pages/SingleTaskView/SingleTaskView';
import { createTheme, ThemeProvider, StyledEngineProvider, adaptV4Theme } from '@mui/material/styles';
import { GlobalStyles } from '../themes/GlobalStyles';
import CssBaseline from '@mui/material/CssBaseline';
import { FailedRefresh, meState } from '../cache';
import {FailedRefresh, mePreferences, meState} from '../cache';
import { Reporting } from './pages/Reporting/Reporting';
import { MitreAttack } from './pages/MITRE_ATTACK/MitreAttack';
import {Tags} from './pages/Tags/Tags';
import { Tooltip } from 'react-tooltip';
import {useQuery, gql } from '@apollo/client';
//background-color: #282c34;
import { Route, Routes } from 'react-router-dom';
import { useInterval } from './utilities/Time';
Expand All @@ -35,29 +37,23 @@ import { ToastContainer } from 'react-toastify';
import "react-toastify/dist/ReactToastify.css";
import {Eventing} from "./pages/Eventing/Eventing";
import {InviteForm} from "./pages/Login/InviteForm";
import {snackActions} from "./utilities/Snackbar";

const userSettingsQuery = gql`
query getUserSettings {
getOperatorPreferences {
status
error
preferences
}
}
`;


export function App(props) {
const me = useReactiveVar(meState);
const preferences = useReactiveVar(mePreferences);
const [themeMode, themeToggler] = useDarkMode();
const localStorageFontSize = localStorage.getItem(`${me?.user?.user_id || 0}-fontSize`);
const initialLocalStorageFontSizeValue = localStorageFontSize === null ? 12 : parseInt(localStorageFontSize);
const localStorageFontFamily = localStorage.getItem(`${me?.user?.user_id || 0}-fontFamily`);
const initialLocalStorageFontFamilyValue = localStorageFontFamily === null ? [
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
].join(',') : localStorageFontFamily;
const localStorageTopColor = localStorage.getItem(`${me?.user?.user_id || 0}-topColor`);
const initialLocalStorageTopColorValue = localStorageTopColor === null ? "#3c4d67" : localStorageTopColor;
const theme = React.useMemo(
() =>
createTheme(adaptV4Theme({
Expand Down Expand Up @@ -122,16 +118,35 @@ export function App(props) {
pageHeaderText: {
main: 'white',
},
topAppBarColor: initialLocalStorageTopColorValue,
topAppBarColor: preferences?.topColor,
typography: {
fontSize: initialLocalStorageFontSizeValue,
fontFamily: initialLocalStorageFontFamilyValue
fontSize: preferences?.fontSize,
fontFamily: preferences?.fontFamily
},
})),
[themeMode]
[themeMode, preferences.topColor, preferences.fontSize, preferences.fontFamily]
);
const mountedRef = React.useRef(true);
const [openRefreshDialog, setOpenRefreshDialog] = React.useState(false);
const [loadingPreference, setLoadingPreferences] = React.useState(true);
useQuery(userSettingsQuery, {
onCompleted: (data) => {
//console.log("got preferences", data.getOperatorPreferences.preferences)
if(data.getOperatorPreferences.status === "success"){
if(data.getOperatorPreferences.preferences !== null){
mePreferences(data.getOperatorPreferences.preferences);
}
} else {
snackActions.error(`Failed to get user preferences:\n${data.getOperatorPreferences.error}`);
}
setLoadingPreferences(false);
},
onError: (error) => {
console.log(error);
snackActions.error(error.message);
setLoadingPreferences(false);
}
})
useInterval( () => {
// interval should run every 10 minutes (600000 milliseconds) to check JWT status
let millisecondsLeft = JWTTimeLeft();
Expand All @@ -145,11 +160,16 @@ export function App(props) {
}
}
}, 600000, mountedRef, mountedRef);
if(loadingPreference){
// make sure we've loaded preferences before loading actual app content
return null
}
return (
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<GlobalStyles theme={theme} />
<CssBaseline />
<Tooltip id={"my-tooltip"} style={{zIndex: 100000}}/>
<ToastContainer limit={2} autoClose={3000}
theme={themeMode}
style={{maxWidth: "100%", minWidth: "40%", width: "40%", marginTop: "20px", display: "flex", flexWrap: "wrap",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,16 @@
import React from 'react';
import Button from '@mui/material/Button';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import {MythicDialog} from './MythicDialog';
import {MythicDialog, MythicModifyStringDialog} from './MythicDialog';

export function MythicDisplayTextDialog(props) {
return (
<MythicDialog fullWidth={props.fullWidth === null ? false : props.fullWidth} maxWidth={props.maxWidth === null ? "sm" : props.maxWidth} open={props.open} onClose={()=>{props.onClose()}} innerDialog={
<React.Fragment>
<DialogTitle id="form-dialog-title">{props.title}</DialogTitle>
<DialogContent dividers={true}>
<pre style={{whiteSpace: "pre-wrap"}}>
{props.value}
</pre>
</DialogContent>
<DialogActions>
<Button onClick={props.onClose} variant="contained" color="primary">
Close
</Button>
</DialogActions>
</React.Fragment>
<MythicDialog fullWidth={props.fullWidth === null ? false : props.fullWidth}
maxWidth={props.maxWidth === null ? "sm" : props.maxWidth}
open={props.open}
onClose={()=>{props.onClose()}} innerDialog={
<MythicModifyStringDialog title={props.title}
onClose={props.onClose}
value={props.value}
maxRows={20} />
} />
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {classes} from './styles';
import ClickAwayListener from '@mui/material/ClickAwayListener';
import {Dropdown, DropdownMenuItem, DropdownNestedMenuItem} from "../MythicNestedMenus";

const CellPreMemo = ({ VariableSizeGridProps: { style, rowIndex, columnIndex, data } }) => {
const CellPreMemo = ({ style, rowIndex, columnIndex, data }) => {
const [openContextMenu, setOpenContextMenu] = React.useState(false);
const rowClassName = data.gridUUID + "row" + rowIndex;
const [contextMenuOptions, setContextMenuOptions] = React.useState(data?.rowContextMenuOptions || []);
Expand Down
Loading

0 comments on commit 51cf1c9

Please sign in to comment.