passbolt-styleguide 5.14.0 → 5.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/RELEASE_NOTES.md +3 -2
- package/build/css/help.min.css +2 -2
- package/build/css/public.min.css +2 -2
- package/build/css/themes/default/api_authentication.min.css +2 -2
- package/build/css/themes/default/api_cloud.min.css +2 -2
- package/build/css/themes/default/api_main.min.css +2 -2
- package/build/css/themes/default/api_reports.min.css +2 -2
- package/build/css/themes/default/api_webinstaller.min.css +2 -2
- package/build/css/themes/default/ext_app.min.css +2 -2
- package/build/css/themes/default/ext_authentication.min.css +2 -2
- package/build/css/themes/default/ext_external.min.css +2 -2
- package/build/css/themes/default/ext_in_form_cta.min.css +2 -2
- package/build/css/themes/default/ext_in_form_menu.min.css +2 -2
- package/build/css/themes/default/ext_quickaccess.min.css +2 -2
- package/build/css/themes/midgar/api_authentication.min.css +2 -2
- package/build/css/themes/midgar/api_main.min.css +2 -2
- package/build/css/themes/midgar/api_reports.min.css +2 -2
- package/build/css/themes/midgar/ext_app.min.css +2 -2
- package/build/css/themes/midgar/ext_authentication.min.css +2 -2
- package/build/css/themes/midgar/ext_in_form_cta.min.css +2 -2
- package/build/css/themes/midgar/ext_in_form_menu.min.css +2 -2
- package/build/css/themes/midgar/ext_quickaccess.min.css +2 -2
- package/build/css/themes/solarized_dark/api_authentication.min.css +2 -2
- package/build/css/themes/solarized_dark/api_main.min.css +2 -2
- package/build/css/themes/solarized_dark/api_reports.min.css +2 -2
- package/build/css/themes/solarized_dark/ext_app.min.css +2 -2
- package/build/css/themes/solarized_dark/ext_authentication.min.css +2 -2
- package/build/css/themes/solarized_dark/ext_in_form_cta.min.css +2 -2
- package/build/css/themes/solarized_dark/ext_in_form_menu.min.css +2 -2
- package/build/css/themes/solarized_dark/ext_quickaccess.min.css +2 -2
- package/build/css/themes/solarized_light/api_authentication.min.css +2 -2
- package/build/css/themes/solarized_light/api_main.min.css +2 -2
- package/build/css/themes/solarized_light/api_reports.min.css +2 -2
- package/build/css/themes/solarized_light/ext_app.min.css +2 -2
- package/build/css/themes/solarized_light/ext_authentication.min.css +2 -2
- package/build/css/themes/solarized_light/ext_in_form_cta.min.css +2 -2
- package/build/css/themes/solarized_light/ext_in_form_menu.min.css +2 -2
- package/build/css/themes/solarized_light/ext_quickaccess.min.css +2 -2
- package/build/js/dist/api-app.js +1 -1
- package/build/locales/pl-PL/common.json +2 -2
- package/package.json +1 -1
- package/src/locales/pl-PL/common.json +2 -2
- package/src/react-extension/components/Administration/DowngradeToCe/DowngradeToCe.js +1 -1
- package/src/react-extension/components/Resource/CreateResource/CreateResource.js +11 -1
- package/src/react-extension/components/Resource/CreateResource/CreateResource.test.data.js +2 -0
- package/src/react-extension/components/Resource/CreateResource/CreateResource.test.js +2 -2
- package/src/react-extension/components/Resource/EditResource/EditResource.js +17 -41
- package/src/react-extension/components/Resource/EditResource/EditResource.test.data.js +2 -0
- package/src/react-extension/components/Resource/EditResource/EditResource.test.js +56 -170
- package/src/react-extension/components/Resource/HandlePermissionWorkflow/flows/ResourceCreationFlow.js +31 -7
- package/src/react-extension/components/Resource/HandlePermissionWorkflow/flows/ResourceCreationFlow.test.js +4 -3
- package/src/react-extension/components/Resource/HandlePermissionWorkflow/flows/ResourceEditFlow.js +40 -17
- package/src/react-extension/components/Resource/HandlePermissionWorkflow/flows/ResourceEditFlow.test.js +14 -8
- package/src/react-extension/components/Share/GroupPermissionItem.js +6 -3
- package/src/react-extension/components/Share/ShareDialog.js +14 -1
- package/src/react-extension/components/Share/UserPermissionItem.js +6 -3
- package/src/react-extension/components/Share/Utility/ShareChanges.js +12 -0
- package/src/shared/context/ResourceTypesLocalStorageContext/ResourceTypesLocalStorageContext.js +2 -2
- package/src/shared/context/ResourceTypesLocalStorageContext/ResourceTypesLocalStorageContext.test.js +3 -3
- package/src/shared/services/permission/permissionChangesService.js +1 -5
- package/src/shared/services/permission/permissionChangesService.test.js +18 -42
|
@@ -61,10 +61,12 @@ export class ResourceCreationFlow extends AbstractPermissionFlow {
|
|
|
61
61
|
this.formSubmitted = false;
|
|
62
62
|
this.shareConfirmed = false;
|
|
63
63
|
this.pendingResourceFormEntity = null;
|
|
64
|
+
this.createResourceDialogFocusBackListener = null;
|
|
64
65
|
this.handleCreateResourceSubmit = this.handleCreateResourceSubmit.bind(this);
|
|
65
66
|
this.handleCreateResourceClose = this.handleCreateResourceClose.bind(this);
|
|
66
67
|
this.handleShareDialogConfirm = this.handleShareDialogConfirm.bind(this);
|
|
67
68
|
this.handleShareDialogClose = this.handleShareDialogClose.bind(this);
|
|
69
|
+
this.setFocusBackListener = this.setFocusBackListener.bind(this);
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
/**
|
|
@@ -74,6 +76,7 @@ export class ResourceCreationFlow extends AbstractPermissionFlow {
|
|
|
74
76
|
get defaultState() {
|
|
75
77
|
return {
|
|
76
78
|
status: RESOURCE_CREATION_FLOW_STATUS.INITIALIZING,
|
|
79
|
+
createResourceDialogId: null,
|
|
77
80
|
snapshot: null,
|
|
78
81
|
};
|
|
79
82
|
}
|
|
@@ -100,13 +103,17 @@ export class ResourceCreationFlow extends AbstractPermissionFlow {
|
|
|
100
103
|
* Open the CreateResource dialog and transition to the CREATE_RESOURCE_OPEN state.
|
|
101
104
|
*/
|
|
102
105
|
openCreateResourceDialog() {
|
|
103
|
-
this.props.dialogContext.open(CreateResource, {
|
|
106
|
+
const createResourceDialogId = this.props.dialogContext.open(CreateResource, {
|
|
104
107
|
resourceType: this.props.resourceType,
|
|
105
108
|
folderParentId: this.props.folderParentId,
|
|
106
109
|
onSubmit: this.handleCreateResourceSubmit,
|
|
107
110
|
onClose: this.handleCreateResourceClose,
|
|
111
|
+
setFocusBackListener: this.setFocusBackListener,
|
|
112
|
+
});
|
|
113
|
+
this.setState({
|
|
114
|
+
status: RESOURCE_CREATION_FLOW_STATUS.CREATE_RESOURCE_OPEN,
|
|
115
|
+
createResourceDialogId: createResourceDialogId,
|
|
108
116
|
});
|
|
109
|
-
this.setState({ status: RESOURCE_CREATION_FLOW_STATUS.CREATE_RESOURCE_OPEN });
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
/**
|
|
@@ -122,6 +129,7 @@ export class ResourceCreationFlow extends AbstractPermissionFlow {
|
|
|
122
129
|
permissions: this.state.snapshot.permissions,
|
|
123
130
|
},
|
|
124
131
|
],
|
|
132
|
+
initialChanges: this.state.snapshot.permissions.items,
|
|
125
133
|
initialGroups: this.state.snapshot.groups,
|
|
126
134
|
initialUsers: this.state.snapshot.users,
|
|
127
135
|
onConfirm: this.handleShareDialogConfirm,
|
|
@@ -150,6 +158,8 @@ export class ResourceCreationFlow extends AbstractPermissionFlow {
|
|
|
150
158
|
this.props.t("The resource has been added successfully"),
|
|
151
159
|
`/app/passwords/view/${created.id}`,
|
|
152
160
|
);
|
|
161
|
+
this.closeCreateResourceDialog();
|
|
162
|
+
this.terminate();
|
|
153
163
|
} catch (error) {
|
|
154
164
|
this.handleError(error);
|
|
155
165
|
}
|
|
@@ -161,9 +171,6 @@ export class ResourceCreationFlow extends AbstractPermissionFlow {
|
|
|
161
171
|
* dialog or API call); ignore it. Otherwise it's a cancellation: terminate.
|
|
162
172
|
*/
|
|
163
173
|
handleCreateResourceClose() {
|
|
164
|
-
if (this.formSubmitted) {
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
174
|
this.terminate();
|
|
168
175
|
}
|
|
169
176
|
|
|
@@ -196,13 +203,14 @@ export class ResourceCreationFlow extends AbstractPermissionFlow {
|
|
|
196
203
|
// exist yet. The extension stamps real ids server-side as part of its create-then-share
|
|
197
204
|
// orchestration, so passing null here is fine.
|
|
198
205
|
null,
|
|
199
|
-
this.props.context.loggedInUser.id,
|
|
200
206
|
);
|
|
201
207
|
const created = await this.createResource(this.pendingResourceFormEntity, finalChanges);
|
|
202
208
|
await this.finalizeSuccess(
|
|
203
209
|
this.props.t("The resource has been added successfully"),
|
|
204
210
|
`/app/passwords/view/${created.id}`,
|
|
205
211
|
);
|
|
212
|
+
this.closeCreateResourceDialog();
|
|
213
|
+
this.terminate();
|
|
206
214
|
} catch (error) {
|
|
207
215
|
this.handleError(error);
|
|
208
216
|
}
|
|
@@ -217,7 +225,23 @@ export class ResourceCreationFlow extends AbstractPermissionFlow {
|
|
|
217
225
|
if (this.shareConfirmed) {
|
|
218
226
|
return;
|
|
219
227
|
}
|
|
220
|
-
this.
|
|
228
|
+
this.createResourceDialogFocusBackListener?.();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Closes the currently opened create resource dialog.
|
|
233
|
+
*/
|
|
234
|
+
closeCreateResourceDialog() {
|
|
235
|
+
this.props.dialogContext.close(this.state.createResourceDialogId);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Sets the callback for when the create resource dialog needs to get the focus back.
|
|
240
|
+
* It's necessary for when the operator cancels the "share" process.
|
|
241
|
+
* @param {function} listener
|
|
242
|
+
*/
|
|
243
|
+
setFocusBackListener(listener) {
|
|
244
|
+
this.createResourceDialogFocusBackListener = listener;
|
|
221
245
|
}
|
|
222
246
|
|
|
223
247
|
/**
|
|
@@ -165,9 +165,10 @@ describe("ResourceCreationFlow", () => {
|
|
|
165
165
|
// the operator's autocomplete addition; the operator's own row is excluded.
|
|
166
166
|
const createCall = props.context.port.request.mock.calls.find(([event]) => event === "passbolt.resources.create");
|
|
167
167
|
const createPermissionsArg = createCall[3];
|
|
168
|
-
expect(createPermissionsArg).toHaveLength(
|
|
168
|
+
expect(createPermissionsArg).toHaveLength(3);
|
|
169
169
|
expect(createPermissionsArg).toEqual(
|
|
170
170
|
expect.arrayContaining([
|
|
171
|
+
expect.objectContaining({ is_new: true, aro_foreign_key: operatorId, type: 15 }),
|
|
171
172
|
expect.objectContaining({ is_new: true, aro_foreign_key: readerId, type: 1 }),
|
|
172
173
|
expect.objectContaining({ is_new: true, aro_foreign_key: newAroId, type: 1 }),
|
|
173
174
|
]),
|
|
@@ -342,7 +343,7 @@ describe("ResourceCreationFlow", () => {
|
|
|
342
343
|
);
|
|
343
344
|
});
|
|
344
345
|
|
|
345
|
-
it("As LU cancelling ShareDialog should
|
|
346
|
+
it("As LU cancelling ShareDialog should go back to the resource creation dialog without creating the resource", async () => {
|
|
346
347
|
expect.assertions(2);
|
|
347
348
|
const props = defaultProps();
|
|
348
349
|
const operatorId = props.context.loggedInUser.id;
|
|
@@ -381,7 +382,7 @@ describe("ResourceCreationFlow", () => {
|
|
|
381
382
|
const shareProps = dialogPropsFor(props.dialogContext, ShareDialog);
|
|
382
383
|
shareProps.onClose();
|
|
383
384
|
|
|
384
|
-
expect(props.onStop).
|
|
385
|
+
expect(props.onStop).not.toHaveBeenCalled();
|
|
385
386
|
expect(props.context.port.request).not.toHaveBeenCalledWith(
|
|
386
387
|
"passbolt.resources.create",
|
|
387
388
|
expect.anything(),
|
package/src/react-extension/components/Resource/HandlePermissionWorkflow/flows/ResourceEditFlow.js
CHANGED
|
@@ -20,8 +20,8 @@ import { withActionFeedback } from "../../../../contexts/ActionFeedbackContext";
|
|
|
20
20
|
import EditResource from "../../EditResource/EditResource";
|
|
21
21
|
import ShareDialog from "../../../Share/ShareDialog";
|
|
22
22
|
import PermissionEntity from "../../../../../shared/models/entity/permission/permissionEntity";
|
|
23
|
-
import { RESOURCE_TYPE_PASSWORD_STRING_SLUG } from "../../../../../shared/models/entity/resourceType/resourceTypeSchemasDefinition";
|
|
24
23
|
import { AbstractPermissionFlow, PERMISSION_FLOW_STATUS } from "./AbstractPermissionFlow";
|
|
24
|
+
import { withResourceWorkspace } from "../../../../contexts/ResourceWorkspaceContext";
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Status values driving the resource-edition flow state machine.
|
|
@@ -61,10 +61,13 @@ export class ResourceEditFlow extends AbstractPermissionFlow {
|
|
|
61
61
|
this.formSubmitted = false;
|
|
62
62
|
this.shareConfirmed = false;
|
|
63
63
|
this.pendingResourceFormEntity = null;
|
|
64
|
+
this.pendingResourceSecret = null;
|
|
65
|
+
this.editResourceDialogFocusBackListener = null;
|
|
64
66
|
this.handleEditResourceSubmit = this.handleEditResourceSubmit.bind(this);
|
|
65
67
|
this.handleEditResourceClose = this.handleEditResourceClose.bind(this);
|
|
66
68
|
this.handleShareDialogConfirm = this.handleShareDialogConfirm.bind(this);
|
|
67
69
|
this.handleShareDialogClose = this.handleShareDialogClose.bind(this);
|
|
70
|
+
this.setFocusBackListener = this.setFocusBackListener.bind(this);
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
/**
|
|
@@ -74,6 +77,7 @@ export class ResourceEditFlow extends AbstractPermissionFlow {
|
|
|
74
77
|
get defaultState() {
|
|
75
78
|
return {
|
|
76
79
|
status: RESOURCE_EDIT_FLOW_STATUS.INITIALIZING,
|
|
80
|
+
editResourceDialogId: null,
|
|
77
81
|
snapshot: null,
|
|
78
82
|
};
|
|
79
83
|
}
|
|
@@ -108,12 +112,13 @@ export class ResourceEditFlow extends AbstractPermissionFlow {
|
|
|
108
112
|
* Open the EditResource dialog and transition to the EDIT_RESOURCE_OPEN state.
|
|
109
113
|
*/
|
|
110
114
|
openEditResourceDialog() {
|
|
111
|
-
this.props.dialogContext.open(EditResource, {
|
|
115
|
+
const editResourceDialogId = this.props.dialogContext.open(EditResource, {
|
|
112
116
|
resource: this.props.resource,
|
|
113
117
|
onSubmit: this.handleEditResourceSubmit,
|
|
114
118
|
onClose: this.handleEditResourceClose,
|
|
119
|
+
setFocusBackListener: this.setFocusBackListener,
|
|
115
120
|
});
|
|
116
|
-
this.setState({ status: RESOURCE_EDIT_FLOW_STATUS.EDIT_RESOURCE_OPEN });
|
|
121
|
+
this.setState({ status: RESOURCE_EDIT_FLOW_STATUS.EDIT_RESOURCE_OPEN, editResourceDialogId: editResourceDialogId });
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
/**
|
|
@@ -146,19 +151,22 @@ export class ResourceEditFlow extends AbstractPermissionFlow {
|
|
|
146
151
|
* @param {ResourceFormEntity} resourceFormEntity The validated form entity.
|
|
147
152
|
* @returns {Promise<void>}
|
|
148
153
|
*/
|
|
149
|
-
async handleEditResourceSubmit(resourceFormEntity) {
|
|
154
|
+
async handleEditResourceSubmit(resourceFormEntity, secretDto) {
|
|
150
155
|
this.formSubmitted = true;
|
|
151
156
|
this.pendingResourceFormEntity = resourceFormEntity;
|
|
157
|
+
this.pendingResourceSecret = secretDto;
|
|
152
158
|
try {
|
|
153
159
|
if (this.isShared(this.state.snapshot)) {
|
|
154
160
|
this.openShareDialog();
|
|
155
161
|
return;
|
|
156
162
|
}
|
|
157
|
-
await this.updateResource(resourceFormEntity);
|
|
163
|
+
await this.updateResource(resourceFormEntity, secretDto);
|
|
158
164
|
await this.finalizeSuccess(
|
|
159
165
|
this.props.t("The resource has been updated successfully"),
|
|
160
166
|
`/app/passwords/view/${this.props.resource.id}`,
|
|
161
167
|
);
|
|
168
|
+
this.closeEditResourceDialog();
|
|
169
|
+
this.terminate();
|
|
162
170
|
} catch (error) {
|
|
163
171
|
this.handleError(error);
|
|
164
172
|
}
|
|
@@ -170,9 +178,6 @@ export class ResourceEditFlow extends AbstractPermissionFlow {
|
|
|
170
178
|
* or API call); ignore it. Otherwise it's a cancellation: terminate.
|
|
171
179
|
*/
|
|
172
180
|
handleEditResourceClose() {
|
|
173
|
-
if (this.formSubmitted) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
181
|
this.terminate();
|
|
177
182
|
}
|
|
178
183
|
|
|
@@ -200,11 +205,14 @@ export class ResourceEditFlow extends AbstractPermissionFlow {
|
|
|
200
205
|
),
|
|
201
206
|
);
|
|
202
207
|
}
|
|
203
|
-
await this.updateResource(this.pendingResourceFormEntity, permissionChanges);
|
|
208
|
+
await this.updateResource(this.pendingResourceFormEntity, this.pendingResourceSecret, permissionChanges);
|
|
204
209
|
await this.finalizeSuccess(
|
|
205
210
|
this.props.t("The resource has been updated successfully"),
|
|
206
211
|
`/app/passwords/view/${this.props.resource.id}`,
|
|
207
212
|
);
|
|
213
|
+
this.closeEditResourceDialog();
|
|
214
|
+
this.terminate();
|
|
215
|
+
this.props.resourceWorkspaceContext.onResourceEdited();
|
|
208
216
|
} catch (error) {
|
|
209
217
|
this.handleError(error);
|
|
210
218
|
}
|
|
@@ -219,7 +227,24 @@ export class ResourceEditFlow extends AbstractPermissionFlow {
|
|
|
219
227
|
if (this.shareConfirmed) {
|
|
220
228
|
return;
|
|
221
229
|
}
|
|
222
|
-
this.
|
|
230
|
+
this.editResourceDialogFocusBackListener?.();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Closes the currently opened edit resource dialog.
|
|
235
|
+
*/
|
|
236
|
+
closeEditResourceDialog() {
|
|
237
|
+
this.pendingResourceSecret = null;
|
|
238
|
+
this.props.dialogContext.close(this.state.editResourceDialogId);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Sets the callback for when the edit resource dialog needs to get the focus back.
|
|
243
|
+
* It's necessary for when the operator cancels the "share" process.
|
|
244
|
+
* @param {function} listener
|
|
245
|
+
*/
|
|
246
|
+
setFocusBackListener(listener) {
|
|
247
|
+
this.editResourceDialogFocusBackListener = listener;
|
|
223
248
|
}
|
|
224
249
|
|
|
225
250
|
/**
|
|
@@ -230,13 +255,8 @@ export class ResourceEditFlow extends AbstractPermissionFlow {
|
|
|
230
255
|
* @param {Array<object>} [permissionChanges] Operator-confirmed permission changes.
|
|
231
256
|
* @returns {Promise<Object>} The updated resource DTO.
|
|
232
257
|
*/
|
|
233
|
-
updateResource(resourceFormEntity, permissionChanges) {
|
|
258
|
+
updateResource(resourceFormEntity, secretDto, permissionChanges) {
|
|
234
259
|
const resourceDto = resourceFormEntity.toResourceDto();
|
|
235
|
-
const resourceType = resourceFormEntity.resourceTypeId
|
|
236
|
-
? this.props.context.resourceTypesCollection?.getFirstById(resourceFormEntity.resourceTypeId)
|
|
237
|
-
: null;
|
|
238
|
-
const isV4PasswordString = resourceType?.slug === RESOURCE_TYPE_PASSWORD_STRING_SLUG;
|
|
239
|
-
const secretDto = isV4PasswordString ? resourceFormEntity.toSecretDto().password : resourceFormEntity.toSecretDto();
|
|
240
260
|
return this.permissionServiceWorkerService.updateResource(resourceDto, secretDto, permissionChanges);
|
|
241
261
|
}
|
|
242
262
|
}
|
|
@@ -244,6 +264,9 @@ export class ResourceEditFlow extends AbstractPermissionFlow {
|
|
|
244
264
|
ResourceEditFlow.propTypes = {
|
|
245
265
|
...AbstractPermissionFlow.propTypes,
|
|
246
266
|
resource: PropTypes.object.isRequired, // the resource DTO being edited (carries the operator's own permission)
|
|
267
|
+
resourceWorkspaceContext: PropTypes.any, // The resource workspace context
|
|
247
268
|
};
|
|
248
269
|
|
|
249
|
-
export default withAppContext(
|
|
270
|
+
export default withAppContext(
|
|
271
|
+
withDialog(withActionFeedback(withRouter(withResourceWorkspace(withTranslation("common")(ResourceEditFlow))))),
|
|
272
|
+
);
|
|
@@ -64,6 +64,7 @@ function dialogPropsFor(dialogContext, DialogComponent) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const fakeResourceFormEntity = { toResourceDto: () => ({}), toSecretDto: () => ({}) };
|
|
67
|
+
const fakeSecretDto = { object_type: "PASSBOLT_SECRET_DATA" };
|
|
67
68
|
|
|
68
69
|
/**
|
|
69
70
|
* Mount the flow and wait until the EditResource dialog is open.
|
|
@@ -110,7 +111,7 @@ describe("ResourceEditFlow", () => {
|
|
|
110
111
|
|
|
111
112
|
// Submit the form: ShareDialog must follow because the resource is shared.
|
|
112
113
|
const editProps = dialogPropsFor(props.dialogContext, EditResource);
|
|
113
|
-
await act(() => editProps.onSubmit(fakeResourceFormEntity));
|
|
114
|
+
await act(() => editProps.onSubmit(fakeResourceFormEntity, fakeSecretDto));
|
|
114
115
|
await waitFor(() => {
|
|
115
116
|
if (page._instance.state.status !== RESOURCE_EDIT_FLOW_STATUS.SHARE_DIALOG_OPEN) {
|
|
116
117
|
throw new Error("ShareDialog not yet opened");
|
|
@@ -168,7 +169,7 @@ describe("ResourceEditFlow", () => {
|
|
|
168
169
|
|
|
169
170
|
const page = await mountUntilEditOpen(props);
|
|
170
171
|
const editProps = dialogPropsFor(props.dialogContext, EditResource);
|
|
171
|
-
await act(() => editProps.onSubmit(fakeResourceFormEntity));
|
|
172
|
+
await act(() => editProps.onSubmit(fakeResourceFormEntity, fakeSecretDto));
|
|
172
173
|
await waitFor(() => {
|
|
173
174
|
if (page._instance.state.status !== RESOURCE_EDIT_FLOW_STATUS.SHARE_DIALOG_OPEN) {
|
|
174
175
|
throw new Error("ShareDialog not yet opened");
|
|
@@ -214,7 +215,7 @@ describe("ResourceEditFlow", () => {
|
|
|
214
215
|
|
|
215
216
|
const page = await mountUntilEditOpen(props);
|
|
216
217
|
const editProps = dialogPropsFor(props.dialogContext, EditResource);
|
|
217
|
-
await act(() => editProps.onSubmit(fakeResourceFormEntity));
|
|
218
|
+
await act(() => editProps.onSubmit(fakeResourceFormEntity, fakeSecretDto));
|
|
218
219
|
await waitFor(() => {
|
|
219
220
|
if (page._instance.state.status !== RESOURCE_EDIT_FLOW_STATUS.SHARE_DIALOG_OPEN) {
|
|
220
221
|
throw new Error("ShareDialog not yet opened");
|
|
@@ -255,7 +256,7 @@ describe("ResourceEditFlow", () => {
|
|
|
255
256
|
jest
|
|
256
257
|
.spyOn(props.context.port, "request")
|
|
257
258
|
.mockImplementation((event) => (event === "passbolt.resources.update" ? { id: props.resource.id } : undefined));
|
|
258
|
-
await act(() => editProps.onSubmit(fakeResourceFormEntity));
|
|
259
|
+
await act(() => editProps.onSubmit(fakeResourceFormEntity, fakeSecretDto));
|
|
259
260
|
|
|
260
261
|
expect(props.context.port.request).toHaveBeenCalledWith(
|
|
261
262
|
"passbolt.resources.update",
|
|
@@ -292,8 +293,8 @@ describe("ResourceEditFlow", () => {
|
|
|
292
293
|
);
|
|
293
294
|
});
|
|
294
295
|
|
|
295
|
-
it("As LU cancelling ShareDialog should
|
|
296
|
-
expect.assertions(
|
|
296
|
+
it("As LU cancelling ShareDialog should return to the edit dialog without updating the resource", async () => {
|
|
297
|
+
expect.assertions(3);
|
|
297
298
|
const props = defaultProps();
|
|
298
299
|
const operatorId = props.context.loggedInUser.id;
|
|
299
300
|
wireSnapshotListeners(props.context.port, {
|
|
@@ -305,7 +306,10 @@ describe("ResourceEditFlow", () => {
|
|
|
305
306
|
|
|
306
307
|
const page = await mountUntilEditOpen(props);
|
|
307
308
|
const editProps = dialogPropsFor(props.dialogContext, EditResource);
|
|
308
|
-
|
|
309
|
+
// The edit dialog registers a focus-back listener the flow calls to refocus it on share cancel.
|
|
310
|
+
const focusBackListener = jest.fn();
|
|
311
|
+
editProps.setFocusBackListener(focusBackListener);
|
|
312
|
+
await act(() => editProps.onSubmit(fakeResourceFormEntity, fakeSecretDto));
|
|
309
313
|
await waitFor(() => {
|
|
310
314
|
if (page._instance.state.status !== RESOURCE_EDIT_FLOW_STATUS.SHARE_DIALOG_OPEN) {
|
|
311
315
|
throw new Error("ShareDialog not yet opened");
|
|
@@ -316,7 +320,9 @@ describe("ResourceEditFlow", () => {
|
|
|
316
320
|
const shareProps = dialogPropsFor(props.dialogContext, ShareDialog);
|
|
317
321
|
shareProps.onClose();
|
|
318
322
|
|
|
319
|
-
|
|
323
|
+
// Cancelling the share step returns to the edit dialog: the workflow is not terminated.
|
|
324
|
+
expect(focusBackListener).toHaveBeenCalledTimes(1);
|
|
325
|
+
expect(props.onStop).not.toHaveBeenCalled();
|
|
320
326
|
expect(props.context.port.request).not.toHaveBeenCalledWith(
|
|
321
327
|
"passbolt.resources.update",
|
|
322
328
|
expect.anything(),
|
|
@@ -157,9 +157,11 @@ class GroupPermissionItem extends Component {
|
|
|
157
157
|
/>
|
|
158
158
|
</div>
|
|
159
159
|
|
|
160
|
-
|
|
161
|
-
<
|
|
162
|
-
|
|
160
|
+
{!this.props.isReadOnly && (
|
|
161
|
+
<div className="actions">
|
|
162
|
+
<SharePermissionDeleteButton onClose={this.handleDelete} disabled={isInputDisabled} />
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
163
165
|
</li>
|
|
164
166
|
);
|
|
165
167
|
}
|
|
@@ -183,6 +185,7 @@ GroupPermissionItem.propTypes = {
|
|
|
183
185
|
shouldDisplayGroupMembers: PropTypes.bool,
|
|
184
186
|
canDisplayGroupMembers: PropTypes.bool,
|
|
185
187
|
permissionType: PropTypes.number,
|
|
188
|
+
isReadOnly: PropTypes.bool,
|
|
186
189
|
t: PropTypes.func, // The translation function
|
|
187
190
|
};
|
|
188
191
|
|
|
@@ -82,6 +82,15 @@ class ShareDialog extends Component {
|
|
|
82
82
|
|
|
83
83
|
this.shareChanges = new ShareChanges(this.resources, this.folders);
|
|
84
84
|
const permissions = this.shareChanges.aggregatePermissionsByAro();
|
|
85
|
+
|
|
86
|
+
const permissionsMap = new Map(permissions.map((p) => [p.aro.id, p]));
|
|
87
|
+
this.props.initialChanges?.forEach((change) => {
|
|
88
|
+
const permission = permissionsMap.get(change.aroForeignKey);
|
|
89
|
+
if (permission) {
|
|
90
|
+
this.shareChanges.markPermissionHasChanged(permission);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
85
94
|
this.setState({ loading: false, name: "", permissions: permissions }, () => {
|
|
86
95
|
// scroll at the top of the permission list
|
|
87
96
|
this.permissionListRef.current.scrollTo(0);
|
|
@@ -326,7 +335,7 @@ class ShareDialog extends Component {
|
|
|
326
335
|
|
|
327
336
|
// TODO restore to original permission if any
|
|
328
337
|
const permission = this.shareChanges.addAroPermissions(aro);
|
|
329
|
-
permission.updated =
|
|
338
|
+
permission.updated = this.shareChanges.hasChanges(aro.id);
|
|
330
339
|
const permissions = this.state.permissions;
|
|
331
340
|
permissions.push(permission);
|
|
332
341
|
this.setState({ permissions: permissions }, () => {
|
|
@@ -718,6 +727,7 @@ class ShareDialog extends Component {
|
|
|
718
727
|
onToggleGroupMemberVisibility={this.handleToggleGroupMemberVisibility}
|
|
719
728
|
shouldDisplayGroupMembers={this.state.expandedGroupIds.includes(permission.aro.id)}
|
|
720
729
|
canDisplayGroupMembers={this.isControlledMode()}
|
|
730
|
+
isReadOnly={this.props.readOnly}
|
|
721
731
|
/>
|
|
722
732
|
);
|
|
723
733
|
}
|
|
@@ -733,6 +743,7 @@ class ShareDialog extends Component {
|
|
|
733
743
|
disabled={this.hasAllInputDisabled() || this.isReadOnly()}
|
|
734
744
|
onUpdate={this.handlePermissionUpdate}
|
|
735
745
|
onDelete={this.handlePermissionDelete}
|
|
746
|
+
isReadOnly={this.props.readOnly}
|
|
736
747
|
/>
|
|
737
748
|
);
|
|
738
749
|
}
|
|
@@ -851,6 +862,7 @@ class ShareDialog extends Component {
|
|
|
851
862
|
ShareDialog.defaultProps = {
|
|
852
863
|
listMinSize: 4,
|
|
853
864
|
isPermissionConfirmationMode: true,
|
|
865
|
+
initialChanges: [],
|
|
854
866
|
};
|
|
855
867
|
|
|
856
868
|
ShareDialog.propTypes = {
|
|
@@ -862,6 +874,7 @@ ShareDialog.propTypes = {
|
|
|
862
874
|
listMinSize: PropTypes.number, // The minimum size to be renderered in the permission list
|
|
863
875
|
isPermissionConfirmationMode: PropTypes.bool, // Is the dialog used to confirm permissions
|
|
864
876
|
initialResources: PropTypes.array, // Controlled mode: the ACOs to seed the dialog with instead of fetching from the API, each as { id, metadata, permission, permissions: PermissionsCollection }
|
|
877
|
+
initialChanges: PropTypes.array, // Set of permission to mark them as "modified" in the initial list
|
|
865
878
|
acoType: PropTypes.string, // Controlled mode: the ACO type of the seeded entries (PermissionEntity.ACO_RESOURCE, default, or ACO_FOLDER)
|
|
866
879
|
initialGroups: PropTypes.object, // Controlled mode: GroupsCollection providing the groups referenced by the resources' permissions
|
|
867
880
|
initialUsers: PropTypes.object, // Controlled mode: UsersCollection providing the users referenced by the resources' permissions
|
|
@@ -185,9 +185,11 @@ class UserPermissionItem extends Component {
|
|
|
185
185
|
/>
|
|
186
186
|
</div>
|
|
187
187
|
|
|
188
|
-
|
|
189
|
-
<
|
|
190
|
-
|
|
188
|
+
{!this.props.isReadOnly && (
|
|
189
|
+
<div className="actions">
|
|
190
|
+
<SharePermissionDeleteButton onClose={this.handleDelete} disabled={isInputDisabled} />
|
|
191
|
+
</div>
|
|
192
|
+
)}
|
|
191
193
|
</li>
|
|
192
194
|
);
|
|
193
195
|
}
|
|
@@ -203,6 +205,7 @@ UserPermissionItem.propTypes = {
|
|
|
203
205
|
onUpdate: PropTypes.func,
|
|
204
206
|
onDelete: PropTypes.func,
|
|
205
207
|
permissionType: PropTypes.number,
|
|
208
|
+
isReadOnly: PropTypes.bool,
|
|
206
209
|
t: PropTypes.func, // The translation function
|
|
207
210
|
};
|
|
208
211
|
|
|
@@ -206,6 +206,18 @@ export default class ShareChanges {
|
|
|
206
206
|
});
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Used to initialise the ShareChanges list with already "changed" permissions.
|
|
211
|
+
* For example when creating a new shared resource, the permissions must be shown as "new"
|
|
212
|
+
* instead of "already existing".
|
|
213
|
+
* @param {PermissionEntity} permission
|
|
214
|
+
*/
|
|
215
|
+
markPermissionHasChanged(permission) {
|
|
216
|
+
const permissionChange = this._buildChange(permission.permissions[0].aco, permission.aro, permission.type);
|
|
217
|
+
this._changes.push(permissionChange);
|
|
218
|
+
permission.updated = true;
|
|
219
|
+
}
|
|
220
|
+
|
|
209
221
|
/**
|
|
210
222
|
* Delete aro's permissions.
|
|
211
223
|
* @param {string} aroId The aro to delete the permissions for
|
package/src/shared/context/ResourceTypesLocalStorageContext/ResourceTypesLocalStorageContext.js
CHANGED
|
@@ -83,8 +83,8 @@ export class ResourceTypesLocalStorageContextProvider extends React.Component {
|
|
|
83
83
|
* Handles update of the resource types in the local storage.
|
|
84
84
|
*/
|
|
85
85
|
handleStorageChange(changes) {
|
|
86
|
-
if (changes.
|
|
87
|
-
this.set(changes.
|
|
86
|
+
if (changes[this.storageKey] && changes[this.storageKey].newValue) {
|
|
87
|
+
this.set(changes[this.storageKey].newValue);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
package/src/shared/context/ResourceTypesLocalStorageContext/ResourceTypesLocalStorageContext.test.js
CHANGED
|
@@ -80,7 +80,7 @@ describe("ResourceTypesLocalStorageContext", () => {
|
|
|
80
80
|
const expectedResourceTypes = resourceTypesCollectionDto();
|
|
81
81
|
|
|
82
82
|
contextProvider.handleStorageChange({
|
|
83
|
-
|
|
83
|
+
[contextProvider.storageKey]: {
|
|
84
84
|
newValue: expectedResourceTypes,
|
|
85
85
|
},
|
|
86
86
|
});
|
|
@@ -112,7 +112,7 @@ describe("ResourceTypesLocalStorageContext", () => {
|
|
|
112
112
|
const expectedResourceTypes = resourceTypesCollectionDto();
|
|
113
113
|
|
|
114
114
|
contextProvider.handleStorageChange({
|
|
115
|
-
|
|
115
|
+
[contextProvider.storageKey]: {
|
|
116
116
|
newValue: expectedResourceTypes,
|
|
117
117
|
},
|
|
118
118
|
});
|
|
@@ -127,7 +127,7 @@ describe("ResourceTypesLocalStorageContext", () => {
|
|
|
127
127
|
mockComponentSetState(contextProvider);
|
|
128
128
|
|
|
129
129
|
contextProvider.handleStorageChange({
|
|
130
|
-
|
|
130
|
+
[contextProvider.storageKey]: {
|
|
131
131
|
newValue: [],
|
|
132
132
|
},
|
|
133
133
|
});
|
|
@@ -27,15 +27,11 @@ export default class PermissionChangesService {
|
|
|
27
27
|
* @param {PermissionSnapshotEntity} snapshot The snapshot shown in ShareDialog.
|
|
28
28
|
* @param {Array<object>} dialogChanges Deltas as `ShareChanges.getResourcesChanges()` emits them.
|
|
29
29
|
* @param {string} resourceId Id of the freshly-created resource.
|
|
30
|
-
* @param {string} operatorId Id of the logged-in user (excluded from output).
|
|
31
30
|
* @returns {Array<object>} `is_new`-flagged permission DTOs in the shape `share.resources.save` accepts.
|
|
32
31
|
*/
|
|
33
|
-
buildResourcePermissionChanges(snapshot, dialogChanges, resourceId
|
|
32
|
+
buildResourcePermissionChanges(snapshot, dialogChanges, resourceId) {
|
|
34
33
|
const finalByAroId = new Map();
|
|
35
34
|
for (const permission of snapshot.permissions.items) {
|
|
36
|
-
if (permission.aroForeignKey === operatorId) {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
35
|
finalByAroId.set(permission.aroForeignKey, {
|
|
40
36
|
is_new: true,
|
|
41
37
|
aro: permission.aro,
|
|
@@ -38,7 +38,7 @@ describe("PermissionChangesService", () => {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
describe("::buildResourcePermissionChanges", () => {
|
|
41
|
-
it("emits every
|
|
41
|
+
it("emits every row as is_new targeting the new resource when there are no dialog edits", () => {
|
|
42
42
|
expect.assertions(3);
|
|
43
43
|
|
|
44
44
|
const resourceId = uuidv4();
|
|
@@ -64,8 +64,16 @@ describe("PermissionChangesService", () => {
|
|
|
64
64
|
|
|
65
65
|
const changes = service.buildResourcePermissionChanges(snapshot, [], resourceId, operatorId);
|
|
66
66
|
|
|
67
|
-
expect(changes).toHaveLength(
|
|
67
|
+
expect(changes).toHaveLength(2);
|
|
68
68
|
expect(changes[0]).toMatchObject({
|
|
69
|
+
is_new: true,
|
|
70
|
+
aro: "User",
|
|
71
|
+
aro_foreign_key: operatorId,
|
|
72
|
+
aco: "Resource",
|
|
73
|
+
aco_foreign_key: resourceId,
|
|
74
|
+
type: 15,
|
|
75
|
+
});
|
|
76
|
+
expect(changes[1]).toMatchObject({
|
|
69
77
|
is_new: true,
|
|
70
78
|
aro: "User",
|
|
71
79
|
aro_foreign_key: readerId,
|
|
@@ -73,8 +81,6 @@ describe("PermissionChangesService", () => {
|
|
|
73
81
|
aco_foreign_key: resourceId,
|
|
74
82
|
type: 1,
|
|
75
83
|
});
|
|
76
|
-
// Operator row is the implicit owner on the newly-created resource and must not be emitted.
|
|
77
|
-
expect(changes.find((change) => change.aro_foreign_key === operatorId)).toBeUndefined();
|
|
78
84
|
});
|
|
79
85
|
|
|
80
86
|
it("drops a snapshot row when the operator's dialog edits include a matching `delete` delta", () => {
|
|
@@ -94,11 +100,11 @@ describe("PermissionChangesService", () => {
|
|
|
94
100
|
|
|
95
101
|
const changes = service.buildResourcePermissionChanges(snapshot, dialogChanges, resourceId, operatorId);
|
|
96
102
|
|
|
97
|
-
expect(changes).
|
|
103
|
+
expect(changes).toHaveLength(1);
|
|
98
104
|
});
|
|
99
105
|
|
|
100
106
|
it("patches the type of a snapshot row when the operator's dialog edits include a type-update delta", () => {
|
|
101
|
-
expect.assertions(
|
|
107
|
+
expect.assertions(3);
|
|
102
108
|
|
|
103
109
|
const folderId = uuidv4();
|
|
104
110
|
const resourceId = uuidv4();
|
|
@@ -112,10 +118,11 @@ describe("PermissionChangesService", () => {
|
|
|
112
118
|
{ aro: "User", aro_foreign_key: readerId, aco: "Resource", aco_foreign_key: null, type: 15 },
|
|
113
119
|
];
|
|
114
120
|
|
|
115
|
-
const changes = service.buildResourcePermissionChanges(snapshot, dialogChanges, resourceId
|
|
121
|
+
const changes = service.buildResourcePermissionChanges(snapshot, dialogChanges, resourceId);
|
|
116
122
|
|
|
117
|
-
expect(changes).toHaveLength(
|
|
118
|
-
expect(changes[0]).toMatchObject({ is_new: true, aro_foreign_key:
|
|
123
|
+
expect(changes).toHaveLength(2);
|
|
124
|
+
expect(changes[0]).toMatchObject({ is_new: true, aro_foreign_key: operatorId, type: 15 });
|
|
125
|
+
expect(changes[1]).toMatchObject({ is_new: true, aro_foreign_key: readerId, type: 15 });
|
|
119
126
|
});
|
|
120
127
|
|
|
121
128
|
it("appends a brand-new aro from the operator's dialog edits with aco_foreign_key stamped", () => {
|
|
@@ -134,8 +141,8 @@ describe("PermissionChangesService", () => {
|
|
|
134
141
|
|
|
135
142
|
const changes = service.buildResourcePermissionChanges(snapshot, dialogChanges, resourceId, operatorId);
|
|
136
143
|
|
|
137
|
-
expect(changes).toHaveLength(
|
|
138
|
-
expect(changes[
|
|
144
|
+
expect(changes).toHaveLength(2);
|
|
145
|
+
expect(changes[1]).toMatchObject({
|
|
139
146
|
is_new: true,
|
|
140
147
|
aro_foreign_key: newAroId,
|
|
141
148
|
aco: "Resource",
|
|
@@ -143,36 +150,5 @@ describe("PermissionChangesService", () => {
|
|
|
143
150
|
type: 1,
|
|
144
151
|
});
|
|
145
152
|
});
|
|
146
|
-
|
|
147
|
-
it("excludes the operator's own row from the output even when the snapshot lists it explicitly", () => {
|
|
148
|
-
expect.assertions(2);
|
|
149
|
-
|
|
150
|
-
const folderId = uuidv4();
|
|
151
|
-
const resourceId = uuidv4();
|
|
152
|
-
const operatorId = uuidv4();
|
|
153
|
-
const readerId = uuidv4();
|
|
154
|
-
const snapshot = snapshotWithPermissions([
|
|
155
|
-
defaultPermissionDto({ aco: "Folder", aco_foreign_key: folderId, aro_foreign_key: operatorId, type: 15 }),
|
|
156
|
-
defaultPermissionDto({ aco: "Folder", aco_foreign_key: folderId, aro_foreign_key: readerId, type: 1 }),
|
|
157
|
-
]);
|
|
158
|
-
|
|
159
|
-
const changes = service.buildResourcePermissionChanges(snapshot, [], resourceId, operatorId);
|
|
160
|
-
|
|
161
|
-
expect(changes).toHaveLength(1);
|
|
162
|
-
expect(changes.find((change) => change.aro_foreign_key === operatorId)).toBeUndefined();
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it("returns an empty array when the snapshot has only the operator and the dialog has no edits", () => {
|
|
166
|
-
expect.assertions(1);
|
|
167
|
-
|
|
168
|
-
const folderId = uuidv4();
|
|
169
|
-
const resourceId = uuidv4();
|
|
170
|
-
const operatorId = uuidv4();
|
|
171
|
-
const snapshot = snapshotWithPermissions([
|
|
172
|
-
defaultPermissionDto({ aco: "Folder", aco_foreign_key: folderId, aro_foreign_key: operatorId, type: 15 }),
|
|
173
|
-
]);
|
|
174
|
-
|
|
175
|
-
expect(service.buildResourcePermissionChanges(snapshot, [], resourceId, operatorId)).toEqual([]);
|
|
176
|
-
});
|
|
177
153
|
});
|
|
178
154
|
});
|