* Calendar/CalDAV: do not allow to resurrect a deleted meeting by accepting it again via CalDAV or meeting-request from mail app

This commit is contained in:
ralf 2024-03-13 17:25:22 +02:00
parent a8222ed710
commit 0846fa78f3
3 changed files with 107 additions and 40 deletions

View File

@ -1067,6 +1067,25 @@ class calendar_groupdav extends Api\CalDAV\Handler
} }
} }
// if path not found, check the UID and return "403 Forbidden" if event is deleted or user has not rights to event with same UID
if (!isset($oldEvent) && ($events = $handler->icaltoegw($vCalendar)) &&
($oldEvents = $this->bo->read(['cal_uid' => $events[0]['uid'], 'cal_reference=0'], null, false, 'server')) !== null)
{
foreach($oldEvents as $oldEvent)
{
if (empty($oldEvent['deleted']))
{
break;
}
}
if (!empty($oldEvent['deleted']))
{
$this->caldav->log("Event with UID='{$events[0]['uid']}' has already been deleted!");
return '403 Forbidden';
}
// case user has no edit-rights for $oldEvent is handled below
}
if (is_array($oldEvent)) if (is_array($oldEvent))
{ {
$eventId = $oldEvent['id']; $eventId = $oldEvent['id'];
@ -1583,6 +1602,21 @@ class calendar_groupdav extends Api\CalDAV\Handler
$return_no_access = true; // to allow to check if current use is a participant and reject the event for him $return_no_access = true; // to allow to check if current use is a participant and reject the event for him
$event = $this->_common_get_put_delete('DELETE',$options,$id,$return_no_access); $event = $this->_common_get_put_delete('DELETE',$options,$id,$return_no_access);
/* user has delete-rights, check if we have an external organizer and more participants
if ($event && $return_no_access && count($event['participants']) > 2)
{
foreach($event['participants'] as $uid => $status)
{
$quantity = $role = null;
calendar_so::split_status($status, $quantity, $role);
if (!is_numeric($uid) && $role == 'CHAIR') break;
}
if (!(!is_numeric($uid) && $role == 'CHAIR'))
{
$return_no_access = false; // only set status rejected, but do NOT delete the event
}
}*/
// no event found --> 404 Not Found // no event found --> 404 Not Found
if (!is_array($event)) if (!is_array($event))
{ {
@ -1643,15 +1677,31 @@ class calendar_groupdav extends Api\CalDAV\Handler
* the same UID and/or caldav_name as not deleted events and would block access to valid entries * the same UID and/or caldav_name as not deleted events and would block access to valid entries
* *
* @param string|id $id * @param string|id $id
* @return array|boolean array with entry, false if no read rights, null if $id does not exist * @return array|boolean array with entry, false if no read rights or deleted, null if $id does not exist
*/ */
function read($id) function read($id)
{ {
if (strpos($column=self::$path_attr,'_') === false) $column = 'cal_'.$column; if (strpos($column=self::$path_attr,'_') === false) $column = 'cal_'.$column;
$event = $this->bo->read(array($column => $id, 'cal_deleted IS NULL', 'cal_reference=0'), null, true, $event = $this->bo->read(array($column => $id, 'cal_reference=0'), null, true,
$date_format = Api\CalDAV::isJSON() ? 'object' : 'server'); $date_format = Api\CalDAV::isJSON() ? 'object' : 'server');
if ($event) $event = array_shift($event); // read with array as 1. param, returns an array of events!
// read with array as 1. param, returns an array of events!
// as we no longer return only NOT-deleted events, there might be more
if ($event)
{
foreach ($event as $event)
{
if (empty($event['cal_deleted'])) break;
}
// the above prefers a NOT deleted event over deleted noes, thought all might be deleted
if (!empty($event['cal_deleted']))
{
$retval = false;
if ($this->debug > 0) error_log(__METHOD__."($id) event has been deleted returning ".array2string($retval));
return $retval;
}
}
if (!($retval = $this->bo->check_perms(calendar_bo::ACL_FREEBUSY,$event, 0, 'server')) && if (!($retval = $this->bo->check_perms(calendar_bo::ACL_FREEBUSY,$event, 0, 'server')) &&
// above can be true, if current user is not in master but just a recurrence // above can be true, if current user is not in master but just a recurrence

View File

@ -2130,8 +2130,7 @@ class calendar_uiforms extends calendar_ui
$event['end'] += $diff; $event['end'] += $diff;
} }
if (($existing_event = $this->bo->read($event['uid'], $event['recurrence'], false, 'ts', null, true)) && // true = read the exception if (($existing_event = $this->bo->read($event['uid'], $event['recurrence'], false, 'ts', null, true))) // true = read the exception
!$existing_event['deleted'])
{ {
// check if mail is from extern organizer // check if mail is from extern organizer
$from_extern_organizer = false; $from_extern_organizer = false;
@ -2179,7 +2178,7 @@ class calendar_uiforms extends calendar_ui
// warn user about party-crashers (non-participants sending a reply) // warn user about party-crashers (non-participants sending a reply)
if (!isset($existing_status)) if (!isset($existing_status))
{ {
if (!empty($event['sender_warning'])) $event['sender_warning'] .= "\n"; if (!empty($event['sender_warning'])) $event['sender_warning'] .= "\n\n";
$event['sender_warning'] .= lang('Replying "%1" is NOT a participant of the event! Only continue if you want to add as new participant.', $participant); $event['sender_warning'] .= lang('Replying "%1" is NOT a participant of the event! Only continue if you want to add as new participant.', $participant);
} }
calendar_so::split_status($existing_status, $quantity, $role); calendar_so::split_status($existing_status, $quantity, $role);
@ -2255,7 +2254,7 @@ class calendar_uiforms extends calendar_ui
$event['recure'] = $this->bo->recure2string($event); $event['recure'] = $this->bo->recure2string($event);
$event['all_participants'] = implode(",\n",$this->bo->participants($event, true)); $event['all_participants'] = implode(",\n",$this->bo->participants($event, true));
// EGroupware event has been deleted, dont let user resurect it by accepting again // EGroupware event has been deleted, don't let user resurrect it by accepting again
if ($existing_event && $existing_event['deleted'] && strtolower($ical_method) !== 'cancel') if ($existing_event && $existing_event['deleted'] && strtolower($ical_method) !== 'cancel')
{ {
// check if this is an EGroupware event or has an external organizer // check if this is an EGroupware event or has an external organizer
@ -2265,12 +2264,8 @@ class calendar_uiforms extends calendar_ui
calendar_so::split_status($status, $quantity, $role); calendar_so::split_status($status, $quantity, $role);
if (!is_numeric($uid) && $role == 'CHAIR') break; if (!is_numeric($uid) && $role == 'CHAIR') break;
} }
if (!(!is_numeric($uid) && $role == 'CHAIR')) $event['sender_warning'] = lang('Event has been deleted by organizer!').
{ (!empty($event['sender_warning']) ? "\n\n".$event['sender_warning'] : '');
$event['error'] = lang('Event has been deleted by organizer!');
$readonlys['button[accept]'] = $readonlys['button[tentativ]'] =
$readonlys['button[reject]'] = $readonlys['button[cancel]'] = true;
}
} }
// ignore events in the past (for recurring events check enddate!) // ignore events in the past (for recurring events check enddate!)
elseif ($this->bo->date2ts($event['start']) < $this->bo->now_su && elseif ($this->bo->date2ts($event['start']) < $this->bo->now_su &&

View File

@ -10,7 +10,7 @@
</columns> </columns>
<rows> <rows>
<row disabled="!@sender_warning"> <row disabled="!@sender_warning">
<grid width="100%" class="meetingRequest" span="all"> <grid width="100%" class="meetingRequest meetingWarning" span="all">
<columns> <columns>
<column/> <column/>
</columns> </columns>
@ -19,35 +19,42 @@
<et2-description value="Attention"></et2-description> <et2-description value="Attention"></et2-description>
</row> </row>
<row class="row"> <row class="row">
<et2-description id="sender_warning" class="meetingRequestError"></et2-description> <et2-description id="sender_warning"></et2-description>
</row> </row>
</rows> </rows>
</grid> </grid>
</row> </row>
<row disabled="!@ics_method=request"> <row disabled="!@ics_method=request">
<et2-description value="This mail contains a meeting request" class="meetingRequestMessage"></et2-description> <et2-vbox>
<et2-hbox> <et2-description value="This mail contains a meeting request" class="meetingRequestMessage"></et2-description>
<et2-button label="Apply" id="button[apply]" class="leftPad5" hideOnReadonly="true"></et2-button> <et2-hbox class="buttonRow">
<et2-button label="Accept" id="button[accept]" class="leftPad5" image="calendar/accepted"></et2-button> <et2-button label="Apply" id="button[apply]" hideOnReadonly="true"></et2-button>
<et2-button label="Tentative" id="button[tentativ]" class="leftPad5" image="calendar/tentative"></et2-button> <et2-button label="Accept" id="button[accept]" image="calendar/accepted"></et2-button>
<et2-button label="Reject" id="button[reject]" class="leftPad5" image="calendar/rejected"></et2-button> <et2-button label="Tentative" id="button[tentativ]" image="calendar/tentative"></et2-button>
<et2-button statustext="Edit event in calendar" label="Edit" id="button[edit]" image="edit" hideOnReadonly="true" onclick="window.open(egw::link('/index.php','menuaction=calendar.calendar_uiforms.edit&amp;cal_id=$cont[id]'),'_blank','dependent=yes,width=750,height=410,scrollbars=yes,status=yes'); return false;" class="leftPad5" noSubmit="true"></et2-button> <et2-button label="Reject" id="button[reject]" image="calendar/rejected"></et2-button>
</et2-hbox> <et2-button statustext="Edit event in calendar" label="Edit" id="button[edit]" image="edit" hideOnReadonly="true"
<et2-description id="error" class="meetingRequestError" align="right"></et2-description> onclick="window.open(egw::link('/index.php','menuaction=calendar.calendar_uiforms.edit&amp;cal_id=$cont[id]'),'_blank','dependent=yes,width=750,height=410,scrollbars=yes,status=yes'); return false;" noSubmit="true"></et2-button>
</et2-hbox>
</et2-vbox>
</row> </row>
<row disabled="!@ics_method=reply"> <row disabled="!@ics_method=reply">
<et2-description value="This mail contains a reply to a meeting request" class="meetingRequestMessage"></et2-description> <et2-vbox>
<et2-button label="Apply" id="button[apply]" class="leftPad5"></et2-button> <et2-description value="This mail contains a reply to a meeting request" class="meetingRequestMessage"></et2-description>
<et2-description id="error" class="meetingRequestError" align="right"></et2-description> <et2-hbox class="buttonRow">
<et2-button label="Apply" id="button[apply]"></et2-button>
</et2-hbox>
</et2-vbox>
</row> </row>
<row disabled="!@ics_method=cancel"> <row disabled="!@ics_method=cancel">
<et2-description value="This mail cancels a meeting" class="meetingRequestMessage"></et2-description> <et2-vbox>
<et2-hbox> <et2-description value="This mail cancels a meeting" class="meetingRequestMessage"></et2-description>
<et2-button label="Apply" statustext="Removes the event from my calendar" id="button[cancel]" class="leftPad5"></et2-button> <et2-hbox class="buttonRow">
<et2-button label="Delete" statustext="Delete this meeting for all participants" id="button[delete]" class="leftPad5" onclick="et2_dialog.confirm(widget,'Delete this meeting for all participants','Delete')"></et2-button> <et2-button label="Apply" statustext="Removes the event from my calendar" id="button[cancel]"></et2-button>
<et2-button statustext="Edit event in calendar" label="Edit" id="button[edit]" image="edit" onclick="window.open(egw::link('/index.php','menuaction=calendar.calendar_uiforms.edit&amp;cal_id=$cont[id]'),'_blank','dependent=yes,width=750,height=410,scrollbars=yes,status=yes'); return false;" class="leftPad5"></et2-button> <et2-button label="Delete" statustext="Delete this meeting for all participants" id="button[delete]" onclick="et2_dialog.confirm(widget,'Delete this meeting for all participants','Delete')"></et2-button>
</et2-hbox> <et2-button statustext="Edit event in calendar" label="Edit" id="button[edit]" image="edit"
<et2-description id="error" class="meetingRequestError" align="right"></et2-description> onclick="window.open(egw::link('/index.php','menuaction=calendar.calendar_uiforms.edit&amp;cal_id=$cont[id]'),'_blank','dependent=yes,width=750,height=410,scrollbars=yes,status=yes'); return false;"></et2-button>
</et2-hbox>
</et2-vbox>
</row> </row>
</rows> </rows>
</grid> </grid>
@ -61,6 +68,9 @@
<row class="th"> <row class="th">
<et2-description id="ics_method_label" span="all"></et2-description> <et2-description id="ics_method_label" span="all"></et2-description>
</row> </row>
<row class="row">
<et2-description id="error" class="meetingRequestError" span="all"></et2-description>
</row>
<row class="row"> <row class="row">
<et2-description value="Title"></et2-description> <et2-description value="Title"></et2-description>
<et2-description id="title" noLang="1"></et2-description> <et2-description id="title" noLang="1"></et2-description>
@ -75,9 +85,9 @@
</row> </row>
<row class="row"> <row class="row">
<et2-description value="Date"></et2-description> <et2-description value="Date"></et2-description>
<et2-hbox> <et2-hbox class="dates">
<et2-date-time id="start" readonly="true"></et2-date-time> <et2-date-time id="start" readonly="true"></et2-date-time>
<et2-date-time label="-" id="end" readonly="true" class="leftPad5"></et2-date-time> <et2-date-time label="-" id="end" readonly="true"></et2-date-time>
</et2-hbox> </et2-hbox>
</row> </row>
<row class="row" disabled="!@recure"> <row class="row" disabled="!@recure">
@ -107,7 +117,7 @@
overflow-y: auto; overflow-y: auto;
} }
.meetingRequestMessage { .meetingRequestMessage {
font-size: 120%; font-size: 150%;
} }
table.meetingRequest { table.meetingRequest {
@ -123,6 +133,7 @@
} }
.meetingRequest td { .meetingRequest td {
padding: 3px; padding: 3px;
padding-left: 0;
} }
.meetingRequest a { .meetingRequest a {
display: inline-block; display: inline-block;
@ -130,11 +141,22 @@
overflow-wrap: break-word; overflow-wrap: break-word;
vertical-align: text-top; vertical-align: text-top;
} }
et2-hbox.dates {
padding-left: 5px;
}
.meetingWarning tr.th {
background-color: #dc2625;
}
.meetingWarning tr.row td {
font-size: 120% !important;
}
.meetingRequestError { .meetingRequestError {
color: red;
font-style: italic; font-style: italic;
font-size: 120%; color: red;
}
.buttonRow {
padding-top: 10px;
padding-bottom: 10px;
} }
</styles> </styles>
</template> </template>