cozy-sharing 40.1.2 → 40.1.3

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.
@@ -0,0 +1,294 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
3
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
4
+
5
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
6
+
7
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
8
+
9
+ import _regeneratorRuntime from "@babel/runtime/regenerator";
10
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
11
+ import React, { useState } from 'react';
12
+ import { createMockClient } from 'cozy-client';
13
+ import { FederatedFolderModal } from './FederatedFolderModal';
14
+ import AppLike from '../../../test/AppLike';
15
+ import { SharingProvider } from '../../SharingProvider';
16
+ import { useSharingContext } from '../../hooks/useSharingContext';
17
+ jest.mock('../ShareByEmail', function () {
18
+ return function () {
19
+ return null;
20
+ };
21
+ });
22
+ jest.mock('../ShareByLink', function () {
23
+ return function () {
24
+ return null;
25
+ };
26
+ });
27
+ var document = {
28
+ id: 'received-folder',
29
+ _id: 'received-folder',
30
+ name: 'Received drive',
31
+ type: 'directory',
32
+ driveId: 'received-sharing'
33
+ };
34
+ var instance = 'https://me.mycozy.cloud';
35
+ var members = [{
36
+ status: 'owner',
37
+ instance: 'https://owner.mycozy.cloud',
38
+ name: 'Owner',
39
+ email: 'owner@test.com'
40
+ }, {
41
+ status: 'ready',
42
+ instance: instance,
43
+ name: 'Me',
44
+ email: 'me@test.com'
45
+ }];
46
+ var sharing = {
47
+ id: document.driveId,
48
+ _id: document.driveId,
49
+ type: 'io.cozy.sharings',
50
+ attributes: {
51
+ owner: false,
52
+ drive: true,
53
+ active: true,
54
+ rules: [{
55
+ values: [document.id]
56
+ }],
57
+ members: members
58
+ }
59
+ };
60
+
61
+ function RevocationView() {
62
+ var _useState = useState(false),
63
+ _useState2 = _slicedToArray(_useState, 2),
64
+ hasLeft = _useState2[0],
65
+ setHasLeft = _useState2[1];
66
+
67
+ var _useSharingContext = useSharingContext(),
68
+ allLoaded = _useSharingContext.allLoaded,
69
+ getSharingForSelf = _useSharingContext.getSharingForSelf;
70
+
71
+ if (!allLoaded) return null;
72
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("output", null, getSharingForSelf(document.id) ? 'Member of drive' : 'Not a member'), hasLeft ? /*#__PURE__*/React.createElement("h1", null, "Shared drives") : /*#__PURE__*/React.createElement(FederatedFolderModal, {
73
+ document: document,
74
+ onClose: function onClose() {
75
+ return setHasLeft(true);
76
+ },
77
+ onRevokeSuccess: function onRevokeSuccess() {
78
+ return setHasLeft(true);
79
+ }
80
+ }));
81
+ }
82
+
83
+ describe('FederatedFolderModal self-revocation with SharingProvider', function () {
84
+ it('closes and runs the success transition after leaving, without refreshing forbidden members', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7() {
85
+ var hasRevoked, client, sharingCol;
86
+ return _regeneratorRuntime.wrap(function _callee7$(_context7) {
87
+ while (1) switch (_context7.prev = _context7.next) {
88
+ case 0:
89
+ hasRevoked = false;
90
+ client = createMockClient({});
91
+ client.options.uri = instance;
92
+ client.getStackClient().uri = instance;
93
+ client.isLogged = true;
94
+ client.plugins.realtime = {
95
+ subscribe: jest.fn(),
96
+ unsubscribe: jest.fn()
97
+ };
98
+ sharingCol = {
99
+ findByDoctype: function () {
100
+ var _findByDoctype = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
101
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
102
+ while (1) switch (_context.prev = _context.next) {
103
+ case 0:
104
+ return _context.abrupt("return", {
105
+ data: [sharing]
106
+ });
107
+
108
+ case 1:
109
+ case "end":
110
+ return _context.stop();
111
+ }
112
+ }, _callee);
113
+ }));
114
+
115
+ function findByDoctype() {
116
+ return _findByDoctype.apply(this, arguments);
117
+ }
118
+
119
+ return findByDoctype;
120
+ }(),
121
+ revokeSelf: function () {
122
+ var _revokeSelf = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
123
+ return _regeneratorRuntime.wrap(function _callee2$(_context2) {
124
+ while (1) switch (_context2.prev = _context2.next) {
125
+ case 0:
126
+ hasRevoked = true;
127
+
128
+ case 1:
129
+ case "end":
130
+ return _context2.stop();
131
+ }
132
+ }, _callee2);
133
+ }));
134
+
135
+ function revokeSelf() {
136
+ return _revokeSelf.apply(this, arguments);
137
+ }
138
+
139
+ return revokeSelf;
140
+ }(),
141
+ fetchEffectiveRecipients: function () {
142
+ var _fetchEffectiveRecipients = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
143
+ return _regeneratorRuntime.wrap(function _callee3$(_context3) {
144
+ while (1) switch (_context3.prev = _context3.next) {
145
+ case 0:
146
+ if (!hasRevoked) {
147
+ _context3.next = 2;
148
+ break;
149
+ }
150
+
151
+ throw Object.assign(new Error('Forbidden'), {
152
+ status: 403
153
+ });
154
+
155
+ case 2:
156
+ return _context3.abrupt("return", {
157
+ data: members.map(function (member, memberIndex) {
158
+ return _objectSpread(_objectSpread({}, member), {}, {
159
+ sources: [{
160
+ kind: 'self',
161
+ sharing_id: sharing.id,
162
+ member_index: memberIndex
163
+ }]
164
+ });
165
+ })
166
+ });
167
+
168
+ case 3:
169
+ case "end":
170
+ return _context3.stop();
171
+ }
172
+ }, _callee3);
173
+ }));
174
+
175
+ function fetchEffectiveRecipients() {
176
+ return _fetchEffectiveRecipients.apply(this, arguments);
177
+ }
178
+
179
+ return fetchEffectiveRecipients;
180
+ }()
181
+ };
182
+
183
+ client.collection = function (doctype) {
184
+ if (doctype === 'io.cozy.sharings') return sharingCol;
185
+
186
+ if (doctype === 'io.cozy.permissions') {
187
+ return {
188
+ findLinksByDoctype: function () {
189
+ var _findLinksByDoctype = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
190
+ return _regeneratorRuntime.wrap(function _callee4$(_context4) {
191
+ while (1) switch (_context4.prev = _context4.next) {
192
+ case 0:
193
+ return _context4.abrupt("return", {
194
+ data: []
195
+ });
196
+
197
+ case 1:
198
+ case "end":
199
+ return _context4.stop();
200
+ }
201
+ }, _callee4);
202
+ }));
203
+
204
+ function findLinksByDoctype() {
205
+ return _findLinksByDoctype.apply(this, arguments);
206
+ }
207
+
208
+ return findLinksByDoctype;
209
+ }(),
210
+ findLinksByIds: function () {
211
+ var _findLinksByIds = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() {
212
+ return _regeneratorRuntime.wrap(function _callee5$(_context5) {
213
+ while (1) switch (_context5.prev = _context5.next) {
214
+ case 0:
215
+ return _context5.abrupt("return", {
216
+ data: []
217
+ });
218
+
219
+ case 1:
220
+ case "end":
221
+ return _context5.stop();
222
+ }
223
+ }, _callee5);
224
+ }));
225
+
226
+ function findLinksByIds() {
227
+ return _findLinksByIds.apply(this, arguments);
228
+ }
229
+
230
+ return findLinksByIds;
231
+ }()
232
+ };
233
+ }
234
+
235
+ return {
236
+ all: function () {
237
+ var _all = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6() {
238
+ return _regeneratorRuntime.wrap(function _callee6$(_context6) {
239
+ while (1) switch (_context6.prev = _context6.next) {
240
+ case 0:
241
+ return _context6.abrupt("return", {
242
+ data: []
243
+ });
244
+
245
+ case 1:
246
+ case "end":
247
+ return _context6.stop();
248
+ }
249
+ }, _callee6);
250
+ }));
251
+
252
+ function all() {
253
+ return _all.apply(this, arguments);
254
+ }
255
+
256
+ return all;
257
+ }()
258
+ };
259
+ };
260
+
261
+ render( /*#__PURE__*/React.createElement(AppLike, {
262
+ client: client
263
+ }, /*#__PURE__*/React.createElement(SharingProvider, {
264
+ client: client,
265
+ doctype: "io.cozy.files"
266
+ }, /*#__PURE__*/React.createElement(RevocationView, null))));
267
+ _context7.t0 = fireEvent;
268
+ _context7.next = 12;
269
+ return screen.findByLabelText('Remove from sharing');
270
+
271
+ case 12:
272
+ _context7.t1 = _context7.sent;
273
+
274
+ _context7.t0.click.call(_context7.t0, _context7.t1);
275
+
276
+ _context7.next = 16;
277
+ return waitFor(function () {
278
+ expect(screen.queryByRole('dialog')).toBe(null);
279
+ expect(screen.queryByRole('heading', {
280
+ name: 'Shared drives'
281
+ })).toBeInTheDocument();
282
+ expect(screen.queryByText('Not a member')).toBeInTheDocument();
283
+ });
284
+
285
+ case 16:
286
+ expect(hasRevoked).toBe(true);
287
+
288
+ case 17:
289
+ case "end":
290
+ return _context7.stop();
291
+ }
292
+ }, _callee7);
293
+ })));
294
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "40.1.2",
3
+ "version": "40.1.3",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -58,7 +58,7 @@
58
58
  "cozy-intent": "^2.31.1",
