@@ -377,11 +377,130 @@ async def _persist_events_and_state_updates(
377377
378378 event_counter .labels (event .type , origin_type , origin_entity ).inc ()
379379
380+ if (
381+ not self .hs .config .experimental .msc4293_enabled
382+ or event .type != EventTypes .Member
383+ or event .state_key is None
384+ ):
385+ continue
386+
387+ # check if this is an unban/join that will undo a ban/kick redaction for
388+ # a user in the room
389+ if event .membership in [Membership .LEAVE , Membership .JOIN ]:
390+ if (
391+ event .membership == Membership .LEAVE
392+ and event .sender == event .state_key
393+ ):
394+ # self-leave, ignore
395+ continue
396+
397+ # if there is an existing ban/leave causing redactions for
398+ # this user/room combination update the entry with the stream
399+ # ordering when the redactions should stop - in the case of a backfilled
400+ # event where the stream ordering is negative, use the current max stream
401+ # ordering
402+ stream_ordering = event .internal_metadata .stream_ordering
403+ assert stream_ordering is not None
404+ if stream_ordering < 0 :
405+ stream_ordering = self ._stream_id_gen .get_current_token ()
406+ await self .db_pool .simple_update (
407+ "room_ban_redactions" ,
408+ {"room_id" : event .room_id , "user_id" : event .state_key },
409+ {"redact_end_ordering" : stream_ordering },
410+ desc = "room_ban_redactions update redact_end_ordering" ,
411+ )
412+
413+ # check for msc4293 redact_events flag and apply if found
414+ if event .membership not in [Membership .LEAVE , Membership .BAN ]:
415+ continue
416+ redact = event .content .get ("org.matrix.msc4293.redact_events" , False )
417+ if not redact or not isinstance (redact , bool ):
418+ continue
419+ # self-bans currently are not authorized so we don't check for that
420+ # case
421+ if (
422+ event .membership == Membership .BAN
423+ and event .sender == event .state_key
424+ ):
425+ continue
426+
427+ # check that sender can redact
428+ redact_allowed = await self ._can_sender_redact (event )
429+
430+ # Signal that this user's past events in this room
431+ # should be redacted by adding an entry to
432+ # `room_ban_redactions`.
433+ if redact_allowed :
434+ await self .db_pool .simple_upsert (
435+ "room_ban_redactions" ,
436+ {"room_id" : event .room_id , "user_id" : event .state_key },
437+ {
438+ "redacting_event_id" : event .event_id ,
439+ "redact_end_ordering" : None ,
440+ },
441+ {
442+ "room_id" : event .room_id ,
443+ "user_id" : event .state_key ,
444+ "redacting_event_id" : event .event_id ,
445+ "redact_end_ordering" : None ,
446+ },
447+ )
448+
449+ # normally the cache entry for a redacted event would be invalidated
450+ # by an arriving redaction event, but since we are not creating redaction
451+ # events we invalidate manually
452+ self .store ._invalidate_local_get_event_cache_room_id (event .room_id )
453+
454+ self .store ._invalidate_async_get_event_cache_room_id (event .room_id )
455+
380456 if new_forward_extremities :
381457 self .store .get_latest_event_ids_in_room .prefill (
382458 (room_id ,), frozenset (new_forward_extremities )
383459 )
384460
461+ async def _can_sender_redact (self , event : EventBase ) -> bool :
462+ state_filter = StateFilter .from_types (
463+ [(EventTypes .PowerLevels , "" ), (EventTypes .Create , "" )]
464+ )
465+ state = await self .store .get_partial_filtered_current_state_ids (
466+ event .room_id , state_filter
467+ )
468+ pl_id = state [(EventTypes .PowerLevels , "" )]
469+ pl_event = await self .store .get_event (pl_id , allow_none = True )
470+
471+ if pl_event is None :
472+ # per the spec, if a power level event isn't in the room, grant the creator
473+ # level 100 and all other users 0
474+ create_id = state [(EventTypes .Create , "" )]
475+ create_event = await self .store .get_event (create_id , allow_none = True )
476+ if create_event is None :
477+ # not sure how this would happen but if it does then just deny the redaction
478+ logger .warning ("No create event found for room %s" , event .room_id )
479+ return False
480+ if create_event .sender == event .sender :
481+ return True
482+
483+ assert pl_event is not None
484+ sender_level = pl_event .content .get ("users" , {}).get (event .sender )
485+ if sender_level is None :
486+ sender_level = pl_event .content .get ("users_default" , 0 )
487+
488+ redact_level = pl_event .content .get ("redact" )
489+ if redact_level is None :
490+ redact_level = pl_event .content .get ("events_default" , 0 )
491+
492+ room_redaction_level = pl_event .content .get ("events" , {}).get (
493+ "m.room.redaction"
494+ )
495+ if room_redaction_level is not None :
496+ if sender_level < room_redaction_level :
497+ return False
498+
499+ if sender_level >= redact_level :
500+ return True
501+
502+ return False
503+
385504 async def _calculate_sliding_sync_table_changes (
386505 self ,
387506 room_id : str ,
0 commit comments