/
DAVCalendarAclSpec.js
452 lines (408 loc) · 15 KB
/
DAVCalendarAclSpec.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
import config from '../lib/config'
import WebDAV from '../lib/WebDAV'
import TestUtility from '../lib/utilities'
describe('create, read, modify, delete events for regular user', function() {
const webdav = new WebDAV(config.username, config.password)
const webdav_su = new WebDAV(config.superuser, config.superuser_password)
const webdav_subscriber = new WebDAV(config.subscriber_username, config.subscriber_password)
const utility = new TestUtility(webdav)
const event_template = `BEGIN:VCALENDAR
PRODID:-//Inverse//Event Generator//EN
VERSION:2.0
BEGIN:VEVENT
SEQUENCE:0
TRANSP:OPAQUE
UID:12345-%(class)-%(filename)
SUMMARY:%(class) event (orig. title)
DTSTART:20090805T100000Z
DTEND:20090805T140000Z
CLASS:%(class)
DESCRIPTION:%(class) description
LOCATION:location
%(organizer_line)%(attendee_line)CREATED:20090805T100000Z
DTSTAMP:20090805T100000Z
END:VEVENT
END:VCALENDAR`
const task_template = `BEGIN:VCALENDAR
PRODID:-//Inverse//Event Generator//EN
VERSION:2.0
BEGIN:VTODO
CREATED:20100122T201440Z
LAST-MODIFIED:20100201T175246Z
DTSTAMP:20100201T175246Z
UID:12345-%(class)-%(filename)
SUMMARY:%(class) event (orig. title)
CLASS:%(class)
DESCRIPTION:%(class) description
STATUS:IN-PROCESS
PERCENT-COMPLETE:0
END:VTODO
END:VCALENDAR`
const resource = `/SOGo/dav/${config.username}/Calendar/test-dav-acl/`
const classToICSClass = {
'pu': 'PUBLIC',
'pr': 'PRIVATE',
'co': 'CONFIDENTIAL'
}
let user
const _checkViewEventRight = function(operation, event, eventClass, right) {
if (right) {
expect(event)
.withContext(`Returned event during operation '${operation}'`)
.toBeTruthy()
if (['v', 'r', 'm'].includes(right)) {
const iscClass = classToICSClass[eventClass]
const expectedEvent = utility.formatTemplate(event_template, {
'class': iscClass,
'filename': `${iscClass.toLowerCase()}-event.ics`
})
expect(event).toBe(expectedEvent)
}
else if (right == 'd') {
_testEventIsSecureVersion(eventClass, event)
}
else {
throw new Error(`Right '${right} is not supported`)
}
}
else {
expect(event).toBeFalsy()
}
}
const _currentUserPrivilegeSet = async function(resource, expectedCode = 207) {
const results = await webdav_subscriber.currentUserPrivilegeSet(resource)
expect(results.length).toBe(1)
const response = results[0]
expect(response.status).toBe(expectedCode)
let privileges = []
if (expectedCode < 300) {
privileges = response.props.currentUserPrivilegeSet.privilege.map(o => {
return Object.keys(o)[0]
})
}
return privileges
}
const _deleteEvent = async function(client, filename, expectedCode = 204) {
const response = await client.deleteObject(resource + filename)
expect(response.status).toBe(expectedCode)
}
const _getEvent = async function(eventClass, isInvitation = false) {
const iscClass = classToICSClass[eventClass].toLowerCase()
const filename = (isInvitation ? `invitation-${iscClass}` : iscClass) + '-event.ics'
const [{ status, raw = '' }] = await webdav_subscriber.getEvent(resource, filename)
if (status == 200)
return raw.replace(/\r\n/g,'\n')
else
return undefined
}
const _multigetEvent = async function(eventClass) {
const iscClass = classToICSClass[eventClass].toLowerCase()
const filename = `${iscClass}-event.ics`
let event = undefined
const results = await webdav_subscriber.calendarMultiGet(resource, filename)
if (results.status !== 404) {
results.find(o => {
if (o.href == resource + filename) {
const { props: { calendarData = '' } } = o
event = calendarData.replace(/\r\n/g,'\n')
return true
}
return false
})
}
return event
}
const _propfindEvent = async function(eventClass) {
const iscClass = classToICSClass[eventClass].toLowerCase()
const filename = `${iscClass}-event.ics`
const results = await webdav_subscriber.propfindEvent(resource + filename)
let event = undefined
if (results.status !== 404) {
results.find(o => {
if (o.href == resource + filename) {
const { props: { calendarData = '' } } = o
event = calendarData.replace(/\r\n/g,'\n')
return true
}
return false
})
}
return event
}
const _putEvent = async function(client, filename, eventClass = 'PUBLIC', expectedCode = 201, organizer, attendee, partstat = 'NEEDS-ACTION') {
const organizer_line = organizer ? `ORGANIZER:${organizer}\n` : ''
const attendee_line = attendee ? `ATTENDEE;PARTSTAT=${partstat}:${attendee}\n` : ''
const event = utility.formatTemplate(event_template, {
'class': eventClass,
'filename': filename,
organizer_line,
attendee_line
})
const response = await client.createCalendarObject(resource, filename, event)
expect(response.status).toBe(expectedCode)
}
const _webdavSyncEvent = async function(eventClass) {
const iscClass = classToICSClass[eventClass].toLowerCase()
const filename = `${iscClass}-event.ics`
let event = undefined
const results = await webdav_subscriber.syncColletion(resource)
if (results.status !== 404) {
results.find(o => {
if (o.href == resource + filename) {
const { props: { calendarData = '' } } = o
event = calendarData.length ? calendarData.replace(/\r\n/g,'\n') : undefined
return true
}
return false
})
}
return event
}
const _testCreate = async function(rights) {
let expectedCode
if (rights.c)
expectedCode = 201
else if (Object.keys(rights).length === 0)
expectedCode = 404
else
expectedCode = 403
return _putEvent(webdav_subscriber, 'creation-test.ics', 'PUBLIC', expectedCode)
}
const _testCollectionDAVAcl = async function(rights) {
let expectedPrivileges = []
if (Object.keys(rights).length > 0) {
expectedPrivileges.push('read', 'readCurrentUserPrivilegeSet', 'readFreeBusy')
}
if (rights.c) {
expectedPrivileges.push(
'bind',
'writeContent',
'schedule',
'schedulePost',
'schedulePostVevent',
'schedulePostVtodo',
'schedulePostVjournal',
'schedulePostVfreebusy',
'scheduleDeliver',
'scheduleDeliverVevent',
'scheduleDeliverVtodo',
'scheduleDeliverVjournal',
'scheduleDeliverVfreebusy',
'scheduleRespond',
'scheduleRespondVevent',
'scheduleRespondVtodo'
)
}
if (rights.d) {
expectedPrivileges.push('unbind')
}
const expectedCode = (expectedPrivileges.length == 0) ? 404 : 207
const privileges = await _currentUserPrivilegeSet(resource, expectedCode)
// When comparing privileges on DAV collection, we remove all 'default'
// privileges on the collection.
for (const c of ['Public', 'Private', 'Confidential']) {
for (const r of ['viewdant', 'viewwhole', 'modify', 'respondto']) {
const i = privileges.indexOf(`${r}${c}Records`)
if (i >= 0) {
privileges.splice(i, 1)
}
}
}
// for (const privilege of ['read', 'readCurrentUserPrivilegeSet', 'readFreeBusy']) {
for (const expectedPrivilege of expectedPrivileges) {
expect(privileges).toContain(expectedPrivilege)
}
}
const _testEventIsSecureVersion = function(eventClass, event) {
const iscClass = classToICSClass[eventClass].toLowerCase().replace(/^\w/, c => c.toUpperCase())
const expectedDict = {
version: 'VERSION:2.0',
prodid: 'PRODID:-//Inverse//Event Generator//EN',
summary: `SUMMARY:(${iscClass} event)`,
dtstart: 'DTSTART:20090805T100000Z',
dtend: 'DTEND:20090805T140000Z',
dtstamp: 'DTSTAMP:20090805T100000Z',
'x-sogo-secure': 'X-SOGO-SECURE:YES'
}
const eventDict = utility.versitDict(event)
// Ignore UID
for (const key of Object.keys(eventDict).filter(k => k !== 'uid')) {
expect(expectedDict[key])
.withContext(`Key ${key} of secure event is expected`)
.toBeTruthy()
if (expectedDict[key])
expect(expectedDict[key])
.withContext(`Value of key ${key} of secure event is valid`)
.toBe(eventDict[key])
}
for (const key of Object.keys(expectedDict)) {
expect(eventDict[key])
.withContext(`Key ${key} of secure event is present`)
.toBeTruthy()
}
}
const _testModify = async function(eventClass, right, errorCode) {
const iscClass = classToICSClass[eventClass]
const filename = `${iscClass.toLowerCase()}-event.ics`
let expectedCode = errorCode
if (['r', 'm'].includes(right))
expectedCode = 204
return _putEvent(webdav_subscriber, filename, iscClass, expectedCode)
}
const _testRespondTo = async function(eventClass, right, errorCode) {
const iscClass = classToICSClass[eventClass]
const filename = `invitation-${iscClass.toLowerCase()}-event.ics`
let expectedCode = errorCode
if (['r', 'm'].includes(right))
expectedCode = 204
await _putEvent(webdav, filename, iscClass, 201, 'mailto:nobody@somewhere.com', user.email, 'NEEDS-ACTION')
// here we only do 'passive' validation: if a user has a "respond to"
// right, only the attendee entry will me modified. The change of
// organizer must thus be silently ignored below.
await _putEvent(webdav_subscriber, filename, iscClass, expectedCode, 'mailto:someone@nowhere.com', user.email, 'ACCEPTED')
if (expectedCode == 204) {
const attendee_line = `ATTENDEE;PARTSTAT=ACCEPTED:${user.email}\n`
let expectedEvent
if (right == 'r') {
expectedEvent = utility.formatTemplate(event_template, {
'class': iscClass,
'filename': filename,
organizer_line: 'ORGANIZER;CN=nobody@somewhere.com:mailto:nobody@somewhere.com\n',
attendee_line
})
}
else {
expectedEvent = utility.formatTemplate(event_template, {
'class': iscClass,
'filename': filename,
organizer_line: 'ORGANIZER;CN=someone@nowhere.com:mailto:someone@nowhere.com\n',
attendee_line
})
}
const event = await _getEvent(eventClass, true)
expect(utility.calendarsAreEqual(expectedEvent, event))
.withContext('Calendars of organizer and attendee are identical')
.toBe(true)
}
}
const _testEventDAVAcl = async function(eventClass, right, errorCode) {
const iscClass = classToICSClass[eventClass].toLowerCase()
for (const suffix of ['event', 'task']) {
const filename = `${iscClass}-${suffix}.ics`
let expectedCode = errorCode
let expectedPrivileges = []
if (right) {
expectedCode = 207
expectedPrivileges.push('readCurrentUserPrivilegeSet', 'viewDateAndTime', 'read')
if (right != 'd') {
expectedPrivileges.push('viewWholeComponent')
if (right != 'v') {
expectedPrivileges.push('respondToComponent', 'writeContent')
if (right != 'r') {
expectedPrivileges.push('writeProperties', 'write')
}
}
}
}
const privileges = await _currentUserPrivilegeSet(resource + filename, expectedCode)
if (errorCode != expectedCode) {
for (const expectedPrivilege of expectedPrivileges) {
expect(privileges).toContain(expectedPrivilege)
}
}
}
}
const _testEventRight = async function(eventClass, rights) {
const right = Object.keys(rights).includes(eventClass) ? rights[eventClass] : undefined
let event
event = await _getEvent(eventClass)
_checkViewEventRight('GET', event, eventClass, right)
event = await _propfindEvent(eventClass)
_checkViewEventRight('PROPFIND', event, eventClass, right)
event = await _multigetEvent(eventClass)
_checkViewEventRight('multiget', event, eventClass, right)
event = await _webdavSyncEvent(eventClass)
_checkViewEventRight('webdav-sync', event, eventClass, right)
const errorCode = (Object.keys(rights).length > 0) ? 403 : 404
await _testModify(eventClass, right, errorCode)
await _testRespondTo(eventClass, right, errorCode)
await _testEventDAVAcl(eventClass, right, errorCode)
}
const _testDelete = async function(rights) {
let expectedCode = 403
if (rights && rights.d) {
expectedCode = 204
}
else if (Object.keys(rights) == 0) {
expectedCode = 404
}
for (const eventClass of Object.values(classToICSClass)) {
await _deleteEvent(webdav_subscriber, `${eventClass.toLocaleLowerCase()}-event.ics`, expectedCode)
}
}
const _testRights = async function(rights) {
const results = await utility.setupCalendarRights(resource, config.subscriber_username, rights)
expect(results.length).toBe(1)
expect(results[0].status)
.withContext(`Setup rights (${JSON.stringify(rights)}) on ${resource}`)
.toBe(204)
await _testCreate(rights)
await _testCollectionDAVAcl(rights)
await _testEventRight('pu', rights)
await _testEventRight('pr', rights)
await _testEventRight('co', rights)
await _testDelete(rights)
}
beforeEach(async function() {
user = await utility.fetchUserInfo(config.username)
await webdav.deleteObject(resource)
await webdav.makeCalendar(resource)
for (const c of Object.values(classToICSClass)) {
// Create event for each class
const eventFilename = `${c.toLowerCase()}-event.ics`
const event = utility.formatTemplate(event_template, {
'class': c,
'filename': eventFilename
})
let response = await webdav.createCalendarObject(resource, eventFilename, event)
expect(response.status)
.withContext(`HTTP status when creating event with ${c} class`)
.toBe(201)
// Create task for each class
const taskFilename = `${c.toLowerCase()}-task.ics`
const task = utility.formatTemplate(task_template, {
'class': c,
'filename': taskFilename
})
response = await webdav.createCalendarObject(resource, taskFilename, task)
expect(response.status)
.withContext(`HTTP status when creating task with ${c} class`)
.toBe(201)
}
})
afterEach(async function() {
await webdav_su.deleteObject(resource)
})
// DAVCalendarAclTest
it("'view all' on a specific class (PUBLIC)", async function() {
await _testRights({ pu: 'v' })
})
it("'modify' PUBLIC, 'view all' PRIVATE, 'view d&t' confidential", async function() {
await _testRights({ pu: 'm', pr: 'v', co: 'd' })
})
it("'create' only", async function() {
await _testRights({ c: true })
})
it("'delete' only", async function() {
await _testRights({ d: true })
})
it("'create', 'delete', 'view d&t' PUBLIC, 'modify' PRIVATE", async function() {
await _testRights({ c: true, d: true, pu: 'd', pr: 'm' })
})
it("'create', 'respond to' PUBLIC", async function() {
await _testRights({ c: true, pu: 'r' })
})
it("no right given", async function() {
await _testRights({})
})
})