-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Read Only Mode in Canvas #23304
Read Only Mode in Canvas #23304
Conversation
@@ -43,6 +44,8 @@ export const esPersistMiddleware = ({ getState }) => { | |||
next(action); | |||
const newState = getState(); | |||
|
|||
if (getReadOnly(newState)) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rashidkpc I tried moving the current page from being stored as part of the workpad to transient state, but there were conflicts with the aeroelastic store where it relies the selected page to stay in the workpad state. I talked to @w33ble about the issues I was encountering, and we decided moving the selected page to transient state may be easier to implement after refactoring our redux implementation.
This was the simpler fix for preventing the user from updating the current page in the saved workpad object in elasticsearch.
@alexfrancoeur @rashidkpc With the changes in this PR, it only sets Canvas in read only mode when the user doesn't have write permissions. Should I also provide an option somewhere to export the workpad as read only and read it off of the workpad JSON on upload? Maybe add a lock button in the workpad header to set the workpad to read only. |
💔 Build Failed |
@cqliu1 I like the idea of exporting in read only. That would allow us to add demos to demo.elastic.co and then users could download them as well. A lock button would be nice too. I'll try and take this for a spin today. |
💚 Build Succeeded |
Opening an existing workpad now works like a charm. I'm using the "old" way to create a read only role. We may want to check with @kobelb and @legrego to see if the read only roles is significantly different with 6.5 As a read only user, I can still "create a workpad", just with constant errors. It makes for an awkward experience because every time you move an element you get a flurry of errors. Maybe we should disable the button? @asawariS @gingerwizard if we provide a read only mode in this fashion, end users will not be able to create and/or manipulate workpads on demo.elastic.co. I think this could be fine. We're showing the art of the possible with example workpads but if they want to get a feel for how to build them, they should spin up a trial. Because of the way Canvas saves progress, the only other way I can think of providing the "build" experience without saving is to simply hide or ignore the errors with a flag or something. |
6.5 likely won't help here, at least not in its current state. 6.6/Granular app privs will provide the necessary hooks for all applications to make decisions based on the current user's capabilities, so if you can wait for 6.6, that'd be preferred. There is an outside chance that we could provide a flag to the UI in 6.5 indicating if the user is a readonly user, but it would be a temporary measure, as 6.6/Granular App Privileges will fundamentally change the way this works. I'm saying "outside chance" because we'll want to wait for Spaces to merge into |
From my understanding, we're trying to update the workpad with the existing workpad and if we get a 403, then we assume we're in read-only mode per:
It's an interesting approach, that could potentially cause someone's changes to be overwritten if there's a certain timing of events, but from the security perspective it should work with RBAC and the legacy fallback to determine whether the user can edit workpads. |
import { getId } from '../../../lib/get_id'; | ||
import { notify } from '../../../lib/notify'; | ||
import { WorkpadDropzone as Component } from './workpad_dropzone'; | ||
|
||
export const WorkpadDropzone = compose( | ||
withState('isDropping', 'setDropping', false), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isDropping
is unused as far as I can tell, so I just removed it.
@legrego is there no easy way to check a users permissions upon entering the app? Something like https://www.elastic.co/guide/en/kibana/6.x/role-management-api-get.html but for users? I'm assuming not given your previous statement. I wonder if we can gain any insight here into how dashboard only mode was introduced? I'm guessing that the app itself checks for the dashboard_only_role or any others from the advanced setting list. Which is a completely different approach but I wonder if we can get creative here. |
@alexfrancoeur there's no great way to check a user's permissions when entering the app. The authorization logic is rather complex because of all of the permutations we need to allow:
The implementation we have for 6.5 is very focused, and unfortunately not extensible for the canvas use case. Dashboard only mode was also a very focused implementation, and from what I can tell, it's method isn't something we can apply here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spoke with Catherine about this. We cannot avoid the "flash" of the editing controls upon first interaction within the Canvas app until we have granular application permissions in Kibana. @legrego & @kobelb are targeting this for 6.6/6.x. I'd like to have the logic using granular app permissions before GA if possible. While this experience isn't ideal, I feel that it's fine for beta. Once merged, I'll open an issue to track adding granular app permissions so we can know the users roles & permissions for an application when they enter it. This will allow us to control the experience appropriately.
That being said, LGTM.
💔 Build Failed |
💔 Build Failed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I spoke with @cqliu1 yesterday, and I'm suggesting we divide this read-only change into two concepts:
1/ The viewer does not have permission within Kibana/Canvas to write workpads, and
2/ A particular workpad is not writable by the viewer, (for whatever reason, though there is only one today).
So I'm recommending we take this opportunity to augment the user model to include permissions information around Canvas while at the same time dividing that concern from the permissions of an individual workpad.
I think I caught all of the places below, but YMMV...!
@@ -11,6 +11,7 @@ import { setWorkpad } from '../../state/actions/workpad'; | |||
import { setAssets, resetAssets } from '../../state/actions/assets'; | |||
import { gotoPage } from '../../state/actions/pages'; | |||
import { getWorkpad } from '../../state/selectors/workpad'; | |||
import { setReadOnlyUser } from '../../state/actions/transient'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider setUserCanRead
, (which would default to true
) and setUserCanWrite
, which would be set based on the success or error response.
import { setReadOnlyUser } from '../../state/actions/transient'; | |
import { setUserCanRead, setUserCanWrite } from '../../state/actions/transient'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently there are only read or all privileges in Kibana, so I don't believe it's necessary to track read permissions since every user at a baseline has read privileges. It should be sufficient to just track if the user has write permissions.
@@ -29,6 +30,8 @@ export const routes = [ | |||
router.redirectTo('loadWorkpad', { id: newWorkpad.id, page: 1 }); | |||
} catch (err) { | |||
notify.error(err, { title: `Couldn't create workpad` }); | |||
if (err.response.status === 403) dispatch(setReadOnlyUser(true)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (err.response.status === 403) dispatch(setReadOnlyUser(true)); | |
if (err.response.status === 403) dispatch(setUserCanWrite(false)); |
|
||
// tests if user has permissions to write to workpads | ||
workpadService.update(params.id, fetchedWorkpad).catch(err => { | ||
if (err.response.status === 403) dispatch(setReadOnlyUser(true)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (err.response.status === 403) dispatch(setReadOnlyUser(true)); | |
if (err.response.status === 403) dispatch(setUserCanWrite(false)); |
@@ -48,6 +51,11 @@ export const routes = [ | |||
const { assets, ...workpad } = fetchedWorkpad; | |||
dispatch(setWorkpad(workpad)); | |||
dispatch(setAssets(assets)); | |||
|
|||
// tests if user has permissions to write to workpads | |||
workpadService.update(params.id, fetchedWorkpad).catch(err => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you mentioned there will be a new permissions API forthcoming, consider adding a TODO
comment here with a follow-up task?
@@ -8,16 +8,16 @@ import { connect } from 'react-redux'; | |||
import { compose, branch, renderComponent } from 'recompose'; | |||
import { initializeWorkpad } from '../../../state/actions/workpad'; | |||
import { selectElement } from '../../../state/actions/transient'; | |||
import { getEditing, getAppReady } from '../../../state/selectors/app'; | |||
import { getWorkpad } from '../../../state/selectors/workpad'; | |||
import { getReadOnlyUser, getAppReady } from '../../../state/selectors/app'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider boolean-style getters.
import { getReadOnlyUser, getAppReady } from '../../../state/selectors/app'; | |
import { canUserRead, canUserWrite, getAppReady } from '../../../state/selectors/app'; |
@@ -11,7 +11,7 @@ export const getInitialState = path => { | |||
const state = { | |||
app: {}, // Kibana stuff in here | |||
transient: { | |||
editing: true, | |||
readOnlyUser: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
readOnlyUser: false, | |
canUserRead: true, | |
canUserWrite: true, |
@@ -13,6 +13,7 @@ import * as transientActions from '../actions/transient'; | |||
import * as resolvedArgsActions from '../actions/resolved_args'; | |||
import { update } from '../../lib/workpad_service'; | |||
import { notify } from '../../lib/notify'; | |||
import { getReadOnlyUser } from '../selectors/app'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import { getReadOnlyUser } from '../selectors/app'; | |
import { canUserWrite } from '../selectors/app'; |
@@ -43,6 +44,9 @@ export const esPersistMiddleware = ({ getState }) => { | |||
next(action); | |||
const newState = getState(); | |||
|
|||
// skips the update request if in read only mode | |||
if (getReadOnlyUser(newState)) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (getReadOnlyUser(newState)) return; | |
if (!canUserWrite(newState)) return; |
@@ -28,8 +28,8 @@ export const transientReducer = handleActions( | |||
); | |||
}, | |||
|
|||
[actions.setEditing]: (transientState, { payload }) => { | |||
return set(transientState, 'editing', Boolean(payload)); | |||
[actions.setReadOnlyUser]: (transientState, { payload }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[actions.setReadOnlyUser]: (transientState, { payload }) => { | |
[actions.setCanUserWrite]: (transientState, { payload }) => { |
[actions.setEditing]: (transientState, { payload }) => { | ||
return set(transientState, 'editing', Boolean(payload)); | ||
[actions.setReadOnlyUser]: (transientState, { payload }) => { | ||
return set(transientState, 'readOnlyUser', Boolean(payload)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return set(transientState, 'readOnlyUser', Boolean(payload)); | |
return set(transientState, 'canUserWrite', Boolean(payload)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I spoke with @cqliu1 yesterday, and I'm suggesting we divide this read-only change into two concepts:
1/ The viewer does not have permission within Kibana/Canvas to write workpads, and
2/ A particular workpad is not writable by the viewer, (for whatever reason, though there is only one today).
So I'm recommending we take this opportunity to augment the user model to include permissions information around Canvas while at the same time dividing that concern from the permissions of an individual workpad.
I think I caught all of the places below, but YMMV...!
💚 Build Succeeded |
@@ -25,7 +25,6 @@ const mapStateToProps = state => ({ | |||
totalElementCount: getAllElements(state).length, | |||
workpad: getWorkpad(state), | |||
isFullscreen: getFullscreen(state), | |||
isEditing: getEditing(state), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This prop wasn't being used in the workpad component, so I removed it.
@clintandrewhall This is ready for another look when you get a chance. I made all of the changes you requested and added a couple tests for the |
💚 Build Succeeded |
* Added readOnly to transient state * Added check that user has permission to edit when loading workpads * Shows toolbar in readOnly mode * Disables page controls in read only mode * Disables add page button when editing disabled * Added comment to workpad routes * Cleaned up page manager styles * Added a comment to es_persist * Renamed transient.readOnly to transient.readOnlyUser * Replaced isEditing in transient state with readOnly in workpad state * Cleaned up workpad_header * Fixed logic in workpad_header * Disables workpad controls instead of hiding them and adds tooltips * Switched eye icon to lock icon in workpad header * Renamed readOnly and readOnlyUser variables and functions * Added dispatch to update canUserWrite in create/clone/delete workpads * Fixed typo * Removed unnecessary prop from workpad * Refactored tooltips in workpad_loader * Added workpad selector tests
* Added readOnly to transient state * Added check that user has permission to edit when loading workpads * Shows toolbar in readOnly mode * Disables page controls in read only mode * Disables add page button when editing disabled * Added comment to workpad routes * Cleaned up page manager styles * Added a comment to es_persist * Renamed transient.readOnly to transient.readOnlyUser * Replaced isEditing in transient state with readOnly in workpad state * Cleaned up workpad_header * Fixed logic in workpad_header * Disables workpad controls instead of hiding them and adds tooltips * Switched eye icon to lock icon in workpad header * Renamed readOnly and readOnlyUser variables and functions * Added dispatch to update canUserWrite in create/clone/delete workpads * Fixed typo * Removed unnecessary prop from workpad * Refactored tooltips in workpad_loader * Added workpad selector tests
Closes #22979.
Closes #23126.
This implements a read only mode in Canvas for users with only read permissions.
Changes
canUserWrite
to transient statecanUserWrite
to false when user doesn't have write permissionsisWriteable
to workpad objectUpdate 9/26/2018
editing
in transient state toreadOnly
to persistent workpad state. Now, you can export read only workpads and they will upload with edit mode disabled. Once uploaded and if the user has write permissions, the user can turn on edit mode.Update 10/4/18