2018-03-29 04:54:12 +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-07-26 02:08:27 +02:00
namespace ircd : : m
{
static void affect_user_room ( const event & , vm : : eval & ) ;
extern m : : hookfn < m : : vm : : eval & > affect_user_room_hookfn ;
static void auth_room_member_ban ( const event & , event : : auth : : hookdata & ) ;
extern m : : hookfn < event : : auth : : hookdata & > auth_room_member_ban_hookfn ;
static void auth_room_member_leave ( const event & , event : : auth : : hookdata & ) ;
extern m : : hookfn < event : : auth : : hookdata & > auth_room_member_leave_hookfn ;
static void auth_room_member_invite ( const event & , event : : auth : : hookdata & ) ;
extern m : : hookfn < event : : auth : : hookdata & > auth_room_member_invite_hookfn ;
static void auth_room_member_join ( const event & , event : : auth : : hookdata & ) ;
extern m : : hookfn < event : : auth : : hookdata & > auth_room_member_join_hookfn ;
static void auth_room_member ( const event & , event : : auth : : hookdata & ) ;
extern m : : hookfn < event : : auth : : hookdata & > auth_room_member_hookfn ;
}
2018-03-29 04:54:12 +02:00
2019-07-26 02:08:27 +02:00
ircd : : mapi : : header
2018-04-03 22:12:04 +02:00
IRCD_MODULE
{
" Matrix m.room.member "
} ;
2019-07-26 02:08:27 +02:00
decltype ( ircd : : m : : affect_user_room_hookfn )
ircd : : m : : affect_user_room_hookfn
{
affect_user_room ,
{
{ " _site " , " vm.effect " } ,
{ " type " , " m.room.member " } ,
}
} ;
void
ircd : : m : : affect_user_room ( const m : : event & event ,
m : : vm : : eval & eval )
2019-07-26 20:59:14 +02:00
try
2018-04-13 23:54:11 +02:00
{
const auto & room_id
{
at < " room_id " _ > ( event )
} ;
const auto & sender
{
at < " sender " _ > ( event )
} ;
const m : : user : : id & subject
{
at < " state_key " _ > ( event )
} ;
//TODO: ABA / TXN
if ( ! exists ( subject ) )
create ( subject ) ;
//TODO: ABA / TXN
2019-07-26 20:59:14 +02:00
m : : user : : room user_room
{
subject
} ;
2018-04-13 23:54:11 +02:00
send ( user_room , sender , " ircd.member " , room_id , at < " content " _ > ( event ) ) ;
}
2019-07-26 20:59:14 +02:00
catch ( const std : : exception & e )
{
log : : error
{
log , " Failed to update user %s room for membership change in %s by %s :%s " ,
json : : get < " state_key " _ > ( event ) ,
json : : get < " room_id " _ > ( event ) ,
json : : get < " sender " _ > ( event ) ,
e . what ( )
} ;
}
2018-04-13 23:54:11 +02:00
2019-07-26 02:08:27 +02:00
decltype ( ircd : : m : : auth_room_member_hookfn )
ircd : : m : : auth_room_member_hookfn
{
auth_room_member ,
{
{ " _site " , " event.auth " } ,
{ " type " , " m.room.member " } ,
}
} ;
void
ircd : : m : : auth_room_member ( const m : : event & event ,
event : : auth : : hookdata & data )
{
using FAIL = m : : event : : auth : : FAIL ;
// 5. If type is m.room.member:
assert ( json : : get < " type " _ > ( event ) = = " m.room.member " ) ;
// a. If no state_key key ...
if ( empty ( json : : get < " state_key " _ > ( event ) ) )
throw FAIL
{
" m.room.member event is missing a state_key. "
} ;
// a. ... or membership key in content, reject.
if ( empty ( unquote ( json : : get < " content " _ > ( event ) . get ( " membership " ) ) ) )
throw FAIL
{
" m.room.member event is missing a content.membership. "
} ;
if ( ! valid ( m : : id : : USER , json : : get < " state_key " _ > ( event ) ) )
throw FAIL
{
" m.room.member event state_key is not a valid user mxid. "
} ;
const auto & membership
{
m : : membership ( event )
} ;
// b. If membership is join
if ( membership = = " join " )
return ; // see hook handler
// c. If membership is invite
if ( membership = = " invite " )
return ; // see hook handler
// d. If membership is leave
if ( membership = = " leave " )
return ; // see hook handler
// e. If membership is ban
if ( membership = = " ban " )
return ; // see hook handler
// f. Otherwise, the membership is unknown. Reject.
throw FAIL
{
" m.room.member membership=unknown. "
} ;
}
decltype ( ircd : : m : : auth_room_member_join_hookfn )
ircd : : m : : auth_room_member_join_hookfn
2018-04-13 23:54:11 +02:00
{
2019-07-26 02:08:27 +02:00
auth_room_member_join ,
2018-04-13 23:54:11 +02:00
{
2019-07-26 02:08:27 +02:00
{ " _site " , " event.auth " } ,
{ " type " , " m.room.member " } ,
{ " content " ,
{
{ " membership " , " join " } ,
} } ,
}
2018-04-13 23:54:11 +02:00
} ;
2019-07-26 02:08:27 +02:00
void
ircd : : m : : auth_room_member_join ( const m : : event & event ,
event : : auth : : hookdata & data )
2018-03-29 04:54:12 +02:00
{
2019-07-26 02:08:27 +02:00
using FAIL = m : : event : : auth : : FAIL ;
2018-03-29 04:54:12 +02:00
2019-07-26 02:08:27 +02:00
// b. If membership is join
assert ( membership ( event ) = = " join " ) ;
// i. If the only previous event is an m.room.create and the
// state_key is the creator, allow.
const m : : event : : prev prev ( event ) ;
if ( prev . prev_events_count ( ) = = 1 & & prev . auth_events_count ( ) = = 1 )
if ( data . auth_create & & data . auth_create - > event_id = = prev . prev_event ( 0 ) )
{
data . allow = true ;
return ;
}
// ii. If the sender does not match state_key, reject.
if ( json : : get < " sender " _ > ( event ) ! = json : : get < " state_key " _ > ( event ) )
throw FAIL
{
" m.room.member membership=join sender does not match state_key. "
} ;
// iii. If the sender is banned, reject.
2019-08-10 09:28:43 +02:00
if ( ! data . auth_member_sender )
throw FAIL
{
" m.room.member membership=join missing sender member auth event. "
} ;
2019-07-26 02:08:27 +02:00
2019-08-10 09:28:43 +02:00
if ( membership ( * data . auth_member_sender ) = = " ban " )
throw FAIL
{
" m.room.member membership=join references membership=ban auth_event. "
} ;
const json : : string & join_rule
{
data . auth_join_rules ?
json : : get < " content " _ > ( * data . auth_join_rules ) . get ( " join_rule " ) :
" invite " _sv
} ;
// iv. If the join_rule is invite then allow if membership state
// is invite or join.
if ( join_rule = = " invite " )
{
if ( ! data . auth_member_target )
2019-07-26 02:08:27 +02:00
throw FAIL
{
2019-08-10 09:28:43 +02:00
" m.room.member membership=join missing target member auth event. "
2019-07-26 02:08:27 +02:00
} ;
2019-08-10 09:28:43 +02:00
const auto membership_state
{
membership ( * data . auth_member_target )
} ;
if ( membership_state = = " invite " )
{
data . allow = true ;
return ;
}
if ( membership_state = = " join " )
2019-07-26 02:08:27 +02:00
{
data . allow = true ;
return ;
}
}
2019-08-10 09:28:43 +02:00
// v. If the join_rule is public, allow.
if ( join_rule = = " public " )
{
data . allow = true ;
return ;
}
2019-07-26 02:08:27 +02:00
// vi. Otherwise, reject.
throw FAIL
{
" m.room.member membership=join fails authorization. "
} ;
2018-03-29 04:54:12 +02:00
}
2019-07-26 02:08:27 +02:00
decltype ( ircd : : m : : auth_room_member_invite_hookfn )
ircd : : m : : auth_room_member_invite_hookfn
2018-03-29 04:54:12 +02:00
{
2019-07-26 02:08:27 +02:00
auth_room_member_invite ,
2018-03-29 04:54:12 +02:00
{
2019-07-26 02:08:27 +02:00
{ " _site " , " event.auth " } ,
{ " type " , " m.room.member " } ,
{ " content " ,
{
{ " membership " , " invite " } ,
} } ,
}
2018-03-29 04:54:12 +02:00
} ;
2019-07-26 02:08:27 +02:00
void
ircd : : m : : auth_room_member_invite ( const m : : event & event ,
event : : auth : : hookdata & data )
{
using FAIL = m : : event : : auth : : FAIL ;
// c. If membership is invite
assert ( membership ( event ) = = " invite " ) ;
// i. If content has third_party_invite key
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.
throw FAIL
{
" third_party_invite fails authorization. "
} ;
}
2019-08-10 09:28:43 +02:00
if ( ! data . auth_member_sender )
throw FAIL
{
" m.room.member membership=invite missing sender member auth event. "
} ;
2019-07-26 02:08:27 +02:00
// ii. If the sender's current membership state is not join, reject.
2019-08-10 09:28:43 +02:00
if ( membership ( * data . auth_member_sender ) ! = " join " )
throw FAIL
{
" m.room.member membership=invite sender must have membership=join. "
} ;
if ( ! data . auth_member_target )
throw FAIL
{
" m.room.member membership=invite missing target member auth event. "
} ;
2019-07-26 02:08:27 +02:00
// iii. If target user's current membership state is join or ban, reject.
2019-08-10 09:28:43 +02:00
if ( membership ( * data . auth_member_target ) = = " join " )
throw FAIL
{
" m.room.member membership=invite target cannot have membership=join. "
} ;
2019-07-26 02:08:27 +02:00
2019-08-10 09:28:43 +02:00
if ( membership ( * data . auth_member_target ) = = " ban " )
throw FAIL
{
" m.room.member membership=invite target cannot have membership=ban. "
} ;
2019-07-26 02:08:27 +02:00
// iv. If the sender's power level is greater than or equal to the invite level,
// allow.
const m : : room : : power power
{
data . auth_power ? * data . auth_power : m : : event { } , * data . auth_create
} ;
if ( power ( at < " sender " _ > ( event ) , " invite " ) )
{
data . allow = true ;
return ;
}
// v. Otherwise, reject.
throw FAIL
{
" m.room.member membership=invite fails authorization. "
} ;
}
decltype ( ircd : : m : : auth_room_member_leave_hookfn )
ircd : : m : : auth_room_member_leave_hookfn
{
auth_room_member_leave ,
{
{ " _site " , " event.auth " } ,
{ " type " , " m.room.member " } ,
{ " content " ,
{
{ " membership " , " leave " } ,
} } ,
}
} ;
void
ircd : : m : : auth_room_member_leave ( const m : : event & event ,
event : : auth : : hookdata & data )
{
using FAIL = m : : event : : auth : : FAIL ;
// d. If membership is leave
assert ( 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 ( data . auth_member_target & & membership ( * data . auth_member_target ) = = " join " )
{
data . allow = true ;
return ;
}
if ( data . auth_member_target & & membership ( * data . auth_member_target ) = = " invite " )
{
data . allow = true ;
return ;
}
throw FAIL
{
" m.room.member membership=leave self-target must have membership=join|invite. "
} ;
}
2019-08-10 09:28:43 +02:00
if ( ! data . auth_member_sender )
throw FAIL
{
" m.room.member membership=leave missing sender member auth event. "
} ;
2019-07-26 02:08:27 +02:00
// ii. If the sender's current membership state is not join, reject.
2019-08-10 09:28:43 +02:00
if ( membership ( * data . auth_member_sender ) ! = " join " )
throw FAIL
{
" m.room.member membership=leave sender must have membership=join. "
} ;
2019-07-26 02:08:27 +02:00
const m : : room : : power power
{
data . auth_power ? * data . auth_power : m : : event { } , * data . auth_create
} ;
2019-08-10 09:28:43 +02:00
if ( ! data . auth_member_target )
throw FAIL
{
" m.room.member membership=leave missing target member auth event. "
} ;
2019-07-26 02:08:27 +02:00
// iii. If the target user's current membership state is ban, and the sender's
// power level is less than the ban level, reject.
2019-08-10 09:28:43 +02:00
if ( membership ( * data . auth_member_target ) = = " ban " )
if ( ! power ( at < " sender " _ > ( event ) , " ban " ) )
throw FAIL
{
" m.room.member membership=ban->leave sender must have ban power to unban. "
} ;
2019-07-26 02:08:27 +02:00
// 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 ) ) )
{
data . allow = true ;
return ;
}
// v. Otherwise, reject.
throw FAIL
{
" m.room.member membership=leave fails authorization. "
} ;
}
decltype ( ircd : : m : : auth_room_member_ban_hookfn )
ircd : : m : : auth_room_member_ban_hookfn
{
auth_room_member_ban ,
{
{ " _site " , " event.auth " } ,
{ " type " , " m.room.member " } ,
{ " content " ,
{
{ " membership " , " ban " } ,
} } ,
}
} ;
void
ircd : : m : : auth_room_member_ban ( const m : : event & event ,
event : : auth : : hookdata & data )
{
using FAIL = m : : event : : auth : : FAIL ;
// e. If membership is ban
assert ( membership ( event ) = = " ban " ) ;
2019-08-10 09:28:43 +02:00
if ( ! data . auth_member_sender )
throw FAIL
{
" m.room.member membership=ban missing sender member auth event. "
} ;
2019-07-26 02:08:27 +02:00
// i. If the sender's current membership state is not join, reject.
2019-08-10 09:28:43 +02:00
if ( membership ( * data . auth_member_sender ) ! = " join " )
throw FAIL
{
" m.room.member membership=ban sender must have membership=join. "
} ;
2019-07-26 02:08:27 +02:00
const m : : room : : power power
{
data . auth_power ? * data . auth_power : m : : event { } , * data . auth_create
} ;
// 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 ) ) )
{
data . allow = true ;
return ;
}
// iii. Otherwise, reject.
throw FAIL
{
" m.room.member membership=ban fails authorization. "
} ;
}