2018-09-04 16:29:04 +02:00
// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
2019-03-09 22:12:58 +01:00
ircd : : mapi : : header
2018-09-04 16:29:04 +02:00
IRCD_MODULE
{
" Matrix event library; modular components. "
} ;
2019-03-09 22:12:58 +01:00
namespace ircd : : m : : vm
{
extern const m : : hookfn < eval & > issue_missing_auth ;
extern const m : : hookfn < eval & > conform_check_origin ;
extern const m : : hookfn < eval & > conform_check_size ;
extern const m : : hookfn < eval & > conform_report ;
}
/// Check if an eval with a copts structure (indicating this server is
/// creating the event) has an origin set to !my_host().
decltype ( ircd : : m : : vm : : conform_check_origin )
ircd : : m : : vm : : conform_check_origin
{
{
{ " _site " , " vm.conform " }
} ,
[ ] ( const m : : event & event , eval & eval )
{
if ( unlikely ( eval . copts & & ! my_host ( at < " origin " _ > ( event ) ) ) )
throw error
{
fault : : GENERAL , " Issuing event for origin: %s " , at < " origin " _ > ( event )
} ;
}
} ;
/// Check if an event originating from this server exceeds maximum size.
decltype ( ircd : : m : : vm : : conform_check_size )
ircd : : m : : vm : : conform_check_size
{
{
{ " _site " , " vm.conform " } ,
{ " origin " , my_host ( ) }
} ,
[ ] ( const m : : event & event , eval & eval )
{
const size_t & event_size
{
serialized ( event )
} ;
if ( event_size > size_t ( event : : max_size ) )
throw m : : BAD_JSON
{
" Event is %zu bytes which is larger than the maximum %zu bytes " ,
event_size ,
size_t ( event : : max_size )
} ;
}
} ;
/// Check if an event originating from this server exceeds maximum size.
decltype ( ircd : : m : : vm : : conform_report )
ircd : : m : : vm : : conform_report
{
{
{ " _site " , " vm.conform " }
} ,
[ ] ( const m : : event & event , eval & eval )
{
assert ( eval . opts ) ;
auto & opts ( * eval . opts ) ;
// When opts.conformed is set the report is already generated
if ( opts . conformed )
{
eval . report = opts . report ;
return ;
}
// Generate the report here.
eval . report = event : : conforms
{
event , opts . non_conform . report
} ;
// When opts.conforming is false a bad report is not an error.
if ( ! opts . conforming )
return ;
// Otherwise this will kill the eval
if ( ! eval . report . clean ( ) )
throw error
{
fault : : INVALID , " Non-conforming event: %s " , string ( eval . report )
} ;
}
} ;
2019-02-09 23:51:19 +01:00
std : : ostream &
IRCD_MODULE_EXPORT
ircd : : m : : pretty ( std : : ostream & s ,
const event & event )
2018-09-04 16:29:04 +02:00
{
const auto out { [ & s ]
( const string_view & key , auto & & val )
{
if ( defined ( json : : value ( val ) ) )
s < < std : : setw ( 16 ) < < std : : right < < key < < " : " < < val < < std : : endl ;
} } ;
const string_view top_keys [ ]
{
" origin " ,
" event_id " ,
" room_id " ,
" sender " ,
" type " ,
" depth " ,
" state_key " ,
" redacts " ,
} ;
json : : for_each ( event , top_keys , out ) ;
const auto & ts { json : : get < " origin_server_ts " _ > ( event ) } ;
{
thread_local char buf [ 128 ] ;
s < < std : : setw ( 16 ) < < std : : right < < " origin_server_ts " < < " : "
< < timef ( buf , ts / 1000L , ircd : : localtime )
< < " ( " < < ts < < " ) "
< < std : : endl ;
}
const json : : object & contents { json : : get < " content " _ > ( event ) } ;
if ( ! contents . empty ( ) )
s < < std : : setw ( 16 ) < < std : : right < < " content " < < " : "
< < size ( contents ) < < " keys; "
< < size ( string_view { contents } ) < < " bytes. "
< < std : : endl ;
const auto & hashes { json : : get < " hashes " _ > ( event ) } ;
for ( const auto & hash : hashes )
{
s < < std : : setw ( 16 ) < < std : : right < < " [hash] " < < " : "
< < hash . first
< < " "
< < unquote ( hash . second )
< < std : : endl ;
}
const auto & signatures { json : : get < " signatures " _ > ( event ) } ;
for ( const auto & signature : signatures )
{
s < < std : : setw ( 16 ) < < std : : right < < " [signature] " < < " : "
< < signature . first < < " " ;
for ( const auto & key : json : : object { signature . second } )
s < < key . first < < " " ;
s < < std : : endl ;
}
const auto & auth_events { json : : get < " auth_events " _ > ( event ) } ;
for ( const json : : array auth_event : auth_events )
{
s < < std : : setw ( 16 ) < < std : : right < < " [auth event] "
< < " : " < < unquote ( auth_event [ 0 ] ) ;
for ( const auto & hash : json : : object { auth_event [ 1 ] } )
s < < " " < < unquote ( hash . first )
< < " : " < < unquote ( hash . second ) ;
s < < std : : endl ;
}
const auto & prev_states { json : : get < " prev_state " _ > ( event ) } ;
for ( const json : : array prev_state : prev_states )
{
s < < std : : setw ( 16 ) < < std : : right < < " [prev state] "
< < " : " < < unquote ( prev_state [ 0 ] ) ;
for ( const auto & hash : json : : object { prev_state [ 1 ] } )
s < < " " < < unquote ( hash . first )
< < " : " < < unquote ( hash . second ) ;
s < < std : : endl ;
}
const auto & prev_events { json : : get < " prev_events " _ > ( event ) } ;
for ( const json : : array prev_event : prev_events )
{
s < < std : : setw ( 16 ) < < std : : right < < " [prev_event] "
< < " : " < < unquote ( prev_event [ 0 ] ) ;
for ( const auto & hash : json : : object { prev_event [ 1 ] } )
s < < " " < < unquote ( hash . first )
< < " : " < < unquote ( hash . second ) ;
s < < std : : endl ;
}
if ( ! contents . empty ( ) )
for ( const json : : object : : member & content : contents )
s < < std : : setw ( 16 ) < < std : : right < < " [content] " < < " : "
< < std : : setw ( 7 ) < < std : : left < < reflect ( json : : type ( content . second ) ) < < " "
< < std : : setw ( 5 ) < < std : : right < < size ( string_view { content . second } ) < < " bytes "
< < ' : ' < < content . first
< < std : : endl ;
2019-02-09 23:51:19 +01:00
return s ;
2018-09-04 16:29:04 +02:00
}
2019-02-09 23:51:19 +01:00
std : : ostream &
IRCD_MODULE_EXPORT
ircd : : m : : pretty_oneline ( std : : ostream & s ,
const event & event ,
const bool & content_keys )
2018-09-04 16:29:04 +02:00
{
2019-01-10 22:56:44 +01:00
if ( defined ( json : : get < " room_id " _ > ( event ) ) )
s < < json : : get < " room_id " _ > ( event ) < < " " ;
else
s < < " * " ;
if ( json : : get < " depth " _ > ( event ) ! = json : : undefined_number )
2019-02-26 19:44:56 +01:00
s < < json : : get < " depth " _ > ( event ) < < " " ;
2019-01-10 22:56:44 +01:00
else
2019-02-26 19:44:56 +01:00
s < < " * " ;
2019-01-10 22:56:44 +01:00
2019-02-26 19:44:56 +01:00
if ( json : : get < " origin_server_ts " _ > ( event ) ! = json : : undefined_number )
s < < json : : get < " origin_server_ts " _ > ( event ) < < " " ;
else
s < < " * " ;
if ( defined ( json : : get < " origin " _ > ( event ) ) )
s < < ' : ' < < json : : get < " origin " _ > ( event ) < < " " ;
else
s < < " :* " ;
if ( defined ( json : : get < " sender " _ > ( event ) ) )
s < < json : : get < " sender " _ > ( event ) < < " " ;
else
s < < " * " ;
if ( defined ( json : : get < " event_id " _ > ( event ) ) )
s < < json : : get < " event_id " _ > ( event ) < < " " ;
else
s < < " * " ;
2018-09-04 16:29:04 +02:00
const auto & auth_events { json : : get < " auth_events " _ > ( event ) } ;
s < < " A: " < < auth_events . count ( ) < < " " ;
const auto & prev_states { json : : get < " prev_state " _ > ( event ) } ;
s < < " S: " < < prev_states . count ( ) < < " " ;
const auto & prev_events { json : : get < " prev_events " _ > ( event ) } ;
s < < " E: " < < prev_events . count ( ) < < " " ;
const auto & hashes { json : : get < " hashes " _ > ( event ) } ;
s < < " [ " ;
for ( const auto & hash : hashes )
s < < hash . first < < " " ;
s < < " ] " ;
const auto & signatures { json : : get < " signatures " _ > ( event ) } ;
s < < " [ " ;
for ( const auto & signature : signatures )
{
s < < signature . first < < " [ " ;
for ( const auto & key : json : : object { signature . second } )
s < < key . first < < " " ;
s < < " ] " ;
}
s < < " ] " ;
2019-02-26 19:44:56 +01:00
if ( defined ( json : : get < " type " _ > ( event ) ) )
s < < json : : get < " type " _ > ( event ) < < " " ;
else
s < < " * " ;
2018-09-04 16:29:04 +02:00
const auto & state_key
{
json : : get < " state_key " _ > ( event )
} ;
if ( defined ( state_key ) & & empty ( state_key ) )
s < < " \" \" " < < " " ;
else if ( defined ( state_key ) )
s < < state_key < < " " ;
else
s < < " * " < < " " ;
2019-01-26 00:41:09 +01:00
const string_view & membership
{
json : : get < " type " _ > ( event ) = = " m.room.member " ?
m : : membership ( event ) :
2019-02-26 19:44:56 +01:00
" * " _sv
2019-01-26 00:41:09 +01:00
} ;
2019-02-26 19:44:56 +01:00
s < < membership < < " " ;
if ( defined ( json : : get < " redacts " _ > ( event ) ) )
s < < json : : get < " redacts " _ > ( event ) < < " " ;
else
s < < " * " ;
2018-09-04 16:29:04 +02:00
const json : : object & contents
{
content_keys ?
json : : get < " content " _ > ( event ) :
json : : object { }
} ;
if ( ! contents . empty ( ) )
{
s < < " + " < < string_view { contents } . size ( ) < < " bytes : " ;
for ( const auto & content : contents )
s < < content . first < < " " ;
}
2019-02-09 23:51:19 +01:00
return s ;
2018-09-04 16:29:04 +02:00
}
2019-02-09 23:51:19 +01:00
std : : ostream &
IRCD_MODULE_EXPORT
ircd : : m : : pretty_msgline ( std : : ostream & s ,
const event & event )
2018-09-04 16:29:04 +02:00
{
s < < json : : get < " depth " _ > ( event ) < < " : " ;
s < < json : : get < " type " _ > ( event ) < < " " ;
s < < json : : get < " sender " _ > ( event ) < < " " ;
s < < json : : get < " event_id " _ > ( event ) < < " " ;
const auto & state_key
{
json : : get < " state_key " _ > ( event )
} ;
if ( defined ( state_key ) & & empty ( state_key ) )
s < < " \" \" " < < " " ;
else if ( defined ( state_key ) )
s < < state_key < < " " ;
else
s < < " * " < < " " ;
const json : : object & content
{
json : : get < " content " _ > ( event )
} ;
switch ( hash ( json : : get < " type " _ > ( event ) ) )
{
case " m.room.message " _ :
s < < unquote ( content . get ( " msgtype " ) ) < < " " ;
s < < unquote ( content . get ( " body " ) ) < < " " ;
break ;
default :
s < < string_view { content } ;
break ;
}
2019-02-09 23:51:19 +01:00
return s ;
2018-09-04 16:29:04 +02:00
}
2019-02-09 23:51:19 +01:00
std : : ostream &
IRCD_MODULE_EXPORT
ircd : : m : : pretty ( std : : ostream & s ,
const event : : prev & prev )
2018-09-04 16:29:04 +02:00
{
const auto out { [ & s ]
( const string_view & key , auto & & val )
{
if ( json : : defined ( val ) )
s < < key < < " : " < < val < < std : : endl ;
} } ;
const auto & auth_events { json : : get < " auth_events " _ > ( prev ) } ;
for ( const json : : array auth_event : auth_events )
{
s < < std : : setw ( 16 ) < < std : : right < < " [auth event] "
< < " : " < < unquote ( auth_event [ 0 ] ) ;
for ( const auto & hash : json : : object { auth_event [ 1 ] } )
s < < " " < < unquote ( hash . first )
< < " : " < < unquote ( hash . second ) ;
s < < std : : endl ;
}
const auto & prev_states { json : : get < " prev_state " _ > ( prev ) } ;
for ( const json : : array prev_state : prev_states )
{
s < < std : : setw ( 16 ) < < std : : right < < " [prev state] "
< < " : " < < unquote ( prev_state [ 0 ] ) ;
for ( const auto & hash : json : : object { prev_state [ 1 ] } )
s < < " " < < unquote ( hash . first )
< < " : " < < unquote ( hash . second ) ;
s < < std : : endl ;
}
const auto & prev_events { json : : get < " prev_events " _ > ( prev ) } ;
for ( const json : : array prev_event : prev_events )
{
s < < std : : setw ( 16 ) < < std : : right < < " [prev_event] "
< < " : " < < unquote ( prev_event [ 0 ] ) ;
for ( const auto & hash : json : : object { prev_event [ 1 ] } )
s < < " " < < unquote ( hash . first )
< < " : " < < unquote ( hash . second ) ;
s < < std : : endl ;
}
2019-02-09 23:51:19 +01:00
return s ;
2018-09-04 16:29:04 +02:00
}
2019-02-09 23:51:19 +01:00
std : : ostream &
IRCD_MODULE_EXPORT
ircd : : m : : pretty_oneline ( std : : ostream & s ,
const event : : prev & prev )
2018-09-04 16:29:04 +02:00
{
const auto & auth_events { json : : get < " auth_events " _ > ( prev ) } ;
s < < " A[ " ;
for ( const json : : array auth_event : auth_events )
s < < unquote ( auth_event [ 0 ] ) < < " " ;
s < < " ] " ;
const auto & prev_states { json : : get < " prev_state " _ > ( prev ) } ;
s < < " S[ " ;
for ( const json : : array prev_state : prev_states )
s < < unquote ( prev_state [ 0 ] ) < < " " ;
s < < " ] " ;
const auto & prev_events { json : : get < " prev_events " _ > ( prev ) } ;
s < < " E[ " ;
for ( const json : : array prev_event : prev_events )
s < < unquote ( prev_event [ 0 ] ) < < " " ;
s < < " ] " ;
2019-02-09 23:51:19 +01:00
return s ;
2018-09-04 16:29:04 +02:00
}
2019-02-07 02:49:29 +01:00
2019-03-11 23:43:07 +01:00
void
IRCD_MODULE_EXPORT
ircd : : m : : append ( json : : stack : : object & object ,
const event & event ,
const event_append_opts & opts )
{
const bool has_event_idx
{
opts . event_idx & & * opts . event_idx
} ;
const bool has_client_txnid
{
opts . client_txnid & & * opts . client_txnid
} ;
const bool has_user
{
opts . user_id & & opts . user_room
} ;
const bool sender_is_user
{
has_user & & json : : get < " sender " _ > ( event ) = = * opts . user_id
} ;
const auto txnid_idx
{
2019-03-14 01:03:01 +01:00
! has_client_txnid & & sender_is_user & & opts . query_txnid ?
2019-03-11 23:43:07 +01:00
opts . user_room - > get ( std : : nothrow , " ircd.client.txnid " , at < " event_id " _ > ( event ) ) :
0UL
} ;
2019-03-14 21:31:37 +01:00
# if defined(RB_DEBUG) && 0
2019-03-14 01:03:01 +01:00
if ( ! has_client_txnid & & ! txnid_idx & & sender_is_user & & opts . query_txnid )
2019-03-11 23:43:07 +01:00
log : : dwarning
{
log , " Could not find transaction_id for %s from %s in %s " ,
json : : get < " event_id " _ > ( event ) ,
json : : get < " sender " _ > ( event ) ,
json : : get < " room_id " _ > ( event )
} ;
2019-03-14 21:31:37 +01:00
# endif
2019-03-11 23:43:07 +01:00
2019-03-15 01:31:34 +01:00
if ( ! json : : get < " state_key " _ > ( event ) & & has_user )
{
const m : : user : : ignores ignores { * opts . user_id } ;
if ( ignores . enforce ( " events " ) & & ignores . has ( json : : get < " sender " _ > ( event ) ) )
{
log : : debug
{
log , " Not sending event '%s' because '%s' is ignored by '%s' " ,
json : : get < " event_id " _ > ( event ) ,
json : : get < " sender " _ > ( event ) ,
string_view { * opts . user_id }
} ;
return ;
}
}
2019-03-11 23:43:07 +01:00
object . append ( event ) ;
2019-03-12 00:15:46 +01:00
if ( json : : get < " state_key " _ > ( event ) & & has_event_idx )
{
const auto prev_idx
{
room : : state : : prev ( * opts . event_idx )
} ;
if ( prev_idx )
m : : get ( std : : nothrow , prev_idx , " content " , [ & object ]
( const json : : object & content )
{
json : : stack : : member
{
object , " prev_content " , content
} ;
} ) ;
}
2019-03-11 23:43:07 +01:00
json : : stack : : object unsigned_
{
object , " unsigned "
} ;
2019-03-14 00:58:02 +01:00
json : : stack : : member
{
unsigned_ , " age " , json : : value
2019-03-11 23:43:07 +01:00
{
2019-03-14 00:58:02 +01:00
opts . age ! = std : : numeric_limits < long > : : min ( ) ?
opts . age :
2019-04-15 21:16:48 +02:00
json : : get < " depth " _ > ( event ) > = 0 & & opts . room_depth & & * opts . room_depth > = 0L ?
2019-04-17 01:23:31 +02:00
( * opts . room_depth - json : : get < " depth " _ > ( event ) ) + 1 :
! opts . room_depth & & json : : get < " origin_server_ts " _ > ( event ) ?
2019-03-26 01:56:11 +01:00
ircd : : time < milliseconds > ( ) - json : : get < " origin_server_ts " _ > ( event ) :
2019-04-17 01:23:31 +02:00
1L
2019-03-14 00:58:02 +01:00
}
} ;
2019-03-11 23:43:07 +01:00
2019-03-14 00:58:02 +01:00
if ( has_client_txnid )
2019-03-11 23:43:07 +01:00
json : : stack : : member
{
2019-03-14 00:58:02 +01:00
unsigned_ , " transaction_id " , * opts . client_txnid
2019-03-11 23:43:07 +01:00
} ;
if ( txnid_idx )
m : : get ( std : : nothrow , txnid_idx , " content " , [ & unsigned_ ]
( const json : : object & content )
{
json : : stack : : member
{
unsigned_ , " transaction_id " , unquote ( content . get ( " transaction_id " ) )
} ;
} ) ;
}
2019-02-15 01:48:45 +01:00
bool
IRCD_MODULE_EXPORT
ircd : : m : : event : : auth : : chain : : for_each ( const auth : : chain & c ,
const closure_bool & closure )
{
m : : event : : fetch e , a ;
std : : set < event : : idx > ae ;
std : : deque < event : : idx > aq { c . idx } ; do
{
const auto idx ( aq . front ( ) ) ;
aq . pop_front ( ) ;
if ( ! seek ( e , idx , std : : nothrow ) )
continue ;
const m : : event : : prev prev { e } ;
const auto count ( prev . auth_events_count ( ) ) ;
for ( size_t i ( 0 ) ; i < count & & i < 4 ; + + i )
{
const auto & auth_event_id ( prev . auth_event ( i ) ) ;
const auto & auth_event_idx ( index ( auth_event_id , std : : nothrow ) ) ;
if ( ! auth_event_idx )
continue ;
auto it ( ae . lower_bound ( auth_event_idx ) ) ;
if ( it = = end ( ae ) | | * it ! = auth_event_idx )
{
seek ( a , auth_event_idx , std : : nothrow ) ;
ae . emplace_hint ( it , auth_event_idx ) ;
if ( a . valid )
aq . emplace_back ( auth_event_idx ) ;
}
}
}
while ( ! aq . empty ( ) ) ;
for ( const auto & idx : ae )
2019-02-17 00:03:49 +01:00
if ( ! closure ( idx ) )
2019-02-15 01:48:45 +01:00
return false ;
return true ;
}
2019-02-09 23:51:19 +01:00
void
IRCD_MODULE_EXPORT
ircd : : m : : event : : refs : : rebuild ( )
2019-02-08 12:18:41 +01:00
{
static const size_t pool_size { 96 } ;
static const size_t log_interval { 8192 } ;
db : : txn txn
{
* m : : dbs : : events
} ;
auto & column
{
dbs : : event_json
} ;
auto it
{
column . begin ( )
} ;
ctx : : dock dock ;
ctx : : pool pool ;
pool . min ( pool_size ) ;
2019-02-14 22:11:37 +01:00
2019-02-08 12:18:41 +01:00
size_t i ( 0 ) , j ( 0 ) ;
2019-02-14 22:11:37 +01:00
const ctx : : uninterruptible : : nothrow ui ;
2019-02-08 12:18:41 +01:00
for ( ; it ; + + it )
{
2019-02-14 22:11:37 +01:00
if ( ctx : : interruption_requested ( ) )
break ;
2019-02-08 12:18:41 +01:00
const m : : event : : idx event_idx
{
byte_view < m : : event : : idx > ( it - > first )
} ;
2019-02-14 22:11:37 +01:00
std : : string event { it - > second } ;
2019-02-08 12:18:41 +01:00
pool ( [ & txn , & dock , & i , & j , event ( std : : move ( event ) ) , event_idx ]
{
m : : dbs : : write_opts wopts ;
wopts . event_idx = event_idx ;
2019-02-14 22:11:37 +01:00
m : : dbs : : _index_event_refs ( txn , json : : object { event } , wopts ) ;
2019-02-08 12:18:41 +01:00
if ( + + j % log_interval = = 0 ) log : : info
{
2019-02-14 22:11:37 +01:00
m : : log , " Refs builder @%zu:%zu of %lu (@idx: %lu) " ,
2019-02-08 12:18:41 +01:00
i ,
j ,
2019-03-19 19:45:01 +01:00
m : : vm : : sequence : : retired ,
2019-02-08 12:18:41 +01:00
event_idx
} ;
2019-02-14 22:11:37 +01:00
if ( j > = i )
dock . notify_one ( ) ;
2019-02-08 12:18:41 +01:00
} ) ;
+ + i ;
}
dock . wait ( [ & i , & j ]
{
return i = = j ;
} ) ;
txn ( ) ;
}
2019-02-13 02:50:44 +01:00
ircd : : string_view
IRCD_MODULE_EXPORT
ircd : : m : : event : : auth : : failed ( const m : : event & event )
{
2019-02-13 03:02:51 +01:00
const m : : event : : prev refs ( event ) ;
const auto count ( refs . auth_events_count ( ) ) ;
if ( count > 4 )
return " Too many auth_events references. " ;
const m : : event * authv [ 4 ] ;
const m : : event : : fetch auth [ 4 ]
{
{ count > 0 ? refs . auth_event ( 0 ) : event : : id { } , std : : nothrow } ,
{ count > 1 ? refs . auth_event ( 1 ) : event : : id { } , std : : nothrow } ,
{ count > 2 ? refs . auth_event ( 2 ) : event : : id { } , std : : nothrow } ,
{ count > 3 ? refs . auth_event ( 3 ) : event : : id { } , std : : nothrow } ,
} ;
size_t i ( 0 ) , j ( 0 ) ;
for ( ; i < 4 ; + + i )
if ( auth [ i ] . valid )
authv [ j + + ] = & auth [ i ] ;
return failed ( event , { authv , j } ) ;
2019-02-13 02:50:44 +01:00
}
ircd : : string_view
IRCD_MODULE_EXPORT
ircd : : m : : event : : auth : : failed ( const m : : event & event ,
2019-02-13 03:02:51 +01:00
const vector_view < const m : : event * > & auth_events )
2019-02-13 02:50:44 +01:00
{
2019-02-13 03:02:51 +01:00
const m : : event : : prev refs { event } ;
// 1. If type is m.room.create
if ( json : : get < " type " _ > ( event ) = = " m.room.create " )
{
// a. If it has any previous events, reject.
if ( count ( refs ) | | ! empty ( auth_events ) )
return " m.room.create has previous events. " ;
// b. If the domain of the room_id does not match the domain of the
// sender, reject.
assert ( ! conforms ( event ) . has ( conforms : : MISMATCH_CREATE_SENDER ) ) ;
if ( conforms ( event ) . has ( conforms : : MISMATCH_CREATE_SENDER ) )
return " m.room.create room_id domain does not match sender domain. " ;
// c. If content.room_version is present and is not a recognised
// version, reject.
if ( json : : get < " content " _ > ( event ) . has ( " room_version " ) )
if ( unquote ( json : : get < " content " _ > ( event ) . get ( " room_version " ) ) ! = " 1 " )
return " m.room.create room_version is not recognized. " ;
// d. If content has no creator field, reject.
assert ( ! empty ( json : : get < " content " _ > ( event ) . get ( " creator " ) ) ) ;
if ( empty ( json : : get < " content " _ > ( event ) . get ( " creator " ) ) )
return " m.room.create content.creator is missing. " ;
// e. Otherwise, allow.
return { } ;
}
// 2. Reject if event has auth_events that:
const m : : event * auth_create { nullptr } ;
const m : : event * auth_power { nullptr } ;
const m : : event * auth_join_rules { nullptr } ;
const m : : event * auth_member_target { nullptr } ;
const m : : event * auth_member_sender { nullptr } ;
for ( size_t i ( 0 ) ; i < auth_events . size ( ) ; + + i )
{
// a. have duplicate entries for a given type and state_key pair
assert ( auth_events . at ( i ) ) ;
const m : : event & a ( * auth_events . at ( i ) ) ;
for ( size_t j ( 0 ) ; j < auth_events . size ( ) ; + + j ) if ( i ! = j )
{
assert ( auth_events . at ( j ) ) ;
const auto & b ( * auth_events . at ( j ) ) ;
if ( json : : get < " type " _ > ( a ) = = json : : get < " type " _ > ( b ) )
if ( json : : get < " state_key " _ > ( a ) = = json : : get < " state_key " _ > ( b ) )
return " Duplicate (type,state_key) in auth_events. " ;
}
// b. have entries whose type and state_key don't match those specified by
// the auth events selection algorithm described in the server...
const auto & type ( json : : get < " type " _ > ( a ) ) ;
if ( type = = " m.room.create " )
{
assert ( ! auth_create ) ;
auth_create = & a ;
continue ;
}
if ( type = = " m.room.power_levels " )
{
assert ( ! auth_power ) ;
auth_power = & a ;
continue ;
}
if ( type = = " m.room.join_rules " )
{
assert ( ! auth_join_rules ) ;
auth_join_rules = & a ;
continue ;
}
if ( type = = " m.room.member " )
{
if ( json : : get < " sender " _ > ( event ) = = json : : get < " state_key " _ > ( a ) )
{
assert ( ! auth_member_sender ) ;
auth_member_sender = & a ;
}
if ( json : : get < " state_key " _ > ( event ) = = json : : get < " state_key " _ > ( a ) )
{
assert ( ! auth_member_target ) ;
auth_member_target = & a ;
}
continue ;
}
return " Reference in auth_events is not an auth_event. " ;
}
// 3. If event does not have a m.room.create in its auth_events, reject.
if ( ! auth_create )
return " Missing m.room.create in auth_events " ;
const m : : room : : power power
{
auth_power ? * auth_power : m : : event { } , * auth_create
} ;
// 4. If type is m.room.aliases:
if ( json : : get < " type " _ > ( event ) = = " m.room.aliases " )
{
// a. If event has no state_key, reject.
assert ( ! conforms ( event ) . has ( conforms : : MISMATCH_ALIASES_STATE_KEY ) ) ;
if ( empty ( json : : get < " state_key " _ > ( event ) ) )
return " m.room.aliases event is missing a state_key. " ;
// b. If sender's domain doesn't matches state_key, reject.
if ( json : : get < " state_key " _ > ( event ) ! = m : : user : : id ( json : : get < " sender " _ > ( event ) ) . host ( ) )
return " m.room.aliases event state_key is not the sender's domain. " ;
// c. Otherwise, allow
return { } ;
}
// 5. If type is m.room.member:
if ( json : : get < " type " _ > ( event ) = = " m.room.member " )
{
// a. If no state_key key ...
assert ( ! conforms ( event ) . has ( conforms : : MISSING_MEMBER_STATE_KEY ) ) ;
if ( empty ( json : : get < " state_key " _ > ( event ) ) )
return " m.room.member event is missing a state_key. " ;
// a. ... or membership key in content, reject.
assert ( ! conforms ( event ) . has ( conforms : : MISSING_CONTENT_MEMBERSHIP ) ) ;
if ( empty ( unquote ( json : : get < " content " _ > ( event ) . get ( " membership " ) ) ) )
return " m.room.member event is missing a content.membership. " ;
assert ( ! conforms ( event ) . has ( conforms : : INVALID_MEMBER_STATE_KEY ) ) ;
if ( ! valid ( m : : id : : USER , json : : get < " state_key " _ > ( event ) ) )
return " m.room.member event state_key is not a valid user mxid. " ;
// b. If membership is join
if ( membership ( event ) = = " join " )
{
// i. If the only previous event is an m.room.create and the
// state_key is the creator, allow.
if ( refs . prev_events_count ( ) = = 1 & & refs . auth_events_count ( ) = = 1 )
if ( auth_create & & at < " event_id " _ > ( * auth_create ) = = refs . prev_event ( 0 ) )
return { } ;
// ii. If the sender does not match state_key, reject.
if ( json : : get < " sender " _ > ( event ) ! = json : : get < " state_key " _ > ( event ) )
return " m.room.member membership=join sender does not match state_key. " ;
// iii. If the sender is banned, reject.
if ( auth_member_target )
if ( membership ( * auth_member_target ) = = " ban " )
return " m.room.member membership=join references membership=ban auth_event. " ;
if ( auth_member_sender )
if ( membership ( * auth_member_sender ) = = " ban " )
return " m.room.member membership=join references membership=ban auth_event. " ;
if ( auth_join_rules )
{
// iv. If the join_rule is invite then allow if membership state
// is invite or join.
if ( unquote ( json : : get < " content " _ > ( * auth_join_rules ) . get ( " join_rule " ) ) = = " invite " )
if ( auth_member_target )
{
if ( membership ( * auth_member_target ) = = " invite " )
return { } ;
if ( membership ( * auth_member_target ) = = " join " )
return { } ;
}
// v. If the join_rule is public, allow.
if ( unquote ( json : : get < " content " _ > ( * auth_join_rules ) . get ( " join_rule " ) ) = = " public " )
return { } ;
}
// vi. Otherwise, reject.
return " m.room.member membership=join fails authorization. " ;
}
// c. If membership is invite
if ( membership ( event ) = = " invite " )
{
// i. If content has third_party_invite key
2019-03-23 03:34:49 +01:00
if ( json : : get < " content " _ > ( event ) . has ( " third_party_invite " ) )
{
//TODO: XXX
// 1. If target user is banned, reject.
// 2. If content.third_party_invite does not have a signed key, reject.
// 3. If signed does not have mxid and token keys, reject.
// 4. If mxid does not match state_key, reject.
// 5. If there is no m.room.third_party_invite event in the current room state with state_key matching token, reject.
// 6. If sender does not match sender of the m.room.third_party_invite, reject.
// 7. If any signature in signed matches any public key in the m.room.third_party_invite event, allow. The public keys are in content of m.room.third_party_invite as:
// 7.1. A single public key in the public_key field.
// 7.2. A list of public keys in the public_keys field.
// 8. Otherwise, reject.
return " third_party_invite fails authorization. " ;
}
2019-02-13 03:02:51 +01:00
// ii. If the sender's current membership state is not join, reject.
if ( auth_member_sender )
if ( membership ( * auth_member_sender ) ! = " join " )
return " m.room.member membership=invite sender must have membership=join. " ;
// iii. If target user's current membership state is join or ban, reject.
if ( auth_member_target )
{
if ( membership ( * auth_member_target ) = = " join " )
return " m.room.member membership=invite target cannot have membership=join. " ;
if ( membership ( * auth_member_target ) = = " ban " )
return " m.room.member membership=invite target cannot have membership=ban. " ;
}
// iv. If the sender's power level is greater than or equal to the invite level,
// allow.
if ( power ( at < " sender " _ > ( event ) , " invite " ) )
return { } ;
// v. Otherwise, reject.
return " m.room.member membership=invite fails authorization. " ;
}
// d. If membership is leave
if ( membership ( event ) = = " leave " )
{
// i. If the sender matches state_key, allow if and only if that
// user's current membership state is invite or join.
if ( json : : get < " sender " _ > ( event ) = = json : : get < " state_key " _ > ( event ) )
{
if ( membership ( * auth_member_target ) = = " join " )
return { } ;
if ( membership ( * auth_member_target ) = = " invite " )
return { } ;
return " m.room.member membership=leave self-target must have membership=join|invite. " ;
}
// ii. If the sender's current membership state is not join, reject.
if ( auth_member_sender )
if ( membership ( * auth_member_sender ) ! = " join " )
return " m.room.member membership=leave sender must have membership=join. " ;
// iii. If the target user's current membership state is ban, and the sender's
// power level is less than the ban level, reject.
if ( auth_member_target )
if ( membership ( * auth_member_target ) = = " ban " )
if ( ! power ( at < " sender " _ > ( event ) , " ban " ) )
return " m.room.member membership=ban->leave sender must have ban power to unban. " ;
// iv. If the sender's power level is greater than or equal to the
// kick level, and the target user's power level is less than the
// sender's power level, allow.
if ( power ( at < " sender " _ > ( event ) , " kick " ) )
if ( power . level_user ( at < " state_key " _ > ( event ) ) < power . level_user ( at < " sender " _ > ( event ) ) )
return { } ;
// v. Otherwise, reject.
return " m.room.member membership=leave fails authorization. " ;
}
// e. If membership is ban
if ( membership ( event ) = = " ban " )
{
// i. If the sender's current membership state is not join, reject.
if ( auth_member_sender )
if ( membership ( * auth_member_sender ) ! = " join " )
return " m.room.member membership=ban sender must have membership=join. " ;
// ii. If the sender's power level is greater than or equal to the
// ban level, and the target user's power level is less than the
// sender's power level, allow.
if ( power ( at < " sender " _ > ( event ) , " ban " ) )
if ( power . level_user ( at < " state_key " _ > ( event ) ) < power . level_user ( at < " sender " _ > ( event ) ) )
return { } ;
// iii. Otherwise, reject.
return " m.room.member membership=ban fails authorization. " ;
}
// f. Otherwise, the membership is unknown. Reject.
return " m.room.member membership=unknown. " ;
}
// 6. If the sender's current membership state is not join, reject.
if ( auth_member_sender )
if ( membership ( * auth_member_sender ) ! = " join " )
return " sender is not joined to room. " ;
// 7. If type is m.room.third_party_invite:
if ( json : : get < " type " _ > ( event ) = = " m.room.third_party_invite " )
{
2019-03-23 03:34:49 +01:00
// a. Allow if and only if sender's current power level is greater
// than or equal to the invite level.
if ( power ( at < " sender " _ > ( event ) , " invite " ) )
return { } ;
return " sender has power level less than required for invite. " ;
2019-02-13 03:02:51 +01:00
}
// 8. If the event type's required power level is greater than the
// sender's power level, reject.
2019-02-15 00:08:18 +01:00
if ( ! power ( at < " sender " _ > ( event ) , " events " , at < " type " _ > ( event ) ) )
2019-02-13 03:02:51 +01:00
return " sender has insufficient power for event type. " ;
// 9. If the event has a state_key that starts with an @ and does not
// match the sender, reject.
if ( startswith ( json : : get < " state_key " _ > ( event ) , ' @ ' ) )
if ( at < " state_key " _ > ( event ) ! = at < " sender " _ > ( event ) )
return " sender cannot set another user's mxid in a state_key. " ;
// 10. If type is m.room.power_levels:
if ( json : : get < " type " _ > ( event ) = = " m.room.power_levels " )
{
// a. If users key in content is not a dictionary with keys that are
// valid user IDs with values that are integers (or a string that is
// an integer), reject.
if ( json : : type ( json : : get < " content " _ > ( event ) . get ( " users " ) ) ! = json : : OBJECT )
return " m.room.power_levels content.users is not a json object. " ;
for ( const auto & member : json : : object ( at < " content " _ > ( event ) . at ( " users " ) ) )
{
if ( ! m : : valid ( m : : id : : USER , member . first ) )
return " m.room.power_levels content.users key is not a user mxid " ;
if ( ! try_lex_cast < int64_t > ( unquote ( member . second ) ) )
return " m.room.power_levels content.users value is not an integer. " ;
}
// b. If there is no previous m.room.power_levels event in the room, allow.
if ( ! auth_power )
return { } ;
const m : : room : : power old_power { * auth_power , * auth_create } ;
const m : : room : : power new_power { event , * auth_create } ;
const int64_t current_level
{
old_power . level_user ( at < " sender " _ > ( event ) )
} ;
// c. For each of the keys users_default, events_default,
// state_default, ban, redact, kick, invite, as well as each entry
// being changed under the events or users keys:
static const string_view keys [ ]
{
" users_default " ,
" events_default " ,
" state_default " ,
" ban " ,
" redact " ,
" kick " ,
" invite " ,
} ;
for ( const auto & key : keys )
{
const int64_t old_level ( old_power . level ( key ) ) ;
const int64_t new_level ( new_power . level ( key ) ) ;
if ( old_level = = new_level )
continue ;
// i. If the current value is higher than the sender's current
// power level, reject.
if ( old_level > current_level )
return " m.room.power_levels property denied to sender's power level. " ;
// ii. If the new value is higher than the sender's current power
// level, reject.
if ( new_level > current_level )
return " m.room.power_levels property exceeds sender's power level. " ;
}
string_view ret ;
using closure = m : : room : : power : : closure_bool ;
old_power . for_each ( " users " , closure { [ & ret , & new_power , & current_level ]
( const string_view & user_id , const int64_t & old_level )
{
if ( new_power . has_user ( user_id ) )
if ( new_power . level_user ( user_id ) = = old_level )
return true ;
// i. If the current value is higher than the sender's current
// power level, reject.
if ( old_level > current_level )
{
ret = " m.room.power_levels user property denied to sender's power level. " ;
return false ;
} ;
// ii. If the new value is higher than the sender's current power
// level, reject.
if ( new_power . level_user ( user_id ) > current_level )
{
ret = " m.room.power_levels user property exceeds sender's power level. " ;
return false ;
} ;
return true ;
} } ) ;
if ( ret )
return ret ;
new_power . for_each ( " users " , closure { [ & ret , & old_power , & current_level ]
( const string_view & user_id , const int64_t & new_level )
{
if ( old_power . has_user ( user_id ) )
if ( old_power . level_user ( user_id ) = = new_level )
return true ;
// i. If the current value is higher than the sender's current
// power level, reject.
if ( new_level > current_level )
{
ret = " m.room.power_levels user property exceeds sender's power level. " ;
return false ;
} ;
return true ;
} } ) ;
if ( ret )
return ret ;
old_power . for_each ( " events " , closure { [ & ret , & new_power , & current_level ]
( const string_view & type , const int64_t & old_level )
{
if ( new_power . has_event ( type ) )
if ( new_power . level_event ( type ) = = old_level )
return true ;
// i. If the current value is higher than the sender's current
// power level, reject.
if ( old_level > current_level )
{
ret = " m.room.power_levels event property denied to sender's power level. " ;
return false ;
} ;
// ii. If the new value is higher than the sender's current power
// level, reject.
if ( new_power . level_event ( type ) > current_level )
{
ret = " m.room.power_levels event property exceeds sender's power level. " ;
return false ;
} ;
return true ;
} } ) ;
if ( ret )
return ret ;
new_power . for_each ( " events " , closure { [ & ret , & old_power , & current_level ]
( const string_view & type , const int64_t & new_level )
{
if ( old_power . has_event ( type ) )
if ( old_power . level_event ( type ) = = new_level )
return true ;
// i. If the current value is higher than the sender's current
// power level, reject.
if ( new_level > current_level )
{
ret = " m.room.power_levels event property exceeds sender's power level. " ;
return false ;
} ;
return true ;
} } ) ;
// d. For each entry being changed under the users key...
old_power . for_each ( " users " , closure { [ & ret , & event , & new_power , & current_level ]
( const string_view & user_id , const int64_t & old_level )
{
// ...other than the sender's own entry:
if ( user_id = = at < " sender " _ > ( event ) )
return true ;
if ( new_power . has_user ( user_id ) )
if ( new_power . level_user ( user_id ) = = old_level )
return true ;
// i. If the current value is equal to the sender's current power
// level, reject.
if ( old_level = = current_level )
{
ret = " m.room.power_levels user property denied to sender's power level. " ;
return false ;
} ;
return true ;
} } ) ;
if ( ret )
return ret ;
// e. Otherwise, allow.
assert ( ! ret ) ;
return ret ;
}
// 11. If type is m.room.redaction:
if ( json : : get < " type " _ > ( event ) = = " m.room.redaction " )
{
// a. If the sender's power level is greater than or equal to the
// redact level, allow.
if ( power ( at < " sender " _ > ( event ) , " redact " ) )
return { } ;
// b. If the domain of the event_id of the event being redacted is the
// same as the domain of the event_id of the m.room.redaction, allow.
if ( event : : id ( json : : get < " redacts " _ > ( event ) ) . host ( ) = = event : : id ( at < " event_id " _ > ( event ) ) . host ( ) )
return { } ;
// c. Otherwise, reject.
return " m.room.redaction fails authorization. " ;
}
// 12. Otherwise, allow.
2019-02-13 02:50:44 +01:00
return { } ;
}
bool
IRCD_MODULE_EXPORT
ircd : : m : : event : : auth : : is_power_event ( const m : : event & event )
{
if ( json : : get < " type " _ > ( event ) = = " m.room.create " )
return true ;
if ( json : : get < " type " _ > ( event ) = = " m.room.power_levels " )
return true ;
if ( json : : get < " type " _ > ( event ) = = " m.room.join_rules " )
return true ;
if ( json : : get < " type " _ > ( event ) ! = " m.room.member " )
return false ;
if ( at < " sender " _ > ( event ) = = at < " state_key " _ > ( event ) )
return false ;
if ( membership ( event ) = = " leave " | | membership ( event ) = = " ban " )
return true ;
return false ;
}
2019-02-26 19:28:30 +01:00
void
IRCD_MODULE_EXPORT
ircd : : m : : event : : essential ( json : : iov & event ,
const json : : iov & contents ,
const event : : closure_iov_mutable & closure )
{
const auto & type
{
event . at ( " type " )
} ;
if ( type = = " m.room.aliases " )
{
const json : : iov : : push _content { event ,
{
" content " , json : : members
{
{ " aliases " , contents . at ( " aliases " ) }
}
} } ;
closure ( event ) ;
}
else if ( type = = " m.room.create " )
{
const json : : iov : : push _content { event ,
{
" content " , json : : members
{
{ " creator " , contents . at ( " creator " ) }
}
} } ;
closure ( event ) ;
}
else if ( type = = " m.room.history_visibility " )
{
const json : : iov : : push _content { event ,
{
" content " , json : : members
{
{ " history_visibility " , contents . at ( " history_visibility " ) }
}
} } ;
closure ( event ) ;
}
else if ( type = = " m.room.join_rules " )
{
const json : : iov : : push _content { event ,
{
" content " , json : : members
{
{ " join_rule " , contents . at ( " join_rule " ) }
}
} } ;
closure ( event ) ;
}
else if ( type = = " m.room.member " )
{
const json : : iov : : push _content { event ,
{
" content " , json : : members
{
{ " membership " , contents . at ( " membership " ) }
}
} } ;
closure ( event ) ;
}
else if ( type = = " m.room.power_levels " )
{
const json : : iov : : push _content { event ,
{
" content " , json : : members
{
{ " ban " , contents . at ( " ban " ) } ,
{ " events " , contents . at ( " events " ) } ,
{ " events_default " , contents . at ( " events_default " ) } ,
{ " kick " , contents . at ( " kick " ) } ,
{ " redact " , contents . at ( " redact " ) } ,
{ " state_default " , contents . at ( " state_default " ) } ,
{ " users " , contents . at ( " users " ) } ,
{ " users_default " , contents . at ( " users_default " ) } ,
}
} } ;
closure ( event ) ;
}
else if ( type = = " m.room.redaction " )
{
// This simply finds the redacts key and swaps it with jsundefined for
// the scope's duration. The redacts key will still be present and
// visible in the json::iov which is incorrect if directly serialized.
// However, this iov is turned into a json::tuple (m::event) which ends
// up being serialized for signing. That serialization is where the
// jsundefined redacts value is ignored.
auto & redacts { event . at ( " redacts " ) } ;
json : : value temp ( std : : move ( redacts ) ) ;
redacts = json : : value { } ;
const unwind _ { [ & redacts , & temp ]
{
redacts = std : : move ( temp ) ;
} } ;
const json : : iov : : push _content
{
event , { " content " , " {} " }
} ;
closure ( event ) ;
}
else
{
const json : : iov : : push _content
{
event , { " content " , " {} " }
} ;
closure ( event ) ;
}
}
ircd : : m : : event
IRCD_MODULE_EXPORT
ircd : : m : : essential ( m : : event event ,
const mutable_buffer & contentbuf )
{
const auto & type
{
json : : at < " type " _ > ( event )
} ;
json : : object & content
{
json : : get < " content " _ > ( event )
} ;
mutable_buffer essential
{
contentbuf
} ;
if ( type = = " m.room.aliases " )
{
content = json : : stringify ( essential , json : : members
{
{ " aliases " , unquote ( content . at ( " aliases " ) ) }
} ) ;
}
else if ( type = = " m.room.create " )
{
content = json : : stringify ( essential , json : : members
{
{ " creator " , unquote ( content . at ( " creator " ) ) }
} ) ;
}
else if ( type = = " m.room.history_visibility " )
{
content = json : : stringify ( essential , json : : members
{
{ " history_visibility " , unquote ( content . at ( " history_visibility " ) ) }
} ) ;
}
else if ( type = = " m.room.join_rules " )
{
content = json : : stringify ( essential , json : : members
{
{ " join_rule " , unquote ( content . at ( " join_rule " ) ) }
} ) ;
}
else if ( type = = " m.room.member " )
{
content = json : : stringify ( essential , json : : members
{
{ " membership " , unquote ( content . at ( " membership " ) ) }
} ) ;
}
else if ( type = = " m.room.power_levels " )
{
content = json : : stringify ( essential , json : : members
{
{ " ban " , unquote ( content . at ( " ban " ) ) } ,
{ " events " , unquote ( content . at ( " events " ) ) } ,
{ " events_default " , unquote ( content . at ( " events_default " ) ) } ,
{ " kick " , unquote ( content . at ( " kick " ) ) } ,
{ " redact " , unquote ( content . at ( " redact " ) ) } ,
{ " state_default " , unquote ( content . at ( " state_default " ) ) } ,
{ " users " , unquote ( content . at ( " users " ) ) } ,
{ " users_default " , unquote ( content . at ( " users_default " ) ) } ,
} ) ;
}
else if ( type = = " m.room.redaction " )
{
json : : get < " redacts " _ > ( event ) = string_view { } ;
content = " {} " _sv ;
}
else
{
content = " {} " _sv ;
}
json : : get < " signatures " _ > ( event ) = { } ;
return event ;
}