59
59
  "cozy-minilog": "^3.10.1",
60
60
  "cozy-ui": "^140.5.0",
61
- "cozy-ui-plus": "^14.0.7",
61
+ "cozy-ui-plus": "^14.0.8",
62
62
  "jest": "30.3.0",
63
63
  "jest-environment-jsdom": "30.3.0",
64
64
  "react": "16.12.0",
@@ -84,5 +84,5 @@
84
84
  "sideEffects": [
85
85
  "*.css"
86
86
  ],
87
- "gitHead": "b1c4088b4d66cf6a5ec727284773283c55d474ce"
87
+ "gitHead": "958de80707ab26c724df5384bac265e676d80295"
88
88
  }
@@ -381,10 +381,8 @@ export class SharingProvider extends Component {
381
381
  const sharing = getSharingForSelf(this.state, document.id)
382
382
  await this.sharingCol.revokeSelf(sharing)
383
383
  this.dispatch(revokeSelf(sharing))
384
- await this.fetchEffectiveRecipients(
385
- getDocumentId(document),
386
- document.driveId
387
- )
384
+ // Leaving removes our access to the sharing's effective recipients.
385
+ this.dispatch(receiveEffectiveRecipients(getDocumentId(document), []))
388
386
  }
389
387
 
390
388
  /**
@@ -632,6 +632,83 @@ describe('shared drive recipient revocation', () => {
632
632
  })
633
633
  })
634
634
 
635
+ describe('revokeSelf', () => {
636
+ const document = { id: 'received-folder', _id: 'received-folder' }
637
+ const sharing = {
638
+ id: 'received-sharing',
639
+ type: 'io.cozy.sharings',
640
+ attributes: {
641
+ owner: false,
642
+ drive: true,
643
+ rules: [{ values: [document.id] }],
644
+ members: []
645
+ }
646
+ }
647
+ const recipients = [{ sharingId: sharing.id, memberIndex: 1 }]
648
+ const forbidden = Object.assign(new Error('Forbidden'), { status: 403 })
649
+ let provider
650
+ let sharingCol
651
+
652
+ beforeEach(() => {
653
+ const client = createMockClient({})
654
+ sharingCol = {
655
+ revokeSelf: jest.fn().mockResolvedValue({}),
656
+ fetchEffectiveRecipients: jest.fn().mockRejectedValue(forbidden)
657
+ }
658
+ client.collection = () => sharingCol
659
+ provider = setupProvider(
660
+ client,
661
+ {},
662
+ reducer(undefined, receiveSharings({ sharings: [sharing] }))
663
+ )
664
+ provider.dispatch(receiveEffectiveRecipients(document.id, recipients))
665
+ provider.dispatch(receiveEffectiveRecipients('other-folder', recipients))
666
+ })
667
+
668
+ it.each([true, false])(
669
+ 'forgets the sharing and clears its recipients without reloading lost access (drive: %s)',
670
+ async drive => {
671
+ provider.state.sharings = [
672
+ { ...sharing, attributes: { ...sharing.attributes, drive } }
673
+ ]
674
+
675
+ await provider.revokeSelf(document)
676
+
677
+ expect(getDocumentSharing(provider.state, document.id)).toBeNull()
678
+ expect(getEffectiveRecipients(provider.state, document.id)).toEqual([])
679
+ expect(getEffectiveRecipients(provider.state, 'other-folder')).toEqual(
680
+ recipients
681
+ )
682
+ expect(sharingCol.fetchEffectiveRecipients).not.toHaveBeenCalled()
683
+ }
684
+ )
685
+
686
+ it.each([403, 500])(
687
+ 'preserves sharing state and rejects when self-revocation fails (%s)',
688
+ async status => {
689
+ const error = Object.assign(new Error('Revocation failed'), { status })
690
+ sharingCol.revokeSelf.mockRejectedValue(error)
691
+
692
+ await expect(provider.revokeSelf(document)).rejects.toBe(error)
693
+
694
+ expect(getDocumentSharing(provider.state, document.id)).toEqual(sharing)
695
+ expect(getEffectiveRecipients(provider.state, document.id)).toEqual(
696
+ recipients
697
+ )
698
+ }
699
+ )
700
+
701
+ it('still rejects forbidden recipient refreshes outside self-revocation', async () => {
702
+ await expect(provider.fetchEffectiveRecipients(document.id)).rejects.toBe(
703
+ forbidden
704
+ )
705
+
706
+ expect(getEffectiveRecipients(provider.state, document.id)).toEqual(
707
+ recipients
708
+ )
709
+ })
710
+ })
711
+
635
712
  describe('share flow reused by PermissionTypeMenu upgrade', () => {
636
713
  const PARENT_SHARING = {
637
714
  _id: 'parent_sharing',
@@ -0,0 +1,133 @@
1
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react'
2
+ import React, { useState } from 'react'
3
+
4
+ import { createMockClient } from 'cozy-client'
5
+
6
+ import { FederatedFolderModal } from './FederatedFolderModal'
7
+ import AppLike from '../../../test/AppLike'
8
+ import { SharingProvider } from '../../SharingProvider'
9
+ import { useSharingContext } from '../../hooks/useSharingContext'
10
+
11
+ jest.mock('../ShareByEmail', () => () => null)
12
+ jest.mock('../ShareByLink', () => () => null)
13
+
14
+ const document = {
15
+ id: 'received-folder',
16
+ _id: 'received-folder',
17
+ name: 'Received drive',
18
+ type: 'directory',
19
+ driveId: 'received-sharing'
20
+ }
21
+ const instance = 'https://me.mycozy.cloud'
22
+ const members = [
23
+ {
24
+ status: 'owner',
25
+ instance: 'https://owner.mycozy.cloud',
26
+ name: 'Owner',
27
+ email: 'owner@test.com'
28
+ },
29
+ {
30
+ status: 'ready',
31
+ instance,
32
+ name: 'Me',
33
+ email: 'me@test.com'
34
+ }
35
+ ]
36
+ const sharing = {
37
+ id: document.driveId,
38
+ _id: document.driveId,
39
+ type: 'io.cozy.sharings',
40
+ attributes: {
41
+ owner: false,
42
+ drive: true,
43
+ active: true,
44
+ rules: [{ values: [document.id] }],
45
+ members
46
+ }
47
+ }
48
+
49
+ function RevocationView() {
50
+ const [hasLeft, setHasLeft] = useState(false)
51
+ const { allLoaded, getSharingForSelf } = useSharingContext()
52
+ if (!allLoaded) return null
53
+
54
+ return (
55
+ <>
56
+ <output>
57
+ {getSharingForSelf(document.id) ? 'Member of drive' : 'Not a member'}
58
+ </output>
59
+ {hasLeft ? (
60
+ <h1>Shared drives</h1>
61
+ ) : (
62
+ <FederatedFolderModal
63
+ document={document}
64
+ onClose={() => setHasLeft(true)}
65
+ onRevokeSuccess={() => setHasLeft(true)}
66
+ />
67
+ )}
68
+ </>
69
+ )
70
+ }
71
+
72
+ describe('FederatedFolderModal self-revocation with SharingProvider', () => {
73
+ it('closes and runs the success transition after leaving, without refreshing forbidden members', async () => {
74
+ let hasRevoked = false
75
+ const client = createMockClient({})
76
+ client.options.uri = instance
77
+ client.getStackClient().uri = instance
78
+ client.isLogged = true
79
+ client.plugins.realtime = { subscribe: jest.fn(), unsubscribe: jest.fn() }
80
+ const sharingCol = {
81
+ findByDoctype: async () => ({ data: [sharing] }),
82
+ revokeSelf: async () => {
83
+ hasRevoked = true
84
+ },
85
+ fetchEffectiveRecipients: async () => {
86
+ if (hasRevoked) {
87
+ throw Object.assign(new Error('Forbidden'), { status: 403 })
88
+ }
89
+ return {
90
+ data: members.map((member, memberIndex) => ({
91
+ ...member,
92
+ sources: [
93
+ {
94
+ kind: 'self',
95
+ sharing_id: sharing.id,
96
+ member_index: memberIndex
97
+ }
98
+ ]
99
+ }))
100
+ }
101
+ }
102
+ }
103
+ client.collection = doctype => {
104
+ if (doctype === 'io.cozy.sharings') return sharingCol
105
+ if (doctype === 'io.cozy.permissions') {
106
+ return {
107
+ findLinksByDoctype: async () => ({ data: [] }),
108
+ findLinksByIds: async () => ({ data: [] })
109
+ }
110
+ }
111
+ return { all: async () => ({ data: [] }) }
112
+ }
113
+
114
+ render(
115
+ <AppLike client={client}>
116
+ <SharingProvider client={client} doctype="io.cozy.files">
117
+ <RevocationView />
118
+ </SharingProvider>
119
+ </AppLike>
120
+ )
121
+
122
+ fireEvent.click(await screen.findByLabelText('Remove from sharing'))
123
+
124
+ await waitFor(() => {
125
+ expect(screen.queryByRole('dialog')).toBe(null)
126
+ expect(
127
+ screen.queryByRole('heading', { name: 'Shared drives' })
128
+ ).toBeInTheDocument()
129
+ expect(screen.queryByText('Not a member')).toBeInTheDocument()
130
+ })
131
+ expect(hasRevoked).toBe(true)
132
+ })
133
+ })