452 lines
15 KiB
JavaScript
452 lines
15 KiB
JavaScript
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.getObject(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.componentsAreEqual(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({})
|
|
})
|
|
|
|
}) |