Skip to content

Commit

Permalink
feature(editor) Disable non-owner editing.
Browse files Browse the repository at this point in the history
- `handleStrategies` now short circuits if the project is not owned by the current user.
- `processAction` now only permits certain actions to run depending on the ownership
  of the current project.
  • Loading branch information
seanparsons committed Nov 20, 2023
1 parent 4069e6e commit 30af6b0
Show file tree
Hide file tree
Showing 2 changed files with 194 additions and 155 deletions.
24 changes: 18 additions & 6 deletions editor/src/components/editor/store/dispatch-strategies.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -654,12 +654,24 @@ export function handleStrategies(
if (MeasureDispatchTime) {
window.performance.mark('strategies_begin')
}
const { unpatchedEditorState, patchedEditorState, newStrategyState } = handleStrategiesInner(
strategies,
dispatchedActions,
storedState,
result,
)
let unpatchedEditorState: EditorState
let patchedEditorState: EditorState
let newStrategyState: StrategyState
if (storedState.projectServerState.isMyProject === 'no') {
unpatchedEditorState = result.unpatchedEditor
patchedEditorState = result.unpatchedEditor
newStrategyState = result.strategyState
} else {
const strategiesResult = handleStrategiesInner(
strategies,
dispatchedActions,
storedState,
result,
)
unpatchedEditorState = strategiesResult.unpatchedEditorState
patchedEditorState = strategiesResult.patchedEditorState
newStrategyState = strategiesResult.newStrategyState
}

const patchedEditorWithMetadata: EditorState = {
...patchedEditorState,
Expand Down
325 changes: 176 additions & 149 deletions editor/src/components/editor/store/dispatch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,168 +105,195 @@ function processAction(
action: EditorAction,
spyCollector: UiJsxCanvasContextData,
): EditorStoreUnpatched {
let shouldProcessAction: boolean = true
// By default when the current user does not own the project and the action is non-transient prevent actions from running...
if (editorStoreUnpatched.projectServerState.isMyProject === 'no' && !isTransientAction(action)) {
// ...Unless they're more critical to the running of the editor in this case.
const allowedNonOwnerAction =
action.action === 'UPDATE_FROM_WORKER' ||
action.action === 'UPDATE_TOP_LEVEL_ELEMENTS_FROM_COLLABORATION_UPDATE'
shouldProcessAction = allowedNonOwnerAction
}
// When the current user does own the project...
if (editorStoreUnpatched.projectServerState.isMyProject === 'yes') {
// ...Disallow these updates as they're coming from non-owners.
shouldProcessAction = action.action !== 'UPDATE_TOP_LEVEL_ELEMENTS_FROM_COLLABORATION_UPDATE'
}

let working = editorStoreUnpatched
// Sidestep around the local actions so that we definitely run them locally.
if (action.action === 'TRANSIENT_ACTIONS') {
// Drill into the array.
return processActions(dispatchEvent, working, action.transientActions, spyCollector)
} else if (action.action === 'ATOMIC' || action.action === 'MERGE_WITH_PREV_UNDO') {
// Drill into the array.
return processActions(dispatchEvent, working, action.actions, spyCollector)
} else if (action.action === 'UNDO' && !History.canUndo(working.history)) {
// Bail early and make no changes.
return processActions(
dispatchEvent,
working,
[
EditorActions.addToast(
notice(
`Can't undo, reached the end of the undo history.`,
'NOTICE',
false,
cannotUndoRedoToastId,
if (shouldProcessAction) {
// Sidestep around the local actions so that we definitely run them locally.
if (action.action === 'TRANSIENT_ACTIONS') {
// Drill into the array.
return processActions(dispatchEvent, working, action.transientActions, spyCollector)
} else if (action.action === 'ATOMIC' || action.action === 'MERGE_WITH_PREV_UNDO') {
// Drill into the array.
return processActions(dispatchEvent, working, action.actions, spyCollector)
} else if (action.action === 'UNDO' && !History.canUndo(working.history)) {
// Bail early and make no changes.
return processActions(
dispatchEvent,
working,
[
EditorActions.addToast(
notice(
`Can't undo, reached the end of the undo history.`,
'NOTICE',
false,
cannotUndoRedoToastId,
),
),
),
],
spyCollector,
)
} else if (action.action === 'REDO' && !History.canRedo(working.history)) {
// Bail early and make no changes.
return processActions(
dispatchEvent,
working,
[
EditorActions.addToast(
notice(
`Can't redo, reached the end of the undo history.`,
'NOTICE',
false,
cannotUndoRedoToastId,
],
spyCollector,
)
} else if (action.action === 'REDO' && !History.canRedo(working.history)) {
// Bail early and make no changes.
return processActions(
dispatchEvent,
working,
[
EditorActions.addToast(
notice(
`Can't redo, reached the end of the undo history.`,
'NOTICE',
false,
cannotUndoRedoToastId,
),
),
),
],
spyCollector,
)
} else if (action.action === 'SET_SHORTCUT') {
return {
...working,
userState: UPDATE_FNS.SET_SHORTCUT(action, working.userState),
}
} else if (action.action === 'SET_CURRENT_THEME') {
return {
...working,
userState: UPDATE_FNS.SET_CURRENT_THEME(action, working.userState),
}
} else if (action.action === 'SET_LOGIN_STATE') {
return {
...working,
userState: UPDATE_FNS.SET_LOGIN_STATE(action, working.userState),
}
} else if (action.action === 'SET_GITHUB_STATE') {
return {
...working,
userState: UPDATE_FNS.SET_GITHUB_STATE(action, working.userState),
}
} else if (action.action === 'SET_USER_CONFIGURATION') {
return {
...working,
userState: UPDATE_FNS.SET_USER_CONFIGURATION(action, working.userState),
],
spyCollector,
)
} else if (action.action === 'SET_SHORTCUT') {
return {
...working,
userState: UPDATE_FNS.SET_SHORTCUT(action, working.userState),
}
} else if (action.action === 'SET_CURRENT_THEME') {
return {
...working,
userState: UPDATE_FNS.SET_CURRENT_THEME(action, working.userState),
}
} else if (action.action === 'SET_LOGIN_STATE') {
return {
...working,
userState: UPDATE_FNS.SET_LOGIN_STATE(action, working.userState),
}
} else if (action.action === 'SET_GITHUB_STATE') {
return {
...working,
userState: UPDATE_FNS.SET_GITHUB_STATE(action, working.userState),
}
} else if (action.action === 'SET_USER_CONFIGURATION') {
return {
...working,
userState: UPDATE_FNS.SET_USER_CONFIGURATION(action, working.userState),
}
}
}

if (action.action === 'UPDATE_TEXT') {
working = UPDATE_FNS.UPDATE_TEXT(action, working)
}
if (action.action === 'UPDATE_TEXT') {
working = UPDATE_FNS.UPDATE_TEXT(action, working)
}

if (action.action === 'TRUNCATE_HISTORY') {
working = UPDATE_FNS.TRUNCATE_HISTORY(working)
}
if (action.action === 'TRUNCATE_HISTORY') {
working = UPDATE_FNS.TRUNCATE_HISTORY(working)
}

if (action.action === 'START_POST_ACTION_SESSION') {
working = runExecuteStartPostActionMenuAction(action, working)
}
if (action.action === 'START_POST_ACTION_SESSION') {
working = runExecuteStartPostActionMenuAction(action, working)
}

if (action.action === 'EXECUTE_POST_ACTION_MENU_CHOICE') {
working = runExecuteWithPostActionMenuAction(action, working)
}
if (action.action === 'EXECUTE_POST_ACTION_MENU_CHOICE') {
working = runExecuteWithPostActionMenuAction(action, working)
}

if (action.action === 'CLEAR_POST_ACTION_SESSION') {
working = runClearPostActionSession(working)
}
if (action.action === 'CLEAR_POST_ACTION_SESSION') {
working = runClearPostActionSession(working)
}

if (action.action === 'UPDATE_PROJECT_SERVER_STATE') {
working = runUpdateProjectServerState(working, action)
}
if (action.action === 'UPDATE_PROJECT_SERVER_STATE') {
working = runUpdateProjectServerState(working, action)
}

// Process action on the JS side.
const editorAfterUpdateFunction = runLocalEditorAction(
working.unpatchedEditor,
working.unpatchedDerived,
working.userState,
working.workers,
action as EditorAction,
working.history,
dispatchEvent,
spyCollector,
working.builtInDependencies,
working.collaborativeEditingSupport,
working.projectServerState,
)
const editorAfterCanvas = runLocalCanvasAction(
dispatchEvent,
editorAfterUpdateFunction,
working.unpatchedDerived,
working.builtInDependencies,
action as CanvasAction,
)
const editorAfterNavigator = runLocalNavigatorAction(
editorAfterCanvas,
working.unpatchedDerived,
action as LocalNavigatorAction,
)
const withPossiblyClearedPseudoInsert = maybeClearPseudoInsertMode(
editorStoreUnpatched.unpatchedEditor,
editorAfterNavigator,
action,
)
// Process action on the JS side.
const editorAfterUpdateFunction = runLocalEditorAction(
working.unpatchedEditor,
working.unpatchedDerived,
working.userState,
working.workers,
action as EditorAction,
working.history,
dispatchEvent,
spyCollector,
working.builtInDependencies,
working.collaborativeEditingSupport,
working.projectServerState,
)
const editorAfterCanvas = runLocalCanvasAction(
dispatchEvent,
editorAfterUpdateFunction,
working.unpatchedDerived,
working.builtInDependencies,
action as CanvasAction,
)
const editorAfterNavigator = runLocalNavigatorAction(
editorAfterCanvas,
working.unpatchedDerived,
action as LocalNavigatorAction,
)
const withPossiblyClearedPseudoInsert = maybeClearPseudoInsertMode(
editorStoreUnpatched.unpatchedEditor,
editorAfterNavigator,
action,
)

let newStateHistory: StateHistory
switch (action.action) {
case 'UNDO':
newStateHistory = History.undo(working.unpatchedEditor.id, working.history, 'no-side-effects')
working.postActionInteractionSession = null
break
case 'REDO':
newStateHistory = History.redo(working.unpatchedEditor.id, working.history, 'no-side-effects')
break
case 'NEW':
case 'LOAD':
const derivedState = deriveState(
withPossiblyClearedPseudoInsert,
null,
'unpatched',
unpatchedCreateRemixDerivedDataMemo,
)
newStateHistory = History.init(withPossiblyClearedPseudoInsert, derivedState)
break
default:
newStateHistory = working.history
break
}
let newStateHistory: StateHistory
switch (action.action) {
case 'UNDO':
newStateHistory = History.undo(
working.unpatchedEditor.id,
working.history,
'no-side-effects',
)
working.postActionInteractionSession = null
break
case 'REDO':
newStateHistory = History.redo(
working.unpatchedEditor.id,
working.history,
'no-side-effects',
)
break
case 'NEW':
case 'LOAD':
const derivedState = deriveState(
withPossiblyClearedPseudoInsert,
null,
'unpatched',
unpatchedCreateRemixDerivedDataMemo,
)
newStateHistory = History.init(withPossiblyClearedPseudoInsert, derivedState)
break
default:
newStateHistory = working.history
break
}

return {
unpatchedEditor: withPossiblyClearedPseudoInsert,
unpatchedDerived: working.unpatchedDerived,
strategyState: working.strategyState, // this means the actions cannot update strategyState – this piece of state lives outside our "redux" state
postActionInteractionSession: working.postActionInteractionSession,
history: newStateHistory,
userState: working.userState,
workers: working.workers,
persistence: working.persistence,
saveCountThisSession: working.saveCountThisSession,
builtInDependencies: working.builtInDependencies,
projectServerState: working.projectServerState,
collaborativeEditingSupport: working.collaborativeEditingSupport,
return {
unpatchedEditor: withPossiblyClearedPseudoInsert,
unpatchedDerived: working.unpatchedDerived,
strategyState: working.strategyState, // this means the actions cannot update strategyState – this piece of state lives outside our "redux" state
postActionInteractionSession: working.postActionInteractionSession,
history: newStateHistory,
userState: working.userState,
workers: working.workers,
persistence: working.persistence,
saveCountThisSession: working.saveCountThisSession,
builtInDependencies: working.builtInDependencies,
projectServerState: working.projectServerState,
collaborativeEditingSupport: working.collaborativeEditingSupport,
}
} else {
return working
}
}

Expand Down

0 comments on commit 30af6b0

Please sign in to comment.