diff --git a/phpBB/adm/style/acp_ban.html b/phpBB/adm/style/acp_ban.html index ffea40027e..147eae106b 100644 --- a/phpBB/adm/style/acp_ban.html +++ b/phpBB/adm/style/acp_ban.html @@ -11,11 +11,11 @@ @@ -60,11 +73,6 @@
-
-

{L_BAN_EXCLUDE_EXPLAIN}
-
-
-
@@ -90,44 +98,41 @@

{L_UNBAN_EXPLAIN}

-
+
- {L_UNBAN_TITLE} + {{ lang('UNBAN_TITLE') }} - +{% if BANNED_SELECT %}
-
-
+
+
+ {{ FormsSelect(BANNED_SELECT) }} +
-
+
-
+
-
+

-   - +   +

- {S_FORM_TOKEN} -
- - - -

{L_NO_BAN_CELL}

- {S_FORM_TOKEN} +{% else %} +

{{ lang('NO_BAN_CELL') }}

+{% endif %} + {{ S_FORM_TOKEN }} - -
diff --git a/phpBB/adm/style/admin.css b/phpBB/adm/style/admin.css index 6924f6d890..f6598331eb 100644 --- a/phpBB/adm/style/admin.css +++ b/phpBB/adm/style/admin.css @@ -288,6 +288,10 @@ li { padding-right: 10px; } +.w-50 { + width: 50%; +} + @media only screen and (max-width: 700px), only screen and (max-device-width: 700px) { #wrap, #page-body, diff --git a/phpBB/config/default/container/services.yml b/phpBB/config/default/container/services.yml index 59d52073f9..9b01005852 100644 --- a/phpBB/config/default/container/services.yml +++ b/phpBB/config/default/container/services.yml @@ -2,6 +2,7 @@ imports: - { resource: services_attachment.yml } - { resource: services_auth.yml } - { resource: services_avatar.yml } + - { resource: services_ban.yml } - { resource: services_captcha.yml } - { resource: services_console.yml } - { resource: services_content.yml } diff --git a/phpBB/config/default/container/services_ban.yml b/phpBB/config/default/container/services_ban.yml new file mode 100644 index 0000000000..c9e7c6ddb0 --- /dev/null +++ b/phpBB/config/default/container/services_ban.yml @@ -0,0 +1,54 @@ +services: +# ----- Ban management ----- + ban.manager: + class: \phpbb\ban\manager + arguments: + - '@ban.type_collection' + - '@cache.driver' + - '@dbal.conn' + - '@language' + - '@log' + - '@user' + - '%tables.bans%' + - '%tables.users%' + +# ----- Ban types ----- + ban.type_collection: + class: \phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: ban.type } + + ban.type.email: + class: \phpbb\ban\type\email + arguments: + - '@dbal.conn' + - '%tables.bans%' + - '%tables.users%' + - '%tables.sessions%' + - '%tables.sessions_keys%' + tags: + - { name: ban.type } + + ban.type.ip: + class: \phpbb\ban\type\ip + arguments: + - '@dbal.conn' + - '%tables.bans%' + - '%tables.users%' + - '%tables.sessions%' + - '%tables.sessions_keys%' + tags: + - { name: ban.type } + + ban.type.user: + class: \phpbb\ban\type\user + arguments: + - '@dbal.conn' + - '%tables.bans%' + - '%tables.users%' + - '%tables.sessions%' + - '%tables.sessions_keys%' + tags: + - { name: ban.type } diff --git a/phpBB/config/default/container/tables.yml b/phpBB/config/default/container/tables.yml index 2117794b43..005f3ba927 100644 --- a/phpBB/config/default/container/tables.yml +++ b/phpBB/config/default/container/tables.yml @@ -9,7 +9,7 @@ parameters: tables.auth_provider_oauth_states: '%core.table_prefix%oauth_states' tables.auth_provider_oauth_account_assoc: '%core.table_prefix%oauth_accounts' tables.backups: '%core.table_prefix%backups' - tables.banlist: '%core.table_prefix%banlist' + tables.bans: '%core.table_prefix%bans' tables.bbcodes: '%core.table_prefix%bbcodes' tables.bookmarks: '%core.table_prefix%bookmarks' tables.bots: '%core.table_prefix%bots' diff --git a/phpBB/includes/acp/acp_ban.php b/phpBB/includes/acp/acp_ban.php index 22ae9d4e7b..61f3096b74 100644 --- a/phpBB/includes/acp/acp_ban.php +++ b/phpBB/includes/acp/acp_ban.php @@ -14,6 +14,11 @@ /** * @ignore */ + +use phpbb\ban\exception\type_not_found_exception; +use phpbb\ban\manager; +use phpbb\language\language; + if (!defined('IN_PHPBB')) { exit; @@ -25,9 +30,12 @@ class acp_ban function main($id, $mode) { - global $user, $template, $request, $phpbb_dispatcher; + global $language, $template, $request, $phpbb_dispatcher, $phpbb_container; global $phpbb_root_path, $phpEx; + /** @var manager $ban_manager */ + $ban_manager = $phpbb_container->get('ban.manager'); + if (!function_exists('user_ban')) { include($phpbb_root_path . 'includes/functions_user.' . $phpEx); @@ -36,14 +44,15 @@ class acp_ban $bansubmit = $request->is_set_post('bansubmit'); $unbansubmit = $request->is_set_post('unbansubmit'); - $user->add_lang(array('acp/ban', 'acp/users')); + /** @var language $language */ + $language->add_lang(['acp/ban', 'acp/users']); $this->tpl_name = 'acp_ban'; $form_key = 'acp_ban'; add_form_key($form_key); if (($bansubmit || $unbansubmit) && !check_form_key($form_key)) { - trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + trigger_error($language->lang('FORM_INVALID') . adm_back_link($this->u_action), E_USER_WARNING); } // Ban submitted? @@ -53,7 +62,6 @@ class acp_ban $ban = $request->variable('ban', '', true); $ban_length = $request->variable('banlength', 0); $ban_length_other = $request->variable('banlengthother', ''); - $ban_exclude = $request->variable('banexclude', 0); $ban_reason = $request->variable('banreason', '', true); $ban_give_reason = $request->variable('bangivereason', '', true); @@ -68,7 +76,6 @@ class acp_ban * @var string ban Either string or array with usernames, ips or email addresses * @var int ban_length Ban length in minutes * @var string ban_length_other Ban length as a date (YYYY-MM-DD) - * @var bool ban_exclude Are we banning or excluding from another ban * @var string ban_reason Ban reason displayed to moderators * @var string ban_give_reason Ban reason displayed to the banned user * @var mixed abort_ban Either false, or an error message that is displayed to the user. @@ -80,7 +87,6 @@ class acp_ban 'ban', 'ban_length', 'ban_length_other', - 'ban_exclude', 'ban_reason', 'ban_give_reason', 'abort_ban', @@ -91,7 +97,20 @@ class acp_ban { trigger_error($abort_ban . adm_back_link($this->u_action)); } - user_ban($mode, $ban, $ban_length, $ban_length_other, $ban_exclude, $ban_reason, $ban_give_reason); + + $ban_start = new \DateTime(); + $ban_start->setTimestamp(time()); + $ban_end = $ban_manager->get_ban_end($ban_start, $ban_length, $ban_length_other); + + $ban = explode("\n", $ban); + try + { + $ban_manager->ban($mode, $ban, $ban_start, $ban_end, $ban_reason, $ban_give_reason); + } + catch (\phpbb\exception\exception_interface $exception) + { + trigger_error($language->lang_array($exception->getMessage(), $exception->get_parameters()), E_USER_WARNING); + } /** * Use this event to perform actions after the ban has been performed @@ -101,7 +120,6 @@ class acp_ban * @var string ban Either string or array with usernames, ips or email addresses * @var int ban_length Ban length in minutes * @var string ban_length_other Ban length as a date (YYYY-MM-DD) - * @var bool ban_exclude Are we banning or excluding from another ban * @var string ban_reason Ban reason displayed to moderators * @var string ban_give_reason Ban reason displayed to the banned user * @since 3.1.0-RC5 @@ -111,49 +129,50 @@ class acp_ban 'ban', 'ban_length', 'ban_length_other', - 'ban_exclude', 'ban_reason', 'ban_give_reason', ); extract($phpbb_dispatcher->trigger_event('core.acp_ban_after', compact($vars))); - trigger_error($user->lang['BAN_UPDATE_SUCCESSFUL'] . adm_back_link($this->u_action)); + trigger_error($language->lang('BAN_UPDATE_SUCCESSFUL') . adm_back_link($this->u_action)); } } else if ($unbansubmit) { - $ban = $request->variable('unban', array('')); + $ban = $request->variable('unban', ['']); if ($ban) { - user_unban($mode, $ban); + $ban_manager->unban($mode, $ban); - trigger_error($user->lang['BAN_UPDATE_SUCCESSFUL'] . adm_back_link($this->u_action)); + trigger_error($language->lang('BAN_UPDATE_SUCCESSFUL') . adm_back_link($this->u_action)); } } // Define language vars - $this->page_title = $user->lang[strtoupper($mode) . '_BAN']; + $this->page_title = $language->lang(strtoupper($mode) . '_BAN'); - $l_ban_explain = $user->lang[strtoupper($mode) . '_BAN_EXPLAIN']; - $l_ban_exclude_explain = $user->lang[strtoupper($mode) . '_BAN_EXCLUDE_EXPLAIN']; - $l_unban_title = $user->lang[strtoupper($mode) . '_UNBAN']; - $l_unban_explain = $user->lang[strtoupper($mode) . '_UNBAN_EXPLAIN']; - $l_no_ban_cell = $user->lang[strtoupper($mode) . '_NO_BANNED']; + $l_ban_explain = $language->lang(strtoupper($mode) . '_BAN_EXPLAIN'); + $l_unban_title = $language->lang(strtoupper($mode) . '_UNBAN'); + $l_unban_explain = $language->lang(strtoupper($mode) . '_UNBAN_EXPLAIN'); + $l_no_ban_cell = $language->lang(strtoupper($mode) . '_NO_BANNED'); switch ($mode) { case 'user': - $l_ban_cell = $user->lang['USERNAME']; + $l_ban_cell = $language->lang('USERNAME'); break; case 'ip': - $l_ban_cell = $user->lang['IP_HOSTNAME']; + $l_ban_cell = $language->lang('IP_HOSTNAME'); break; case 'email': - $l_ban_cell = $user->lang['EMAIL_ADDRESS']; + $l_ban_cell = $language->lang('EMAIL_ADDRESS'); break; + + default: + throw new type_not_found_exception(); } display_ban_end_options(); @@ -165,10 +184,9 @@ class acp_ban 'L_UNBAN_TITLE' => $l_unban_title, 'L_UNBAN_EXPLAIN' => $l_unban_explain, 'L_BAN_CELL' => $l_ban_cell, - 'L_BAN_EXCLUDE_EXPLAIN' => $l_ban_exclude_explain, 'L_NO_BAN_CELL' => $l_no_ban_cell, - 'S_USERNAME_BAN' => ($mode == 'user') ? true : false, + 'S_USERNAME_BAN' => $mode == 'user' ? true : false, 'U_ACTION' => $this->u_action, 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=acp_ban&field=ban'), diff --git a/phpBB/includes/acp/acp_email.php b/phpBB/includes/acp/acp_email.php index 3fba4917ec..cddf7b3324 100644 --- a/phpBB/includes/acp/acp_email.php +++ b/phpBB/includes/acp/acp_email.php @@ -26,7 +26,7 @@ class acp_email function main($id, $mode) { global $config, $db, $user, $template, $phpbb_log, $request; - global $phpbb_root_path, $phpbb_admin_path, $phpEx, $phpbb_dispatcher; + global $phpbb_root_path, $phpbb_admin_path, $phpEx, $phpbb_dispatcher, $phpbb_container; $user->add_lang('acp/email'); $this->tpl_name = 'acp_email'; @@ -74,7 +74,7 @@ class acp_email { // If giving usernames the admin is able to email inactive users too... $sql_ary = array( - 'SELECT' => 'username, user_email, user_jabber, user_notify_type, user_lang', + 'SELECT' => 'user_id, username, user_email, user_jabber, user_notify_type, user_lang', 'FROM' => array( USERS_TABLE => '', ), @@ -88,7 +88,7 @@ class acp_email if ($group_id) { $sql_ary = array( - 'SELECT' => 'u.user_email, u.username, u.username_clean, u.user_lang, u.user_jabber, u.user_notify_type', + 'SELECT' => 'u.user_id, u.user_email, u.username, u.username_clean, u.user_lang, u.user_jabber, u.user_notify_type', 'FROM' => array( USERS_TABLE => 'u', USER_GROUP_TABLE => 'ug', @@ -104,7 +104,7 @@ class acp_email else { $sql_ary = array( - 'SELECT' => 'u.username, u.username_clean, u.user_email, u.user_jabber, u.user_lang, u.user_notify_type', + 'SELECT' => 'u.user_id, u.username, u.username_clean, u.user_email, u.user_jabber, u.user_lang, u.user_notify_type', 'FROM' => array( USERS_TABLE => 'u', ), @@ -113,21 +113,6 @@ class acp_email 'ORDER_BY' => 'u.user_lang, u.user_notify_type', ); } - - // Mail banned or not - if (!isset($_REQUEST['mail_banned_flag'])) - { - $sql_ary['WHERE'] .= ' AND (b.ban_id IS NULL - OR b.ban_exclude = 1)'; - $sql_ary['LEFT_JOIN'] = array( - array( - 'FROM' => array( - BANLIST_TABLE => 'b', - ), - 'ON' => 'u.user_id = b.ban_userid', - ), - ); - } } /** * Modify sql query to change the list of users the email is sent to @@ -141,11 +126,22 @@ class acp_email $sql = $db->sql_build_query('SELECT', $sql_ary); $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); + $rows = $db->sql_fetchrowset($result); + $db->sql_freeresult($result); - if (!$row) + if (!empty($rows) && !$request->is_set('mail_banned_flag')) + { + /** @var \phpbb\ban\manager $ban_manager */ + $ban_manager = $phpbb_container->get('ban.manager'); + $banned_users = $ban_manager->get_banned_users(); + + $rows = array_filter($rows, function ($row) use ($banned_users) { + return !isset($banned_users[(int) $row['user_id']]); + }); + } + + if (empty($rows)) { - $db->sql_freeresult($result); trigger_error($user->lang['NO_USER'] . adm_back_link($this->u_action), E_USER_WARNING); } @@ -155,10 +151,10 @@ class acp_email // Maximum number of bcc recipients $max_chunk_size = (int) $config['email_max_chunk_size']; $email_list = array(); - $old_lang = $row['user_lang']; - $old_notify_type = $row['user_notify_type']; + $old_lang = $rows[0]['user_lang']; + $old_notify_type = $rows[0]['user_notify_type']; - do + foreach ($rows as $row) { if (($row['user_notify_type'] == NOTIFY_EMAIL && $row['user_email']) || ($row['user_notify_type'] == NOTIFY_IM && $row['user_jabber']) || @@ -185,8 +181,6 @@ class acp_email $i++; } } - while ($row = $db->sql_fetchrow($result)); - $db->sql_freeresult($result); // Send the messages if (!class_exists('messenger')) diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index a56a583307..f560dbfbcb 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -239,7 +239,7 @@ define('ACL_ROLES_TABLE', $table_prefix . 'acl_roles'); define('ACL_USERS_TABLE', $table_prefix . 'acl_users'); define('ATTACHMENTS_TABLE', $table_prefix . 'attachments'); define('BACKUPS_TABLE', $table_prefix . 'backups'); -define('BANLIST_TABLE', $table_prefix . 'banlist'); +define('BANS_TABLE', $table_prefix . 'bans'); define('BBCODES_TABLE', $table_prefix . 'bbcodes'); define('BOOKMARKS_TABLE', $table_prefix . 'bookmarks'); define('BOTS_TABLE', $table_prefix . 'bots'); diff --git a/phpBB/includes/functions_admin.php b/phpBB/includes/functions_admin.php index 11a672a2ed..ea9ddbaf3c 100644 --- a/phpBB/includes/functions_admin.php +++ b/phpBB/includes/functions_admin.php @@ -3156,109 +3156,68 @@ function display_ban_end_options() */ function display_ban_options($mode) { - global $user, $db, $template; + global $language, $user, $template, $phpbb_container; - switch ($mode) + /** @var \phpbb\ban\manager $ban_manager */ + $ban_manager = $phpbb_container->get('ban.manager'); + $ban_rows = $ban_manager->get_bans($mode); + + $banned_options = []; + + foreach ($ban_rows as $ban_row) { - case 'user': + $banned_options[] = [ + 'value' => $ban_row['ban_id'], + 'label' => $ban_row['label'] ?? $ban_row['ban_item'], + ]; - $field = 'username'; - - $sql = 'SELECT b.*, u.user_id, u.username, u.username_clean - FROM ' . BANLIST_TABLE . ' b, ' . USERS_TABLE . ' u - WHERE (b.ban_end >= ' . time() . ' - OR b.ban_end = 0) - AND u.user_id = b.ban_userid - ORDER BY u.username_clean ASC'; - break; - - case 'ip': - - $field = 'ban_ip'; - - $sql = 'SELECT * - FROM ' . BANLIST_TABLE . ' - WHERE (ban_end >= ' . time() . " - OR ban_end = 0) - AND ban_ip <> '' - ORDER BY ban_ip"; - break; - - case 'email': - - $field = 'ban_email'; - - $sql = 'SELECT * - FROM ' . BANLIST_TABLE . ' - WHERE (ban_end >= ' . time() . " - OR ban_end = 0) - AND ban_email <> '' - ORDER BY ban_email"; - break; - } - $result = $db->sql_query($sql); - - $banned_options = $excluded_options = array(); - while ($row = $db->sql_fetchrow($result)) - { - $option = ''; - - if ($row['ban_exclude']) - { - $excluded_options[] = $option; - } - else - { - $banned_options[] = $option; - } - - $time_length = ($row['ban_end']) ? ($row['ban_end'] - $row['ban_start']) / 60 : 0; + $time_length = ($ban_row['ban_end']) ? ($ban_row['ban_end'] - $ban_row['ban_start']) / 60 : 0; if ($time_length == 0) { // Banned permanently - $ban_length = $user->lang['PERMANENT']; + $ban_length = $language->lang('PERMANENT'); } else if (isset($ban_end_text[$time_length])) { // Banned for a given duration - $ban_length = $user->lang('BANNED_UNTIL_DURATION', $ban_end_text[$time_length], $user->format_date($row['ban_end'], false, true)); + $ban_length = $language->lang('BANNED_UNTIL_DURATION', $ban_end_text[$time_length], $user->format_date($ban_row['ban_end'], false, true)); } else { // Banned until given date - $ban_length = $user->lang('BANNED_UNTIL_DATE', $user->format_date($row['ban_end'], false, true)); + $ban_length = $language->lang('BANNED_UNTIL_DATE', $user->format_date($ban_row['ban_end'], false, true)); } $template->assign_block_vars('bans', array( - 'BAN_ID' => (int) $row['ban_id'], + 'BAN_ID' => (int) $ban_row['ban_id'], 'LENGTH' => $ban_length, 'A_LENGTH' => addslashes($ban_length), - 'REASON' => $row['ban_reason'], - 'A_REASON' => addslashes($row['ban_reason']), - 'GIVE_REASON' => $row['ban_give_reason'], - 'A_GIVE_REASON' => addslashes($row['ban_give_reason']), + 'REASON' => $ban_row['ban_reason'], + 'A_REASON' => addslashes($ban_row['ban_reason']), + 'GIVE_REASON' => $ban_row['ban_reason_display'], + 'A_GIVE_REASON' => addslashes($ban_row['ban_reason_display']), )); } - $db->sql_freeresult($result); - $options = ''; - if ($excluded_options) + if (count($banned_options)) { - $options .= ''; - $options .= implode('', $excluded_options); - $options .= ''; - } + $banned_select = [ + 'tag' => 'select', + 'name' => 'unban[]', + 'id' => 'unban', + 'class' => 'w-50', + 'multiple' => true, + 'size' => 10, + 'data' => [ + 'onchange' => 'display_details', + ], + 'options' => [[ + 'label' => $language->lang('OPTIONS_BANNED'), + 'options' => $banned_options, + ]], + ]; - if ($banned_options) - { - $options .= ''; - $options .= implode('', $banned_options); - $options .= ''; + $template->assign_vars(['BANNED_SELECT' => $banned_select]); } - - $template->assign_vars(array( - 'S_BANNED_OPTIONS' => ($banned_options || $excluded_options) ? true : false, - 'BANNED_OPTIONS' => $options, - )); } diff --git a/phpBB/includes/functions_display.php b/phpBB/includes/functions_display.php index e274ae8476..7e49ed3ede 100644 --- a/phpBB/includes/functions_display.php +++ b/phpBB/includes/functions_display.php @@ -1632,6 +1632,10 @@ function phpbb_show_profile($data, $user_notes_enabled = false, $warn_user_enabl include($phpbb_root_path . 'includes/functions_user.' . $phpEx); } + /** @var \phpbb\ban\manager $ban_manager */ + $ban_manager = $phpbb_container->get('ban.manager'); + $user_banned = $ban_manager->check($data); + // Can this user receive a Private Message? $can_receive_pm = $check_can_receive_pm && ( // They must be a "normal" user @@ -1644,7 +1648,7 @@ function phpbb_show_profile($data, $user_notes_enabled = false, $warn_user_enabl count($auth->acl_get_list($user_id, 'u_readpm')) && // They must not be permanently banned - !count(phpbb_get_banned_user_ids($user_id, false)) && + (empty($user_banned) || $user_banned['end'] > 0) && // They must allow users to contact via PM (($auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_')) || $data['user_allow_pm']) diff --git a/phpBB/includes/functions_user.php b/phpBB/includes/functions_user.php index 9cdeb7c619..340f03c0c0 100644 --- a/phpBB/includes/functions_user.php +++ b/phpBB/includes/functions_user.php @@ -749,8 +749,9 @@ function user_delete($mode, $user_ids, $retain_username = true) $db->sql_query($sql); // Delete the user_id from the banlist - $sql = 'DELETE FROM ' . BANLIST_TABLE . ' - WHERE ' . $db->sql_in_set('ban_userid', $user_ids); + $sql = 'DELETE FROM ' . BANS_TABLE . " + WHERE ban_mode = 'user' + AND " . $db->sql_in_set('ban_userid', $user_ids); $db->sql_query($sql); // Delete the user_id from the session table @@ -915,30 +916,26 @@ function user_active_flip($mode, $user_id_ary, $reason = INACTIVE_MANUAL) /** * Add a ban or ban exclusion to the banlist. Bans either a user, an IP or an email address * +* @deprecated 4.0.0-a1 (To be removed: 4.1.0) +* * @param string $mode Type of ban. One of the following: user, ip, email * @param mixed $ban Banned entity. Either string or array with usernames, ips or email addresses * @param int $ban_len Ban length in minutes * @param string $ban_len_other Ban length as a date (YYYY-MM-DD) -* @param boolean $ban_exclude Exclude these entities from banning? * @param string $ban_reason String describing the reason for this ban * @param string $ban_give_reason * @return boolean */ -function user_ban($mode, $ban, $ban_len, $ban_len_other, $ban_exclude, $ban_reason, $ban_give_reason = '') +function user_ban($mode, $ban, $ban_len, $ban_len_other, $ban_reason, $ban_give_reason = '') { - global $db, $user, $cache, $phpbb_log; + global $phpbb_container, $user; - // Delete stale bans - $sql = 'DELETE FROM ' . BANLIST_TABLE . ' - WHERE ban_end < ' . time() . ' - AND ban_end <> 0'; - $db->sql_query($sql); + /** @var \phpbb\ban\manager $ban_manager */ + $ban_manager = $phpbb_container->get('ban.manager'); - $ban_list = (!is_array($ban)) ? array_unique(explode("\n", $ban)) : $ban; - $ban_list_log = implode(', ', $ban_list); + $items = is_array($ban) ? $ban : [$ban]; $current_time = time(); - // Set $ban_end to the unix time when the ban should end. 0 is a permanent ban. if ($ban_len) { @@ -953,9 +950,9 @@ function user_ban($mode, $ban, $ban_len, $ban_len_other, $ban_exclude, $ban_reas (strlen($ban_other[0]) == 4) && (strlen($ban_other[1]) == 2) && (strlen($ban_other[2]) == 2)) { $ban_end = max($current_time, $user->create_datetime() - ->setDate((int) $ban_other[0], (int) $ban_other[1], (int) $ban_other[2]) - ->setTime(0, 0, 0) - ->getTimestamp() + $user->timezone->getOffset(new DateTime('UTC'))); + ->setDate((int) $ban_other[0], (int) $ban_other[1], (int) $ban_other[2]) + ->setTime(0, 0, 0) + ->getTimestamp() + $user->timezone->getOffset(new DateTime('UTC'))); } else { @@ -968,482 +965,28 @@ function user_ban($mode, $ban, $ban_len, $ban_len_other, $ban_exclude, $ban_reas $ban_end = 0; } - $founder = $founder_names = array(); + $start = new \DateTime(); + $start->setTimestamp($current_time); + $end = new \DateTime(); + $end->setTimestamp($ban_end); - if (!$ban_exclude) - { - // Create a list of founder... - $sql = 'SELECT user_id, user_email, username_clean - FROM ' . USERS_TABLE . ' - WHERE user_type = ' . USER_FOUNDER; - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $founder[$row['user_id']] = $row['user_email']; - $founder_names[$row['user_id']] = $row['username_clean']; - } - $db->sql_freeresult($result); - } - - $banlist_ary = array(); - - switch ($mode) - { - case 'user': - $type = 'ban_userid'; - - // At the moment we do not support wildcard username banning - - // Select the relevant user_ids. - $sql_usernames = array(); - - foreach ($ban_list as $username) - { - $username = trim($username); - if ($username != '') - { - $clean_name = utf8_clean_string($username); - if ($clean_name == $user->data['username_clean']) - { - trigger_error('CANNOT_BAN_YOURSELF', E_USER_WARNING); - } - if (in_array($clean_name, $founder_names)) - { - trigger_error('CANNOT_BAN_FOUNDER', E_USER_WARNING); - } - $sql_usernames[] = $clean_name; - } - } - - // Make sure we have been given someone to ban - if (!count($sql_usernames)) - { - trigger_error('NO_USER_SPECIFIED', E_USER_WARNING); - } - - $sql = 'SELECT user_id - FROM ' . USERS_TABLE . ' - WHERE ' . $db->sql_in_set('username_clean', $sql_usernames); - - // Do not allow banning yourself, the guest account, or founders. - $non_bannable = array($user->data['user_id'], ANONYMOUS); - if (count($founder)) - { - $sql .= ' AND ' . $db->sql_in_set('user_id', array_merge(array_keys($founder), $non_bannable), true); - } - else - { - $sql .= ' AND ' . $db->sql_in_set('user_id', $non_bannable, true); - } - - $result = $db->sql_query($sql); - - if ($row = $db->sql_fetchrow($result)) - { - do - { - $banlist_ary[] = (int) $row['user_id']; - } - while ($row = $db->sql_fetchrow($result)); - - $db->sql_freeresult($result); - } - else - { - $db->sql_freeresult($result); - - trigger_error('NO_USERS', E_USER_WARNING); - } - break; - - case 'ip': - $type = 'ban_ip'; - - foreach ($ban_list as $ban_item) - { - if (preg_match('#^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})[ ]*\-[ ]*([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$#', trim($ban_item), $ip_range_explode)) - { - // This is an IP range - // Don't ask about all this, just don't ask ... ! - $ip_1_counter = $ip_range_explode[1]; - $ip_1_end = $ip_range_explode[5]; - - while ($ip_1_counter <= $ip_1_end) - { - $ip_2_counter = ($ip_1_counter == $ip_range_explode[1]) ? $ip_range_explode[2] : 0; - $ip_2_end = ($ip_1_counter < $ip_1_end) ? 254 : $ip_range_explode[6]; - - if ($ip_2_counter == 0 && $ip_2_end == 254) - { - $ip_2_counter = 256; - - $banlist_ary[] = "$ip_1_counter.*"; - } - - while ($ip_2_counter <= $ip_2_end) - { - $ip_3_counter = ($ip_2_counter == $ip_range_explode[2] && $ip_1_counter == $ip_range_explode[1]) ? $ip_range_explode[3] : 0; - $ip_3_end = ($ip_2_counter < $ip_2_end || $ip_1_counter < $ip_1_end) ? 254 : $ip_range_explode[7]; - - if ($ip_3_counter == 0 && $ip_3_end == 254) - { - $ip_3_counter = 256; - - $banlist_ary[] = "$ip_1_counter.$ip_2_counter.*"; - } - - while ($ip_3_counter <= $ip_3_end) - { - $ip_4_counter = ($ip_3_counter == $ip_range_explode[3] && $ip_2_counter == $ip_range_explode[2] && $ip_1_counter == $ip_range_explode[1]) ? $ip_range_explode[4] : 0; - $ip_4_end = ($ip_3_counter < $ip_3_end || $ip_2_counter < $ip_2_end) ? 254 : $ip_range_explode[8]; - - if ($ip_4_counter == 0 && $ip_4_end == 254) - { - $ip_4_counter = 256; - - $banlist_ary[] = "$ip_1_counter.$ip_2_counter.$ip_3_counter.*"; - } - - while ($ip_4_counter <= $ip_4_end) - { - $banlist_ary[] = "$ip_1_counter.$ip_2_counter.$ip_3_counter.$ip_4_counter"; - $ip_4_counter++; - } - $ip_3_counter++; - } - $ip_2_counter++; - } - $ip_1_counter++; - } - } - else if (preg_match('#^([0-9]{1,3})\.([0-9\*]{1,3})\.([0-9\*]{1,3})\.([0-9\*]{1,3})$#', trim($ban_item)) || preg_match('#^[a-f0-9:]+\*?$#i', trim($ban_item))) - { - // Normal IP address - $banlist_ary[] = trim($ban_item); - } - else if (preg_match('#^\*$#', trim($ban_item))) - { - // Ban all IPs - $banlist_ary[] = '*'; - } - else if (preg_match('#^([\w\-_]\.?){2,}$#is', trim($ban_item))) - { - // hostname - $ip_ary = gethostbynamel(trim($ban_item)); - - if (!empty($ip_ary)) - { - foreach ($ip_ary as $ip) - { - if ($ip) - { - if (strlen($ip) > 40) - { - continue; - } - - $banlist_ary[] = $ip; - } - } - } - } - - if (empty($banlist_ary)) - { - trigger_error('NO_IPS_DEFINED', E_USER_WARNING); - } - } - break; - - case 'email': - $type = 'ban_email'; - - foreach ($ban_list as $ban_item) - { - $ban_item = trim($ban_item); - - if (preg_match('#^.*?@*|(([a-z0-9\-]+\.)+([a-z]{2,3}))$#i', $ban_item)) - { - if (strlen($ban_item) > 100) - { - continue; - } - - if (!count($founder) || !in_array($ban_item, $founder)) - { - $banlist_ary[] = $ban_item; - } - } - } - - if (count($ban_list) == 0) - { - trigger_error('NO_EMAILS_DEFINED', E_USER_WARNING); - } - break; - - default: - trigger_error('NO_MODE', E_USER_WARNING); - break; - } - - // Fetch currently set bans of the specified type and exclude state. Prevent duplicate bans. - $sql_where = ($type == 'ban_userid') ? 'ban_userid <> 0' : "$type <> ''"; - - $sql = "SELECT $type - FROM " . BANLIST_TABLE . " - WHERE $sql_where - AND ban_exclude = " . (int) $ban_exclude; - $result = $db->sql_query($sql); - - // Reset $sql_where, because we use it later... - $sql_where = ''; - - if ($row = $db->sql_fetchrow($result)) - { - $banlist_ary_tmp = array(); - do - { - switch ($mode) - { - case 'user': - $banlist_ary_tmp[] = $row['ban_userid']; - break; - - case 'ip': - $banlist_ary_tmp[] = $row['ban_ip']; - break; - - case 'email': - $banlist_ary_tmp[] = $row['ban_email']; - break; - } - } - while ($row = $db->sql_fetchrow($result)); - - $banlist_ary_tmp = array_intersect($banlist_ary, $banlist_ary_tmp); - - if (count($banlist_ary_tmp)) - { - // One or more entities are already banned/excluded, delete the existing bans, so they can be re-inserted with the given new length - $sql = 'DELETE FROM ' . BANLIST_TABLE . ' - WHERE ' . $db->sql_in_set($type, $banlist_ary_tmp) . ' - AND ban_exclude = ' . (int) $ban_exclude; - $db->sql_query($sql); - } - - unset($banlist_ary_tmp); - } - $db->sql_freeresult($result); - - // We have some entities to ban - if (count($banlist_ary)) - { - $sql_ary = array(); - - foreach ($banlist_ary as $ban_entry) - { - $sql_ary[] = array( - $type => $ban_entry, - 'ban_start' => (int) $current_time, - 'ban_end' => (int) $ban_end, - 'ban_exclude' => (int) $ban_exclude, - 'ban_reason' => (string) $ban_reason, - 'ban_give_reason' => (string) $ban_give_reason, - ); - } - - $db->sql_multi_insert(BANLIST_TABLE, $sql_ary); - - // If we are banning we want to logout anyone matching the ban - if (!$ban_exclude) - { - switch ($mode) - { - case 'user': - $sql_where = 'WHERE ' . $db->sql_in_set('session_user_id', $banlist_ary); - break; - - case 'ip': - $sql_where = 'WHERE ' . $db->sql_in_set('session_ip', $banlist_ary); - break; - - case 'email': - $banlist_ary_sql = array(); - - foreach ($banlist_ary as $ban_entry) - { - $banlist_ary_sql[] = (string) str_replace('*', '%', $ban_entry); - } - - $sql = 'SELECT user_id - FROM ' . USERS_TABLE . ' - WHERE ' . $db->sql_in_set('user_email', $banlist_ary_sql); - $result = $db->sql_query($sql); - - $sql_in = array(); - - if ($row = $db->sql_fetchrow($result)) - { - do - { - $sql_in[] = $row['user_id']; - } - while ($row = $db->sql_fetchrow($result)); - - $sql_where = 'WHERE ' . $db->sql_in_set('session_user_id', $sql_in); - } - $db->sql_freeresult($result); - break; - } - - if (isset($sql_where) && $sql_where) - { - $sql = 'DELETE FROM ' . SESSIONS_TABLE . " - $sql_where"; - $db->sql_query($sql); - - if ($mode == 'user') - { - $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . ' ' . ((in_array('*', $banlist_ary)) ? '' : 'WHERE ' . $db->sql_in_set('user_id', $banlist_ary)); - $db->sql_query($sql); - } - } - } - - // Update log - $log_entry = ($ban_exclude) ? 'LOG_BAN_EXCLUDE_' : 'LOG_BAN_'; - - // Add to admin log, moderator log and user notes - $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log_entry . strtoupper($mode), false, array($ban_reason, $ban_list_log)); - $phpbb_log->add('mod', $user->data['user_id'], $user->ip, $log_entry . strtoupper($mode), false, array( - 'forum_id' => 0, - 'topic_id' => 0, - $ban_reason, - $ban_list_log - )); - if ($mode == 'user') - { - foreach ($banlist_ary as $user_id) - { - $phpbb_log->add('user', $user->data['user_id'], $user->ip, $log_entry . strtoupper($mode), false, array( - 'reportee_id' => $user_id, - $ban_reason, - $ban_list_log - )); - } - } - - $cache->destroy('sql', BANLIST_TABLE); - - return true; - } - - // There was nothing to ban/exclude. But destroying the cache because of the removal of stale bans. - $cache->destroy('sql', BANLIST_TABLE); - - return false; + return $ban_manager->ban($mode, $items, $start, $end, $ban_reason, $ban_give_reason); } /** * Unban User +* +* @deprecated 4.0.0-a1 (To be removed: 4.1.0) */ function user_unban($mode, $ban) { - global $db, $user, $cache, $phpbb_log, $phpbb_dispatcher; + global $phpbb_container; - // Delete stale bans - $sql = 'DELETE FROM ' . BANLIST_TABLE . ' - WHERE ban_end < ' . time() . ' - AND ban_end <> 0'; - $db->sql_query($sql); + $items = is_array($ban) ? $ban : [$ban]; - if (!is_array($ban)) - { - $ban = array($ban); - } - - $unban_sql = array_map('intval', $ban); - - if (count($unban_sql)) - { - // Grab details of bans for logging information later - switch ($mode) - { - case 'user': - $sql = 'SELECT u.username AS unban_info, u.user_id - FROM ' . USERS_TABLE . ' u, ' . BANLIST_TABLE . ' b - WHERE ' . $db->sql_in_set('b.ban_id', $unban_sql) . ' - AND u.user_id = b.ban_userid'; - break; - - case 'email': - $sql = 'SELECT ban_email AS unban_info - FROM ' . BANLIST_TABLE . ' - WHERE ' . $db->sql_in_set('ban_id', $unban_sql); - break; - - case 'ip': - $sql = 'SELECT ban_ip AS unban_info - FROM ' . BANLIST_TABLE . ' - WHERE ' . $db->sql_in_set('ban_id', $unban_sql); - break; - } - $result = $db->sql_query($sql); - - $l_unban_list = ''; - $user_ids_ary = array(); - while ($row = $db->sql_fetchrow($result)) - { - $l_unban_list .= (($l_unban_list != '') ? ', ' : '') . $row['unban_info']; - if ($mode == 'user') - { - $user_ids_ary[] = $row['user_id']; - } - } - $db->sql_freeresult($result); - - $sql = 'DELETE FROM ' . BANLIST_TABLE . ' - WHERE ' . $db->sql_in_set('ban_id', $unban_sql); - $db->sql_query($sql); - - // Add to moderator log, admin log and user notes - $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_UNBAN_' . strtoupper($mode), false, array($l_unban_list)); - $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_UNBAN_' . strtoupper($mode), false, array( - 'forum_id' => 0, - 'topic_id' => 0, - $l_unban_list - )); - if ($mode == 'user') - { - foreach ($user_ids_ary as $user_id) - { - $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_UNBAN_' . strtoupper($mode), false, array( - 'reportee_id' => $user_id, - $l_unban_list - )); - } - } - - /** - * Use this event to perform actions after the unban has been performed - * - * @event core.user_unban - * @var string mode One of the following: user, ip, email - * @var array user_ids_ary Array with user_ids - * @since 3.1.11-RC1 - */ - $vars = array( - 'mode', - 'user_ids_ary', - ); - extract($phpbb_dispatcher->trigger_event('core.user_unban', compact($vars))); - } - - $cache->destroy('sql', BANLIST_TABLE); - - return false; + /** @var \phpbb\ban\manager $ban_manager */ + $ban_manager = $phpbb_container->get('ban.manager'); + $ban_manager->unban($mode, $items); } /** @@ -1948,7 +1491,7 @@ function validate_user_email($email, $allowed_email = false) $ban = $user->check_ban(false, false, $email, true); if (!empty($ban)) { - return !empty($ban['ban_give_reason']) ? $ban['ban_give_reason'] : 'EMAIL_BANNED'; + return !empty($ban['reason']) ? $ban['reason'] : 'EMAIL_BANNED'; } if (!$config['allow_emailreuse']) @@ -3719,45 +3262,43 @@ function remove_newly_registered($user_id, $user_data = false) */ function phpbb_get_banned_user_ids($user_ids = array(), $ban_end = true) { - global $db; + global $phpbb_container; - $sql_user_ids = (!empty($user_ids)) ? $db->sql_in_set('ban_userid', $user_ids) : 'ban_userid <> 0'; + /** @var \phpbb\ban\manager $ban_manager */ + $ban_manager = $phpbb_container->get('ban.manager'); + $banned_users = $ban_manager->get_banned_users(); - // Get banned User ID's - // Ignore stale bans which were not wiped yet - $banned_ids_list = array(); - $sql = 'SELECT ban_userid - FROM ' . BANLIST_TABLE . " - WHERE $sql_user_ids - AND ban_exclude <> 1"; - - if ($ban_end === true) + if ($ban_end === false) { - // Banned currently - $sql .= " AND (ban_end > " . time() . ' - OR ban_end = 0)'; + $banned_users = array_filter($banned_users, function ($end) { + return $end <= 0; + }); } - else if ($ban_end === false) + else if ($ban_end !== true) { - // Permanently banned - $sql .= " AND ban_end = 0"; + $banned_users = array_filter($banned_users, function ($end) use ($ban_end) { + return $end <= 0 || $end > (int) $ban_end; + }); } else { - // Banned until a specified time - $sql .= " AND (ban_end > " . (int) $ban_end . ' - OR ban_end = 0)'; + $banned_users = array_filter($banned_users, function ($end) { + return $end <= 0 || $end > time(); + }); } - $result = $db->sql_query($sql); - while ($row = $db->sql_fetchrow($result)) + $result_array = []; + foreach ($banned_users as $user_id => $_) { - $user_id = (int) $row['ban_userid']; - $banned_ids_list[$user_id] = $user_id; - } - $db->sql_freeresult($result); + if (count($user_ids) && !in_array($user_id, $user_ids)) + { + continue; + } - return $banned_ids_list; + $result_array[$user_id] = $user_id; + } + + return $result_array; } /** diff --git a/phpBB/includes/mcp/mcp_ban.php b/phpBB/includes/mcp/mcp_ban.php index c358680e00..2398d76371 100644 --- a/phpBB/includes/mcp/mcp_ban.php +++ b/phpBB/includes/mcp/mcp_ban.php @@ -68,7 +68,6 @@ class mcp_ban $ban = $request->variable('ban', '', $mode === 'user'); $ban_length = $request->variable('banlength', 0); $ban_length_other = $request->variable('banlengthother', ''); - $ban_exclude = $request->variable('banexclude', 0); $ban_reason = $request->variable('banreason', '', true); $ban_give_reason = $request->variable('bangivereason', '', true); @@ -85,7 +84,6 @@ class mcp_ban * @var string ban Either string or array with usernames, ips or email addresses * @var int ban_length Ban length in minutes * @var string ban_length_other Ban length as a date (YYYY-MM-DD) - * @var bool ban_exclude Are we banning or excluding from another ban * @var string ban_reason Ban reason displayed to moderators * @var string ban_give_reason Ban reason displayed to the banned user * @var mixed abort_ban Either false, or an error message that is displayed to the user. @@ -97,7 +95,6 @@ class mcp_ban 'ban', 'ban_length', 'ban_length_other', - 'ban_exclude', 'ban_reason', 'ban_give_reason', 'abort_ban', @@ -108,7 +105,7 @@ class mcp_ban { trigger_error($abort_ban); } - user_ban($mode, $ban, $ban_length, $ban_length_other, $ban_exclude, $ban_reason, $ban_give_reason); + user_ban($mode, $ban, $ban_length, $ban_length_other, $ban_reason, $ban_give_reason); /** * Use this event to perform actions after the ban has been performed @@ -118,7 +115,6 @@ class mcp_ban * @var string ban Either string or array with usernames, ips or email addresses * @var int ban_length Ban length in minutes * @var string ban_length_other Ban length as a date (YYYY-MM-DD) - * @var bool ban_exclude Are we banning or excluding from another ban * @var string ban_reason Ban reason displayed to moderators * @var string ban_give_reason Ban reason displayed to the banned user * @since 3.1.0-RC5 @@ -128,7 +124,6 @@ class mcp_ban 'ban', 'ban_length', 'ban_length_other', - 'ban_exclude', 'ban_reason', 'ban_give_reason', ); @@ -144,7 +139,6 @@ class mcp_ban 'bansubmit' => true, 'banlength' => $ban_length, 'banlengthother' => $ban_length_other, - 'banexclude' => $ban_exclude, 'banreason' => $ban_reason, 'bangivereason' => $ban_give_reason, ); @@ -189,7 +183,6 @@ class mcp_ban $this->page_title = $user->lang[strtoupper($mode) . '_BAN']; $l_ban_explain = $user->lang[strtoupper($mode) . '_BAN_EXPLAIN']; - $l_ban_exclude_explain = $user->lang[strtoupper($mode) . '_BAN_EXCLUDE_EXPLAIN']; $l_unban_title = $user->lang[strtoupper($mode) . '_UNBAN']; $l_unban_explain = $user->lang[strtoupper($mode) . '_UNBAN_EXPLAIN']; $l_no_ban_cell = $user->lang[strtoupper($mode) . '_NO_BANNED']; @@ -218,7 +211,6 @@ class mcp_ban 'L_UNBAN_TITLE' => $l_unban_title, 'L_UNBAN_EXPLAIN' => $l_unban_explain, 'L_BAN_CELL' => $l_ban_cell, - 'L_BAN_EXCLUDE_EXPLAIN' => $l_ban_exclude_explain, 'L_NO_BAN_CELL' => $l_no_ban_cell, 'S_USERNAME_BAN' => ($mode == 'user') ? true : false, diff --git a/phpBB/index.php b/phpBB/index.php index 6275c92458..bc7c1a4707 100644 --- a/phpBB/index.php +++ b/phpBB/index.php @@ -114,13 +114,13 @@ if ($show_birthdays) ), 'LEFT_JOIN' => array( array( - 'FROM' => array(BANLIST_TABLE => 'b'), + 'FROM' => array(BANS_TABLE => 'b'), 'ON' => 'u.user_id = b.ban_userid', ), ), - 'WHERE' => "(b.ban_id IS NULL OR b.ban_exclude = 1) - AND (u.user_birthday LIKE '" . $db->sql_escape(sprintf('%2d-%2d-', $now['mday'], $now['mon'])) . "%' $leap_year_birthdays) - AND u.user_type IN (" . USER_NORMAL . ', ' . USER_FOUNDER . ')', + 'WHERE' => 'b.ban_id IS NULL + AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ") + AND (u.user_birthday LIKE '" . $db->sql_escape(sprintf('%2d-%2d-', $now['mday'], $now['mon'])) . "%' $leap_year_birthdays)", ); /** diff --git a/phpBB/install/convert/convertor.php b/phpBB/install/convert/convertor.php index b52a0b03bc..a90b7337dc 100644 --- a/phpBB/install/convert/convertor.php +++ b/phpBB/install/convert/convertor.php @@ -755,7 +755,7 @@ class convertor { if (!$db->sql_query($insert_query . $waiting_sql)) { - $this->db_error($user->lang['DB_ERR_INSERT'], htmlspecialchars($insert_query . $waiting_sql, ENT_COMPAT) . '

' . htmlspecialchars(print_r($db->_sql_error(), true), ENT_COMPAT), __LINE__, __FILE__, true); + $this->db_error($user->lang['DB_ERR_INSERT'], htmlspecialchars($insert_query . $waiting_sql, ENT_COMPAT) . '

' . htmlspecialchars(print_r($db->sql_error(), true), ENT_COMPAT), __LINE__, __FILE__, true); } } @@ -774,7 +774,7 @@ class convertor if (!$db->sql_query($insert_sql)) { - $this->db_error($user->lang['DB_ERR_INSERT'], htmlspecialchars($insert_sql, ENT_COMPAT) . '

' . htmlspecialchars(print_r($db->_sql_error(), true), ENT_COMPAT), __LINE__, __FILE__, true); + $this->db_error($user->lang['DB_ERR_INSERT'], htmlspecialchars($insert_sql, ENT_COMPAT) . '

' . htmlspecialchars(print_r($db->sql_error(), true), ENT_COMPAT), __LINE__, __FILE__, true); } $db->sql_return_on_error(false); @@ -809,7 +809,7 @@ class convertor foreach ($waiting_rows as $waiting_sql) { $db->sql_query($insert_query . $waiting_sql); - $this->db_error($user->lang['DB_ERR_INSERT'], htmlspecialchars($insert_query . $waiting_sql, ENT_COMPAT) . '

' . htmlspecialchars(print_r($db->_sql_error(), true), ENT_COMPAT), __LINE__, __FILE__, true); + $this->db_error($user->lang['DB_ERR_INSERT'], htmlspecialchars($insert_query . $waiting_sql, ENT_COMPAT) . '

' . htmlspecialchars(print_r($db->sql_error(), true), ENT_COMPAT), __LINE__, __FILE__, true); } $db->sql_return_on_error(false); diff --git a/phpBB/install/convertors/convert_phpbb20.php b/phpBB/install/convertors/convert_phpbb20.php index 62294bf449..afea6a92da 100644 --- a/phpBB/install/convertors/convert_phpbb20.php +++ b/phpBB/install/convertors/convert_phpbb20.php @@ -445,29 +445,42 @@ if (!$get_info) ), array( - 'target' => BANLIST_TABLE, - 'execute_first' => 'phpbb_check_username_collisions();', - 'query_first' => array('target', $convert->truncate_statement . BANLIST_TABLE), + 'target' => BANS_TABLE, + 'execute_first' => 'phpbb_check_username_collisions();', + 'query_first' => array('target', $convert->truncate_statement . BANS_TABLE), - array('ban_ip', 'banlist.ban_ip', 'decode_ban_ip'), - array('ban_userid', 'banlist.ban_userid', 'phpbb_user_id'), - array('ban_email', 'banlist.ban_email', ''), - array('ban_reason', '', ''), - array('ban_give_reason', '', ''), + array('ban_mode', 'user', ''), + array('ban_item', 'banlist.ban_userid', 'phpbb_user_id'), + array('ban_userid', 'banlist.ban_userid', 'phpbb_user_id'), + array('ban_reason', '', ''), + array('ban_reason_display', '', ''), - 'where' => "banlist.ban_ip NOT LIKE '%.%'", + 'where' => "banlist.ban_ip NOT LIKE '%.%' + AND banlist.ban_userid <> 0", ), array( - 'target' => BANLIST_TABLE, + 'target' => BANS_TABLE, - array('ban_ip', 'banlist.ban_ip', ''), - array('ban_userid', 0, ''), - array('ban_email', '', ''), - array('ban_reason', '', ''), - array('ban_give_reason', '', ''), + array('ban_mode', 'email', ''), + array('ban_item', 'banlist.ban_email', ''), + array('ban_reason', '', ''), + array('ban_reason_display', '', ''), - 'where' => "banlist.ban_ip LIKE '%.%'", + 'where' => "banlist.ban_ip NOT LIKE '%.%' + AND banlist.ban_email <> ''", + ), + + array( + 'target' => BANS_TABLE, + + array('ban_mode', 'ip', ''), + array('ban_item', 'banlist.ban_ip', 'decode_ban_ip'), + array('ban_reason', '', ''), + array('ban_reason_display', '', ''), + + 'where' => "banlist.ban_userid = 0 + AND banlist.ban_ip <> ''", ), array( diff --git a/phpBB/language/en/acp/ban.php b/phpBB/language/en/acp/ban.php index 93d5cf9a8b..6f15932c1e 100644 --- a/phpBB/language/en/acp/ban.php +++ b/phpBB/language/en/acp/ban.php @@ -44,7 +44,6 @@ $lang = array_merge($lang, array( 'ACP_BAN_EXPLAIN' => 'Here you can control the banning of users by name, IP or email address. These methods prevent a user reaching any part of the board. You can give a short (maximum 3000 characters) reason for the ban if you wish. This will be displayed in the admin log. The duration of a ban can also be specified. If you want the ban to end on a specific date rather than after a set time period select Until -> for the ban length and enter a date in YYYY-MM-DD format.', - 'BAN_EXCLUDE' => 'Exclude from banning', 'BAN_LENGTH' => 'Length of ban', 'BAN_REASON' => 'Reason for ban', 'BAN_GIVE_REASON' => 'Reason shown to the banned', @@ -53,32 +52,28 @@ $lang = array_merge($lang, array( 'BANNED_UNTIL_DURATION' => '%1$s (until %2$s)', // Example: "7 days (until Tue 14.Jul.2009, 14:44)" 'EMAIL_BAN' => 'Ban one or more email addresses', - 'EMAIL_BAN_EXCLUDE_EXPLAIN' => 'Enable this to exclude the entered email address from all current bans.', 'EMAIL_BAN_EXPLAIN' => 'To specify more than one email address enter each on a new line. To match partial addresses use * as the wildcard, e.g. *@hotmail.com, *@*.domain.tld, etc.', 'EMAIL_NO_BANNED' => 'No banned email addresses', - 'EMAIL_UNBAN' => 'Un-ban or un-exclude emails', - 'EMAIL_UNBAN_EXPLAIN' => 'You can unban (or un-exclude) multiple email addresses in one go using the appropriate combination of mouse and keyboard for your computer and browser. Excluded email addresses are emphasised.', + 'EMAIL_UNBAN' => 'Un-ban emails', + 'EMAIL_UNBAN_EXPLAIN' => 'You can unban multiple email addresses in one go using the appropriate combination of mouse and keyboard for your computer and browser.', 'IP_BAN' => 'Ban one or more IPs', - 'IP_BAN_EXCLUDE_EXPLAIN' => 'Enable this to exclude the entered IP from all current bans.', 'IP_BAN_EXPLAIN' => 'To specify several different IPs or hostnames enter each on a new line. To specify a range of IP addresses separate the start and end with a hyphen (-), to specify a wildcard use “*”.', 'IP_HOSTNAME' => 'IP addresses or hostnames', 'IP_NO_BANNED' => 'No banned IP addresses', - 'IP_UNBAN' => 'Un-ban or un-exclude IPs', - 'IP_UNBAN_EXPLAIN' => 'You can unban (or un-exclude) multiple IP addresses in one go using the appropriate combination of mouse and keyboard for your computer and browser. Excluded IPs are emphasised.', + 'IP_UNBAN' => 'Un-ban IPs', + 'IP_UNBAN_EXPLAIN' => 'You can unban multiple IP addresses in one go using the appropriate combination of mouse and keyboard for your computer and browser.', 'LENGTH_BAN_INVALID' => 'The date has to be formatted YYYY-MM-DD.', 'OPTIONS_BANNED' => 'Banned', - 'OPTIONS_EXCLUDED' => 'Excluded', 'PERMANENT' => 'Permanent', 'UNTIL' => 'Until', 'USER_BAN' => 'Ban one or more users by username', - 'USER_BAN_EXCLUDE_EXPLAIN' => 'Enable this to exclude the entered users from all current bans.', 'USER_BAN_EXPLAIN' => 'You can ban multiple users in one go by entering each name on a new line. Use the Find a member facility to look up and add one or more users automatically.', 'USER_NO_BANNED' => 'No banned usernames', - 'USER_UNBAN' => 'Un-ban or un-exclude users by username', - 'USER_UNBAN_EXPLAIN' => 'You can unban (or un-exclude) multiple users in one go using the appropriate combination of mouse and keyboard for your computer and browser. Excluded users are emphasised.', + 'USER_UNBAN' => 'Un-ban users by username', + 'USER_UNBAN_EXPLAIN' => 'You can unban multiple users in one go using the appropriate combination of mouse and keyboard for your computer and browser.', )); diff --git a/phpBB/language/en/acp/common.php b/phpBB/language/en/acp/common.php index e53937c8a3..aeab0af262 100644 --- a/phpBB/language/en/acp/common.php +++ b/phpBB/language/en/acp/common.php @@ -553,9 +553,6 @@ $lang = array_merge($lang, array( 'LOG_ATTACH_FILEUPLOAD' => 'Orphan File uploaded to Post
» ID %1$d - %2$s', 'LOG_ATTACH_ORPHAN_DEL' => 'Orphan Files deleted
» %s', - 'LOG_BAN_EXCLUDE_USER' => 'Excluded user from ban for reason “%1$s
» %2$s', - 'LOG_BAN_EXCLUDE_IP' => 'Excluded IP from ban for reason “%1$s
» %2$s', - 'LOG_BAN_EXCLUDE_EMAIL' => 'Excluded email from ban for reason “%1$s
» %2$s', 'LOG_BAN_USER' => 'Banned user for reason “%1$s
» %2$s', 'LOG_BAN_IP' => 'Banned IP for reason “%1$s
» %2$s', 'LOG_BAN_EMAIL' => 'Banned email for reason “%1$s
» %2$s', diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php index 18de2a0d2c..5799137f5f 100644 --- a/phpBB/language/en/common.php +++ b/phpBB/language/en/common.php @@ -120,9 +120,9 @@ $lang = array_merge($lang, array( 'BBCODE_GUIDE' => 'BBCode guide', 'BCC' => 'BCC', 'BIRTHDAYS' => 'Birthdays', - 'BOARD_BAN_PERM' => 'You have been permanently banned from this board.

Please contact the %2$sBoard Administrator%3$s for more information.', + 'BOARD_BAN_PERM' => 'You have been permanently banned from this board.

Please contact the %2$sBoard Administrator%3$s for more information.', 'BOARD_BAN_REASON' => 'Reason given for ban: %s', - 'BOARD_BAN_TIME' => 'You have been banned from this board until %1$s.

Please contact the %2$sBoard Administrator%3$s for more information.', + 'BOARD_BAN_TIME' => 'You have been banned from this board until %1$s.

Please contact the %2$sBoard Administrator%3$s for more information.', 'BOARD_DISABLE' => 'Sorry but this board is currently unavailable.', 'BOARD_DISABLED' => 'This board is currently disabled.', 'BOARD_UNAVAILABLE' => 'Sorry but the board is temporarily unavailable, please try again in a few minutes.', diff --git a/phpBB/phpbb/ban/exception/invalid_length_exception.php b/phpBB/phpbb/ban/exception/invalid_length_exception.php new file mode 100644 index 0000000000..dbbf09f0ac --- /dev/null +++ b/phpBB/phpbb/ban/exception/invalid_length_exception.php @@ -0,0 +1,20 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\ban\exception; + +use phpbb\exception\runtime_exception; + +class invalid_length_exception extends runtime_exception +{ +} diff --git a/phpBB/phpbb/ban/exception/no_valid_emails_exception.php b/phpBB/phpbb/ban/exception/no_valid_emails_exception.php new file mode 100644 index 0000000000..74079ccc2f --- /dev/null +++ b/phpBB/phpbb/ban/exception/no_valid_emails_exception.php @@ -0,0 +1,20 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\ban\exception; + +use phpbb\exception\runtime_exception; + +class no_valid_emails_exception extends runtime_exception +{ +} diff --git a/phpBB/phpbb/ban/exception/no_valid_ips_exception.php b/phpBB/phpbb/ban/exception/no_valid_ips_exception.php new file mode 100644 index 0000000000..e5725d27c9 --- /dev/null +++ b/phpBB/phpbb/ban/exception/no_valid_ips_exception.php @@ -0,0 +1,20 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\ban\exception; + +use phpbb\exception\runtime_exception; + +class no_valid_ips_exception extends runtime_exception +{ +} diff --git a/phpBB/phpbb/ban/exception/no_valid_users_exception.php b/phpBB/phpbb/ban/exception/no_valid_users_exception.php new file mode 100644 index 0000000000..72e231c27e --- /dev/null +++ b/phpBB/phpbb/ban/exception/no_valid_users_exception.php @@ -0,0 +1,20 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\ban\exception; + +use phpbb\exception\runtime_exception; + +class no_valid_users_exception extends runtime_exception +{ +} diff --git a/phpBB/phpbb/ban/exception/type_not_found_exception.php b/phpBB/phpbb/ban/exception/type_not_found_exception.php new file mode 100644 index 0000000000..a4d7e8d19f --- /dev/null +++ b/phpBB/phpbb/ban/exception/type_not_found_exception.php @@ -0,0 +1,20 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\ban\exception; + +use phpbb\exception\runtime_exception; + +class type_not_found_exception extends runtime_exception +{ +} diff --git a/phpBB/phpbb/ban/manager.php b/phpBB/phpbb/ban/manager.php new file mode 100644 index 0000000000..e48e734617 --- /dev/null +++ b/phpBB/phpbb/ban/manager.php @@ -0,0 +1,573 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\ban; + +use phpbb\ban\exception\invalid_length_exception; +use phpbb\ban\exception\type_not_found_exception; +use phpbb\ban\type\type_interface; +use phpbb\cache\driver\driver_interface as cache_driver; +use phpbb\db\driver\driver_interface; +use phpbb\di\service_collection; +use phpbb\language\language; +use phpbb\log\log_interface; +use phpbb\user; + +class manager +{ + const CACHE_KEY_INFO = '_ban_info'; + const CACHE_KEY_USERS = '_banned_users'; + const CACHE_TTL = 3600; + + /** @var string */ + protected $bans_table; + + /** @var cache_driver */ + protected $cache; + + /** @var driver_interface */ + protected $db; + + /** @var service_collection */ + protected $types; + + /** @var language */ + protected $language; + + /** @var log_interface */ + protected $log; + + /** @var user */ + protected $user; + + /** @var string */ + protected $users_table; + + /** + * Creates a service which manages all bans. Developers can + * create their own ban types which will be handled in this. + * + * @param service_collection $types A service collection containing all ban types + * @param cache_driver $cache A cache object + * @param driver_interface $db A phpBB DBAL object + * @param language $language Language object + * @param log_interface $log Log object + * @param user $user User object + * @param string $bans_table The bans table + * @param string $users_table The users table + */ + public function __construct(service_collection $types, cache_driver $cache, driver_interface $db, language $language, + log_interface $log, user $user, string $bans_table, string $users_table = '') + { + $this->bans_table = $bans_table; + $this->cache = $cache; + $this->db = $db; + $this->types = $types; + $this->language = $language; + $this->log = $log; + $this->user = $user; + $this->users_table = $users_table; + } + + /** + * Creates ban entries for the given $items. Returns true if successful + * and false if no entries were added to the database + * + * @param string $mode A string which identifies a ban type + * @param array $items An array of items which should be banned + * @param \DateTimeInterface $start A DateTimeInterface object which is the start of the ban + * @param \DateTimeInterface $end A DateTimeInterface object which is the end of the ban (or 0 if permanent) + * @param string $reason An (internal) reason for the ban + * @param string $display_reason An optional reason which should be displayed to the banned + * + * @return bool + */ + public function ban(string $mode, array $items, \DateTimeInterface $start, \DateTimeInterface $end, string $reason, string $display_reason = ''): bool + { + if ($start > $end && $end->getTimestamp() !== 0) + { + throw new invalid_length_exception('LENGTH_BAN_INVALID'); + } + + /** @var type_interface $ban_mode */ + $ban_mode = $this->find_type($mode); + if ($ban_mode === false) + { + throw new type_not_found_exception(); + } + + if (!empty($this->user)) + { + $ban_mode->set_user($this->user); + } + $this->tidy(); + + $ban_items = $ban_mode->prepare_for_storage($items); + + // Prevent duplicate bans + $sql = 'DELETE FROM ' . $this->bans_table . " + WHERE ban_mode = '" . $this->db->sql_escape($mode) . "' + AND " . $this->db->sql_in_set('ban_item', $ban_items, false, true); + $this->db->sql_query($sql); + + $insert_array = []; + foreach ($ban_items as $ban_item) + { + $insert_array[] = [ + 'ban_mode' => $mode, + 'ban_item' => $ban_item, + 'ban_userid' => $mode === 'user' ? $ban_item : 0, + 'ban_start' => $start->getTimestamp(), + 'ban_end' => $end->getTimestamp(), + 'ban_reason' => $reason, + 'ban_reason_display' => $display_reason, + ]; + } + + if (empty($insert_array)) + { + return false; + } + + $this->db->sql_multi_insert($this->bans_table, $insert_array); + + $ban_data = [ + 'items' => $ban_items, + 'start' => $start, + 'end' => $end, + 'reason' => $reason, + 'display_reason' => $display_reason, + ]; + + // Add to admin log, moderator log and user notes + $ban_list_log = implode(', ', $items); + + $log_operation = 'LOG_BAN_' . strtoupper($mode); + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, $log_operation, false, [$reason, $ban_list_log]); + $this->log->add('mod', $this->user->data['user_id'], $this->user->ip, $log_operation, false, [ + 'forum_id' => 0, + 'topic_id' => 0, + $reason, + $ban_list_log + ]); + + if ($banlist_ary = $ban_mode->after_ban($ban_data)) + { + foreach ($banlist_ary as $user_id) + { + $this->log->add('user', $this->user->data['user_id'], $this->user->ip, $log_operation, false, [ + 'reportee_id' => $user_id, + $reason, + $ban_list_log + ]); + } + } + + $this->cache->destroy(self::CACHE_KEY_INFO); + $this->cache->destroy(self::CACHE_KEY_USERS); + + return true; + } + + /** + * Removes ban entries from the database with the given IDs + * + * @param string $mode The ban type in which the ban IDs were created + * @param array $items An array of ban IDs which should be removed + */ + public function unban(string $mode, array $items) + { + /** @var type_interface $ban_mode */ + $ban_mode = $this->find_type($mode); + if ($ban_mode === false) + { + throw new type_not_found_exception(); + } + $this->tidy(); + + $sql_ids = array_map('intval', $items); + + if (count($sql_ids)) + { + $sql = 'SELECT ban_item + FROM ' . $this->bans_table . ' + WHERE ' . $this->db->sql_in_set('ban_id', $sql_ids); + $result = $this->db->sql_query($sql); + + $unbanned_items = []; + while ($row = $this->db->sql_fetchrow($result)) + { + $unbanned_items[] = $row['ban_item']; + } + $this->db->sql_freeresult($result); + + $sql = 'DELETE FROM ' . $this->bans_table . ' + WHERE ' . $this->db->sql_in_set('ban_id', $sql_ids); + $this->db->sql_query($sql); + + $unban_data = [ + 'items' => $unbanned_items, + ]; + $unbanned_users = $ban_mode->after_unban($unban_data); + + // Add to moderator log, admin log and user notes + $log_operation = 'LOG_UNBAN_' . strtoupper($mode); + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, $log_operation, false, [$unbanned_users]); + $this->log->add('mod', $this->user->data['user_id'], $this->user->ip, $log_operation, false, [ + 'forum_id' => 0, + 'topic_id' => 0, + $unbanned_users + ]); + if (count($unbanned_users)) + { + foreach ($unbanned_users as $user_id) + { + $this->log->add('user', $this->user->data['user_id'], $this->user->ip, $log_operation, false, array( + 'reportee_id' => $user_id, + $unbanned_users + )); + } + } + } + + $this->cache->destroy(self::CACHE_KEY_INFO); + $this->cache->destroy(self::CACHE_KEY_USERS); + } + + /** + * Checks for the given user data whether the user is banned. + * Returns false if nothing was found and an array containing + * 'mode', 'end', 'reason' and 'item' otherwise. + * + * @param array $user_data The array containing the user data + * + * @return array|bool + */ + public function check(array $user_data = []) + { + if (empty($user_data)) + { + $user_data = $this->user->data; + } + + $ban_info = $this->get_info_cache(); + + foreach ($ban_info as $mode => $ban_rows) + { + /** @var type_interface $ban_mode */ + $ban_mode = $this->find_type($mode); + if ($ban_mode === false) + { + continue; + } + + if ($ban_mode->get_user_column() === null) + { + $ban_result = $ban_mode->check($ban_rows, $user_data); + if ($ban_result !== false) + { + return $ban_result + ['mode' => $mode]; + } + } + else + { + $user_column = $ban_mode->get_user_column(); + if (!isset($user_data[$user_column])) + { + continue; + } + + foreach ($ban_rows as $ban_row) + { + if (!$ban_row['end'] || $ban_row['end'] > time()) + { + if (stripos($ban_row['item'], '*') === false) + { + if ($ban_row['item'] == $user_data[$user_column]) + { + return $ban_row + ['mode' => $mode]; + } + } + else + { + $regex = '#^' . str_replace('\*', '.*?', preg_quote($ban_row['item'], '#')) . '$#i'; + if (preg_match($regex, $user_data[$user_column])) + { + return $ban_row + ['mode' => $mode]; + } + } + } + } + } + } + + return false; + } + + /** + * Returns all bans for a given ban type. False, if none were found + * + * @param string $mode The ban type for which the entries should be retrieved + * + * @return array|bool + */ + public function get_bans(string $mode) + { + /** @var type_interface $ban_type */ + $ban_type = $this->find_type($mode); + if ($ban_type === false) + { + throw new type_not_found_exception(); + } + $this->tidy(); + + return $ban_type->get_ban_options(); + } + + /** + * Returns an array of banned users with 'id' => 'end' values. + * The result is cached for performance reasons and is not as + * accurate as the check() method. (Wildcards aren't considered e.g.) + * + * @return array + */ + public function get_banned_users(): array + { + $banned_users = $this->cache->get(self::CACHE_KEY_USERS); + if ($banned_users === false) + { + $manual_modes = []; + $where_array = []; + + /** @var type_interface $ban_mode */ + foreach ($this->types as $ban_mode) + { + $user_column = $ban_mode->get_user_column(); + if (empty($user_column)) + { + $manual_modes[] = $ban_mode; + continue; + } + + $where_column = $user_column == 'user_id' ? 'b.ban_userid' : 'b.ban_item'; + + $where_array[] = ['AND', + [ + [$where_column, '=', 'u.' . $user_column], + ['b.ban_mode', '=', "'{$ban_mode->get_type()}'"], + ], + ]; + } + + $sql_array = [ + 'SELECT' => 'u.user_id, b.ban_end', + 'FROM' => [ + $this->bans_table => 'b', + $this->users_table => 'u', + ], + 'WHERE' => ['AND', + [ + ['OR', + $where_array, + ], + ['u.user_type', '<>', USER_FOUNDER], + ], + ], + ]; + $sql = $this->db->sql_build_query('SELECT', $sql_array); + $result = $this->db->sql_query($sql); + + $banned_users = []; + while ($row = $this->db->sql_fetchrow($result)) + { + $user_id = (int) $row['user_id']; + $end = (int) $row['ban_end']; + if (!isset($banned_users[$user_id]) || ($banned_users[$user_id] > 0 && $banned_users[$user_id] < $end)) + { + $banned_users[$user_id] = $end; + } + } + $this->db->sql_freeresult($result); + + /** @var type_interface $manual_mode */ + foreach ($manual_modes as $manual_mode) + { + $mode_banned_users = $manual_mode->get_banned_users(); + foreach ($mode_banned_users as $user_id => $end) + { + $user_id = (int) $user_id; + $end = (int) $end; + if (!isset($banned_users[$user_id]) || ($banned_users[$user_id] > 0 && $banned_users[$user_id] < $end)) + { + $banned_users[$user_id] = $end; + } + } + } + + $this->cache->put(self::CACHE_KEY_USERS, $banned_users, self::CACHE_TTL); + } + + return $banned_users; + } + + /** + * Get ban end + * + * @param \DateTimeInterface $ban_start Ban start time + * @param int $length Ban length in minutes + * @param string $end_date Ban end date as YYYY-MM-DD string + * @return \DateTimeInterface Ban end as DateTimeInterface instance + */ + public function get_ban_end(\DateTimeInterface $ban_start, int $length, string $end_date): \DateTimeInterface + { + $current_time = $ban_start->getTimestamp(); + $end_time = 0; + + if ($length) + { + if ($length != -1 || !$end_date) + { + $end_time = max($current_time, $current_time + ($length) * 60); + } + else + { + if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $end_date)) + { + $end_time = max( + $current_time, + \DateTime::createFromFormat('Y-m-d', $end_date, $this->user->timezone)->getTimestamp() + ); + } + else + { + throw new invalid_length_exception('LENGTH_BAN_INVALID'); + } + } + } + + $ban_end = new \DateTime(); + $ban_end->setTimestamp($end_time); + + return $ban_end; + } + + /** + * Cleans up the database of e.g. stale bans + */ + public function tidy() + { + // Delete stale bans + $sql = 'DELETE FROM ' . $this->bans_table . ' + WHERE ban_end > 0 + AND ban_end < ' . (int) time(); + $this->db->sql_query($sql); + + /** @var type_interface $type */ + foreach ($this->types as $type) + { + $type->tidy(); + } + } + + /** + * Finds the ban type for the given mode string. + * Returns false if none was found + * + * @param string $mode The mode string + * + * @return bool|type\type_interface + */ + protected function find_type(string $mode) + { + /** @var type_interface $type */ + foreach ($this->types as $type) + { + if ($type->get_type() === $mode) + { + return $type; + } + } + + return false; + } + + /** + * Returns the ban_info from the cache. + * If they're not in the cache, bans are retrieved from the database + * and then put into the cache. + * The array contains an array for each mode with respectively + * three values for 'item', 'end' and 'reason' only. + * + * @return array + */ + protected function get_info_cache(): array + { + $ban_info = $this->cache->get(self::CACHE_KEY_INFO); + if ($ban_info === false) + { + $sql = 'SELECT ban_mode, ban_item, ban_end, ban_reason_display + FROM ' . $this->bans_table; + $result = $this->db->sql_query($sql); + + $ban_info = []; + + while ($row = $this->db->sql_fetchrow($result)) + { + if (!isset($ban_info[$row['ban_mode']])) + { + $ban_info[$row['ban_mode']] = []; + } + + $ban_info[$row['ban_mode']][] = [ + 'item' => $row['ban_item'], + 'end' => $row['ban_end'], + 'reason' => $row['ban_reason_display'], + ]; + } + $this->db->sql_freeresult($result); + + $this->cache->put(self::CACHE_KEY_INFO, $ban_info, self::CACHE_TTL); + } + + return $ban_info; + } + + /** + * Get ban info message + * + * @param array $ban_row Ban data row from database + * @param string $ban_triggered_by Ban triggered by; allowed 'user', 'ip', 'email + * @param string $contact_link Contact link URL + * + * @return string Ban message + */ + public function get_ban_message(array $ban_row, string $ban_triggered_by, string $contact_link): string + { + if ($ban_row['end'] > 0) + { + $till_date = $this->user->format_date($ban_row['end']); + $ban_type = 'BOARD_BAN_TIME'; + } + else + { + $till_date = ''; + $ban_type = 'BOARD_BAN_PERM'; + } + + $message = $this->language->lang($ban_type, $till_date, '', ''); + $message .= !empty($ban_row['reason']) ? '

' . $this->language->lang('BOARD_BAN_REASON', $ban_row['reason']) : ''; + $message .= '

' . $this->language->lang('BAN_TRIGGERED_BY_' . strtoupper($ban_triggered_by)) . ''; + + return $message; + } +} diff --git a/phpBB/phpbb/ban/type/base.php b/phpBB/phpbb/ban/type/base.php new file mode 100644 index 0000000000..1ae7080991 --- /dev/null +++ b/phpBB/phpbb/ban/type/base.php @@ -0,0 +1,240 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\ban\type; + +use phpbb\db\driver\driver_interface; + +abstract class base implements type_interface +{ + /** @var driver_interface */ + protected $db; + + /** @var array */ + protected $excluded; + + /** @var string */ + protected $bans_table; + + /** @var string */ + protected $sessions_keys_table; + + /** @var string */ + protected $sessions_table; + + /** @var \phpbb\user */ + protected $user; + + /** @var string */ + protected $users_table; + + /** + * Creates a ban type. + * + * @param driver_interface $db A phpBB DBAL object + * @param string $bans_table The bans table + * @param string $users_table The users table + * @param string $sessions_table The sessions table + * @param string $sessions_keys_table The sessions keys table + */ + public function __construct(driver_interface $db, string $bans_table, string $users_table, string $sessions_table, string $sessions_keys_table) + { + $this->db = $db; + $this->bans_table = $bans_table; + $this->users_table = $users_table; + $this->sessions_table = $sessions_table; + $this->sessions_keys_table = $sessions_keys_table; + } + + /** + * {@inheritDoc} + */ + public function set_user(\phpbb\user $user): void + { + $this->user = $user; + } + + /** + * {@inheritDoc} + */ + public function after_ban(array $data): array + { + return $this->logout_affected_users($data['items']); + } + + /** + * {@inheritDoc} + */ + public function after_unban(array $data): array + { + return []; + } + + /** + * {@inheritDoc} + */ + public function check(array $ban_rows, array $user_data) + { + return false; + } + + /** + * {@inheritDoc} + */ + public function tidy(): void + { + } + + /** + * {@inheritDoc} + */ + public function get_banned_users(): array + { + return []; + } + + /** + * {@inheritDoc} + */ + public function get_ban_options(): array + { + $sql = 'SELECT * + FROM ' . $this->bans_table . ' + WHERE (ban_end >= ' . time() . " + OR ban_end = 0) + AND ban_mode = '{$this->db->sql_escape($this->get_type())}' + ORDER BY ban_item"; + $result = $this->db->sql_query($sql); + $rowset = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return $rowset; + } + + /** + * Queries users that are excluded from banning (like founders) + * from the database and saves them in $this->excluded array. + * Returns true on success and false on failure + * + * @return bool + */ + protected function get_excluded(): bool + { + $user_column = $this->get_user_column(); + if (empty($user_column)) + { + return false; + } + + $this->excluded = []; + + if (!empty($this->user)) + { + $this->excluded[$this->user->id()] = $this->user->data[$user_column]; + } + + $sql = "SELECT user_id, {$this->db->sql_escape($user_column)} + FROM {$this->users_table} + WHERE user_type = " . USER_FOUNDER; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $this->excluded[(int) $row['user_id']] = $row[$user_column]; + } + $this->db->sql_freeresult($result); + + return true; + } + + /** + * Logs out all affected users in the given array. The values + * have to match the values of the column returned by get_user_column(). + * Returns all banned users. + * + * @param array $ban_items + * + * @return array Logged out users + */ + protected function logout_affected_users(array $ban_items): array + { + $user_column = $this->get_user_column(); + + if (empty($user_column)) + { + return []; + } + + if ($user_column !== 'user_id') + { + $ban_items_sql = []; + $ban_like_items = []; + foreach ($ban_items as $ban_item) + { + if (stripos($ban_item, '*') === false) + { + $ban_items_sql[] = $ban_item; + } + else + { + $ban_like_items[] = [$user_column, 'LIKE', str_replace('*', $this->db->get_any_char(), $ban_item)]; + } + } + + $sql_array = [ + 'SELECT' => 'user_id', + 'FROM' => [ + $this->users_table => '', + ], + 'WHERE' => ['AND', + [ + ['OR', + array_merge([ + [$user_column, 'IN', $ban_items_sql] + ], $ban_like_items), + ], + ['user_id', 'NOT_IN', array_map('intval', array_keys($this->excluded))], + ], + ], + ]; + $sql = $this->db->sql_build_query('SELECT', $sql_array); + $result = $this->db->sql_query($sql); + + $user_ids = []; + while ($row = $this->db->sql_fetchrow($result)) + { + $user_ids[] = (int) $row['user_id']; + } + $this->db->sql_freeresult($result); + } + else + { + $user_ids = array_map('intval', $ban_items); + } + + if (!empty($user_ids) && !empty($this->sessions_table)) + { + $sql = 'DELETE FROM ' . $this->sessions_table . ' + WHERE ' . $this->db->sql_in_set('session_user_id', $user_ids); + $this->db->sql_query($sql); + } + if (!empty($user_ids) && !empty($this->sessions_keys_table)) + { + $sql = 'DELETE FROM ' . $this->sessions_keys_table . ' + WHERE ' . $this->db->sql_in_set('user_id', $user_ids); + $this->db->sql_query($sql); + } + + return $user_ids; + } +} diff --git a/phpBB/phpbb/ban/type/email.php b/phpBB/phpbb/ban/type/email.php new file mode 100644 index 0000000000..213084fc24 --- /dev/null +++ b/phpBB/phpbb/ban/type/email.php @@ -0,0 +1,63 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\ban\type; + +use phpbb\ban\exception\no_valid_emails_exception; + +class email extends base +{ + /** + * {@inheritDoc} + */ + public function get_type(): string + { + return 'email'; + } + + /** + * {@inheritDoc} + */ + public function get_user_column(): ?string + { + return 'user_email'; + } + + /** + * {@inheritDoc} + */ + public function prepare_for_storage(array $items): array + { + $this->get_excluded(); + + $regex = '#^.*?@.*|(([a-z0-9\-]+\.)+([a-z]{2,3}))$#i'; + + $ban_items = []; + foreach ($items as $item) + { + $item = trim($item); + if (strlen($item) > 100 || !preg_match($regex, $item) || in_array($item, $this->excluded)) + { + continue; + } + $ban_items[] = $item; + } + + if (empty($ban_items)) + { + throw new no_valid_emails_exception('NO_EMAILS_DEFINED'); + } + + return $ban_items; + } +} diff --git a/phpBB/phpbb/ban/type/ip.php b/phpBB/phpbb/ban/type/ip.php new file mode 100644 index 0000000000..7f8c6ee81d --- /dev/null +++ b/phpBB/phpbb/ban/type/ip.php @@ -0,0 +1,93 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\ban\type; + +use phpbb\ban\exception\no_valid_ips_exception; +use Symfony\Component\HttpFoundation\IpUtils; + +class ip extends base +{ + private const USER_IP = 'user_ip'; + + /** + * @inheritDoc + */ + public function get_type(): string + { + return 'ip'; + } + + /** + * @inheritDoc + */ + public function get_user_column(): ?string + { + return null; + } + + /** + * {@inheritDoc} + */ + public function check(array $ban_rows, array $user_data) + { + if (!isset($user_data[self::USER_IP])) + { + return false; + } + + foreach ($ban_rows as $ip_ban) + { + if (IpUtils::checkIp($user_data[self::USER_IP], $ip_ban['item'])) + { + return $ip_ban; + } + } + + return false; + } + + /** + * @inheritDoc + */ + public function prepare_for_storage(array $items): array + { + $ban_items = []; + foreach ($items as $ip) + { + try + { + // Misuse checkIp for checking validity of IP. Should return true if defined IP is valid. + if (!IpUtils::checkIp($ip, $ip)) + { + continue; + } + + $ban_items[] = $ip; + } + // @codeCoverageIgnoreStart + catch (\RuntimeException $exception) + { + // IPv6 not supported, therefore IPv6 address will not be added + } + // @codeCoverageIgnoreEnd + } + + if (empty($ban_items)) + { + throw new no_valid_ips_exception('NO_IPS_DEFINED'); + } + + return $ban_items; + } +} diff --git a/phpBB/phpbb/ban/type/type_interface.php b/phpBB/phpbb/ban/type/type_interface.php new file mode 100644 index 0000000000..db86fc8ae7 --- /dev/null +++ b/phpBB/phpbb/ban/type/type_interface.php @@ -0,0 +1,126 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\ban\type; + +/** + * Interface implemented by all ban types + */ +interface type_interface +{ + /** + * Returns the type identifier for this ban type + * + * @return string + */ + public function get_type(): string; + + /** + * Returns the column in the users table which contains + * the values that should be looked for when checking a ban. + * If it returns null, the check method will be called when + * checking for bans. + * + * @return string|null + */ + public function get_user_column(): ?string; + + /** + * Sets a user object to the ban type to have it excluded + * from banning. + * + * @param \phpbb\user $user An user object + * + * @return void + */ + public function set_user(\phpbb\user $user): void; + + /** + * Gives the possibility to do some clean up after banning. + * The return value of this method will be passed through + * to the caller. + * + * @param array $data An array containing information about + * the bans, like the reason or the start + * and end of the ban + * + * @return array List of banned users + */ + public function after_ban(array $data): array; + + /** + * Gives the possibility to do some clean up after unbanning. + * The return value of this method will be passed through + * to the caller. + * + * @param array $data An array containing information about + * the unbans, e.g. the unbanned items. + * + * @return array List of unbanned users + */ + public function after_unban(array $data): array; + + /** + * In the case that get_user_column() returns null, this method + * is called when checking the ban status. + * Please note, that this method is basically called on every page, + * so the check should perform rather fast. + * + * Returns an array with information about the ban, like the end or + * the reason. False if the user is not banned. + * + * @param array $ban_rows An array containing the ban rows retrieved + * from the database for this specific mode. + * They contain the item, reason and end of the ban. + * @param array $user_data The user data + * + * @return array|bool + */ + public function check(array $ban_rows, array $user_data); + + /** + * In case get_user_column() returns no string, this method will be called + * when a list of banned users is retrieved. + * Returns a list of banned users. + * The result is cached and is not used for ban checking, so the accuracy + * of the results is not as important as when *really* checking in check() + * + * @return array An array of banned users, where the user ids are the keys + * and the value is the end of the ban (or 0 if permanent) + */ + public function get_banned_users(): array; + + /** + * Get ban options mapping ban ID to an option to display to admins + * + * @return array + */ + public function get_ban_options(): array; + + /** + * Prepares the given ban items before saving them in the database + * + * @param array $items + * + * @return array + */ + public function prepare_for_storage(array $items): array; + + /** + * Does some cleanup work for the banning mode. + * Is called before banning and unbanning and as cron job. + * + * @return void + */ + public function tidy(): void; +} diff --git a/phpBB/phpbb/ban/type/user.php b/phpBB/phpbb/ban/type/user.php new file mode 100644 index 0000000000..bfd28e323c --- /dev/null +++ b/phpBB/phpbb/ban/type/user.php @@ -0,0 +1,156 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\ban\type; + +use phpbb\ban\exception\no_valid_users_exception; + +class user extends base +{ + /** @var array */ + private $banned_users; + + /** + * {@inheritDoc} + */ + public function get_type(): string + { + return 'user'; + } + + /** + * {@inheritDoc} + */ + public function get_user_column(): string + { + return 'user_id'; + } + + /** + * {@inheritDoc} + */ + public function after_ban(array $data): array + { + $this->logout_affected_users($data['items']); + return $this->banned_users; + } + + /** + * {@inheritDoc} + */ + public function after_unban(array $data): array + { + $user_ids = array_map('intval', $data['items']); + + $sql = 'SELECT user_id, username + FROM ' . $this->users_table . ' + WHERE ' . $this->db->sql_in_set('user_id', $user_ids); + $result = $this->db->sql_query($sql); + + $unbanned_users = []; + while ($row = $this->db->sql_fetchrow($result)) + { + $unbanned_users[(int) $row['user_id']] = $row['username']; + } + $this->db->sql_freeresult($result); + + return $unbanned_users; + } + + /** + * {@inheritDoc} + */ + public function get_ban_options(): array + { + $ban_options = []; + + $sql = 'SELECT b.*, u.user_id, u.username, u.username_clean + FROM ' . $this->bans_table . ' b, ' . $this->users_table . ' u + WHERE (b.ban_end >= ' . time() . " + OR b.ban_end = 0) + AND b.ban_userid = u.user_id + AND b.ban_mode = '{$this->db->sql_escape($this->get_type())}' + ORDER BY u.username_clean ASC"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $row['label'] = $row['username']; + $ban_options[] = $row; + } + $this->db->sql_freeresult($result); + + return $ban_options; + } + + /** + * {@inheritDoc} + */ + public function prepare_for_storage(array $items): array + { + // Fill excluded user list + $this->get_excluded(); + + // Prevent banning of anonymous + $this->excluded[ANONYMOUS] = ANONYMOUS; + + $sql_usernames = []; + $sql_or_like = []; + foreach ($items as $item) + { + $cleaned_username = utf8_clean_string($item); + if (stripos($cleaned_username, '*') === false) + { + $sql_usernames[] = $cleaned_username; + } + else + { + $sql_or_like[] = ['username_clean', 'LIKE', str_replace('*', $this->db->get_any_char(), $cleaned_username)]; + } + } + + $sql_array = [ + 'SELECT' => 'user_id, username', + 'FROM' => [ + $this->users_table => '', + ], + 'WHERE' => ['AND', + [ + ['OR', + array_merge([ + ['username_clean', 'IN', $sql_usernames] + ], $sql_or_like), + ], + ['user_id', 'NOT_IN', array_map('intval', $this->excluded)], + ], + ], + ]; + $sql = $this->db->sql_build_query('SELECT', $sql_array); + $result = $this->db->sql_query($sql); + + $ban_items = []; + $this->banned_users = []; + while ($row = $this->db->sql_fetchrow($result)) + { + $ban_items[] = (string) $row['user_id']; + $this->banned_users[(int) $row['user_id']] = $row['username']; + } + $this->db->sql_freeresult($result); + + if (empty($ban_items)) + { + throw new no_valid_users_exception('NO_USER_SPECIFIED'); + } + + return $ban_items; + } +} diff --git a/phpBB/phpbb/db/migration/data/v400/ban_table_p1.php b/phpBB/phpbb/db/migration/data/v400/ban_table_p1.php new file mode 100644 index 0000000000..f58dff52e3 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/ban_table_p1.php @@ -0,0 +1,179 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +use phpbb\db\migration\migration; + +class ban_table_p1 extends migration +{ + public static function depends_on(): array + { + return ['\phpbb\db\migration\data\v320\default_data_type_ids']; + } + + public function update_schema(): array + { + return [ + 'add_tables' => [ + $this->table_prefix . 'bans' => [ + 'COLUMNS' => [ + 'ban_id' => ['ULINT', null, 'auto_increment'], + 'ban_userid' => ['ULINT', 0], + 'ban_mode' => ['VCHAR', ''], + 'ban_item' => ['STEXT_UNI', ''], + 'ban_start' => ['TIMESTAMP', 0], + 'ban_end' => ['TIMESTAMP', 0], + 'ban_reason' => ['VCHAR_UNI', ''], + 'ban_reason_display' => ['VCHAR_UNI', ''], + ], + 'PRIMARY_KEY' => 'ban_id', + 'KEYS' => [ + 'ban_userid' => ['INDEX', 'ban_userid'], + 'ban_end' => ['INDEX', 'ban_end'], + ], + ], + ], + ]; + } + + public function revert_schema(): array + { + return [ + 'drop_tables' => [ + $this->table_prefix . 'bans', + ], + ]; + } + + public function update_data(): array + { + return [ + ['custom', [[$this, 'old_to_new']]], + ]; + } + + public function revert_data(): array + { + return [ + ['custom', [[$this, 'new_to_old']]], + ]; + } + + public function old_to_new($start) + { + $start = (int) $start; + $limit = 500; + $processed_rows = 0; + + $sql = 'SELECT * + FROM ' . $this->table_prefix . 'banlist'; + $result = $this->db->sql_query_limit($sql, $limit, $start); + + $bans = []; + while ($row = $this->db->sql_fetchrow($result)) + { + $processed_rows++; + + if ($row['ban_exclude']) + { + continue; + } + + $row['ban_userid'] = (int) $row['ban_userid']; + $item = $mode = ''; + if ($row['ban_ip'] !== '') + { + $mode = 'ip'; + $item = $row['ban_ip']; + } + else if ($row['ban_email'] !== '') + { + $mode = 'email'; + $item = $row['ban_email']; + } + else if ($row['ban_userid'] !== 0) + { + $mode = 'user'; + $item = $row['ban_userid']; + } + + if ($mode === '' || $item === '') + { + continue; + } + + $bans[] = [ + 'ban_mode' => $mode, + 'ban_userid' => $row['ban_userid'], + 'ban_item' => $item, + 'ban_start' => $row['ban_start'], + 'ban_end' => $row['ban_end'], + 'ban_reason' => $row['ban_reason'], + 'ban_reason_display' => $row['ban_give_reason'], + ]; + } + $this->db->sql_freeresult($result); + + if ($processed_rows > 0) + { + $this->db->sql_multi_insert($this->table_prefix . 'bans', $bans); + } + else if ($processed_rows < $limit) + { + return; + } + + return $limit + $start; + } + + public function new_to_old($start) + { + $start = (int) $start; + $limit = 500; + $processed_rows = 0; + + $sql = 'SELECT * + FROM ' . $this->table_prefix . 'bans'; + $result = $this->db->sql_query_limit($sql, $limit, $start); + + $bans = []; + while ($row = $this->db->sql_fetchrow($result)) + { + $processed_rows++; + + $bans[] = [ + 'ban_userid' => (int) $row['ban_userid'], + 'ban_ip' => ($row['ban_mode'] === 'ip') ? $row['ban_item'] : '', + 'ban_email' => ($row['ban_mode'] === 'email') ? $row['ban_item'] : '', + 'ban_start' => $row['ban_start'], + 'ban_end' => $row['ban_end'], + 'ban_exclude' => false, + 'ban_reason' => $row['ban_reason'], + 'ban_give_reason' => $row['ban_reason_display'], + ]; + } + $this->db->sql_freeresult($result); + + if ($processed_rows > 0) + { + $this->db->sql_multi_insert($this->table_prefix . 'banlist', $bans); + } + else if ($processed_rows < $limit) + { + return; + } + + return $limit + $start; + } +} diff --git a/phpBB/phpbb/db/migration/data/v400/ban_table_p2.php b/phpBB/phpbb/db/migration/data/v400/ban_table_p2.php new file mode 100644 index 0000000000..1f31c1ec57 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/v400/ban_table_p2.php @@ -0,0 +1,59 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v400; + +class ban_table_p2 extends \phpbb\db\migration\migration +{ + public static function depends_on(): array + { + return ['\phpbb\db\migration\data\v400\ban_table_p1']; + } + + public function update_schema(): array + { + return [ + 'drop_tables' => [ + $this->table_prefix . 'banlist', + ], + ]; + } + + public function revert_schema(): array + { + return [ + 'add_tables' => [ + $this->table_prefix . 'banlist' => [ + 'COLUMNS' => [ + 'ban_id' => ['ULINT', null, 'auto_increment'], + 'ban_userid' => ['ULINT', 0], + 'ban_ip' => ['VCHAR:40', ''], + 'ban_email' => ['VCHAR_UNI:100', ''], + 'ban_start' => ['TIMESTAMP', 0], + 'ban_end' => ['TIMESTAMP', 0], + 'ban_exclude' => ['BOOL', 0], + 'ban_reason' => ['VCHAR_UNI', ''], + 'ban_give_reason' => ['VCHAR_UNI', ''], + ], + 'PRIMARY_KEY' => 'ban_id', + 'KEYS' => [ + 'ban_end' => ['INDEX', 'ban_end'], + 'ban_user' => ['INDEX', ['ban_userid', 'ban_exclude']], + 'ban_email' => ['INDEX', ['ban_email', 'ban_exclude']], + 'ban_ip' => ['INDEX', ['ban_ip', 'ban_exclude']], + ], + ], + ], + ]; + } +} diff --git a/phpBB/phpbb/session.php b/phpBB/phpbb/session.php index 9d3f85b9be..4c2471ab92 100644 --- a/phpBB/phpbb/session.php +++ b/phpBB/phpbb/session.php @@ -1136,119 +1136,23 @@ class session */ function check_ban($user_id = false, $user_ips = false, $user_email = false, $return = false) { - global $db, $phpbb_dispatcher; + global $phpbb_container, $phpbb_dispatcher; if (defined('IN_CHECK_BAN') || defined('SKIP_CHECK_BAN')) { return false; } - $banned = false; - $cache_ttl = 3600; - $where_sql = array(); - - $sql = 'SELECT ban_ip, ban_userid, ban_email, ban_exclude, ban_give_reason, ban_end - FROM ' . BANLIST_TABLE . ' - WHERE '; - - // Determine which entries to check, only return those - if ($user_email === false) + /** @var \phpbb\ban\manager $ban_manager */ + $ban_manager = $phpbb_container->get('ban.manager'); + $ban_row = $ban_manager->check(['user_id' => $user_id, 'user_email' => $user_email]); + if (empty($ban_row)) { - $where_sql[] = "ban_email = ''"; + return false; } - if ($user_ips === false) - { - $where_sql[] = "(ban_ip = '' OR ban_exclude = 1)"; - } - - if ($user_id === false) - { - $where_sql[] = '(ban_userid = 0 OR ban_exclude = 1)'; - } - else - { - $cache_ttl = ($user_id == ANONYMOUS) ? 3600 : 0; - $_sql = '(ban_userid = ' . $user_id; - - if ($user_email !== false) - { - $_sql .= " OR ban_email <> ''"; - } - - if ($user_ips !== false) - { - $_sql .= " OR ban_ip <> ''"; - } - - $_sql .= ')'; - - $where_sql[] = $_sql; - } - - $sql .= (count($where_sql)) ? implode(' AND ', $where_sql) : ''; - $result = $db->sql_query($sql, $cache_ttl); - - $ban_triggered_by = 'user'; - while ($row = $db->sql_fetchrow($result)) - { - if ($row['ban_end'] && $row['ban_end'] < time()) - { - continue; - } - - $ip_banned = false; - if (!empty($row['ban_ip'])) - { - if (!is_array($user_ips)) - { - $ip_banned = preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_ip'], '#')) . '$#i', $user_ips); - } - else - { - foreach ($user_ips as $user_ip) - { - if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_ip'], '#')) . '$#i', $user_ip)) - { - $ip_banned = true; - break; - } - } - } - } - - if ((!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id) || - $ip_banned || - (!empty($row['ban_email']) && preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_email'], '#')) . '$#i', $user_email))) - { - if (!empty($row['ban_exclude'])) - { - $banned = false; - break; - } - else - { - $banned = true; - $ban_row = $row; - - if (!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id) - { - $ban_triggered_by = 'user'; - } - else if ($ip_banned) - { - $ban_triggered_by = 'ip'; - } - else - { - $ban_triggered_by = 'email'; - } - - // Don't break. Check if there is an exclude rule for this user - } - } - } - $db->sql_freeresult($result); + $banned = true; + $ban_triggered_by = $ban_row['mode']; /** * Event to set custom ban type @@ -1266,7 +1170,7 @@ class session if ($banned && !$return) { - global $phpEx; + global $config, $phpbb_root_path, $phpEx; // Initiate environment ... since it won't be set at this stage $this->setup(); @@ -1300,7 +1204,8 @@ class session } // Determine which message to output - $message = $this->get_ban_message($ban_row, $ban_triggered_by); + $contact_link = phpbb_get_board_contact_link($config, $phpbb_root_path, $phpEx); + $message = $ban_manager->get_ban_message($ban_row, $ban_triggered_by, $contact_link); // A very special case... we are within the cron script which is not supposed to print out the ban message... show blank page if (defined('IN_CRON')) @@ -1344,19 +1249,6 @@ class session } } - /** - * Get ban info message - * - * @param array $ban_row Ban data row from database - * @param string $ban_triggered_by Ban triggered by; allowed 'user', 'ip', 'email' - * - * @return string - */ - protected function get_ban_message(array $ban_row, string $ban_triggered_by): string - { - return ($ban_row['ban_end']) ? 'BOARD_BAN_TIME' : 'BOARD_BAN_PERM'; - } - /** * Check if ip is blacklisted by Spamhaus SBL * diff --git a/phpBB/phpbb/user.php b/phpBB/phpbb/user.php index 52427d2915..bf4f10c15a 100644 --- a/phpBB/phpbb/user.php +++ b/phpBB/phpbb/user.php @@ -879,12 +879,12 @@ class user extends \phpbb\session { global $config, $phpbb_root_path, $phpEx; - $till_date = ($ban_row['ban_end']) ? $this->format_date($ban_row['ban_end']) : ''; - $message = ($ban_row['ban_end']) ? 'BOARD_BAN_TIME' : 'BOARD_BAN_PERM'; + $till_date = ($ban_row['end']) ? $this->format_date($ban_row['end']) : ''; + $message = ($ban_row['end']) ? 'BOARD_BAN_TIME' : 'BOARD_BAN_PERM'; $contact_link = phpbb_get_board_contact_link($config, $phpbb_root_path, $phpEx); $message = $this->language->lang($message, $till_date, '', ''); - $message .= ($ban_row['ban_give_reason']) ? '

' . $this->language->lang('BOARD_BAN_REASON', $ban_row['ban_give_reason']) : ''; + $message .= ($ban_row['reason']) ? '

' . $this->language->lang('BOARD_BAN_REASON', $ban_row['reason']) : ''; $message .= '

' . $this->language->lang('BAN_TRIGGERED_BY_' . strtoupper($ban_triggered_by)) . ''; return $message; diff --git a/phpBB/styles/prosilver/template/mcp_ban.html b/phpBB/styles/prosilver/template/mcp_ban.html index ed85e294c2..379c992921 100644 --- a/phpBB/styles/prosilver/template/mcp_ban.html +++ b/phpBB/styles/prosilver/template/mcp_ban.html @@ -67,13 +67,6 @@
-
-

{L_BAN_EXCLUDE_EXPLAIN}
-
- - -
-
diff --git a/tests/ban/ban_manager_test.php b/tests/ban/ban_manager_test.php new file mode 100644 index 0000000000..2eb49686d6 --- /dev/null +++ b/tests/ban/ban_manager_test.php @@ -0,0 +1,766 @@ +createXMLDataSet(__DIR__ . '/fixtures/sessions_banlist.xml'); + } + + public function setUp(): void + { + parent::setUp(); + + global $config, $phpbb_dispatcher, $phpbb_root_path, $phpEx; + + $language = new \phpbb\language\language(new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx)); + $user = new \phpbb\user($language, '\phpbb\datetime'); + $user->data['user_id'] = 2; + $user->data['user_email'] = 'foo@bar.com'; + $user->data['user_timezone'] = 0; + $config = new \phpbb\config\config([]); + $phpbb_dispatcher = new \phpbb_mock_event_dispatcher(); + + $phpbb_container = new \phpbb_mock_container_builder(); + $ban_type_email = new \phpbb\ban\type\email($this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $ban_type_user = new \phpbb\ban\type\user($this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $ban_type_ip = new \phpbb\ban\type\ip($this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $phpbb_container->set('ban.type.email', $ban_type_email); + $phpbb_container->set('ban.type.user', $ban_type_user); + $phpbb_container->set('ban.type.ip', $ban_type_ip); + $collection = new \phpbb\di\service_collection($phpbb_container); + $collection->add('ban.type.email'); + $collection->add('ban.type.user'); + $collection->add('ban.type.ip'); + $phpbb_log = new \phpbb\log\dummy(); + + $this->ban_manager = new \phpbb\ban\manager($collection, new \phpbb\cache\driver\dummy(), $this->db, $language, $phpbb_log, $user, 'phpbb_bans', 'phpbb_users'); + $phpbb_container->set('ban.manager', $this->ban_manager); + $this->phpbb_container = $phpbb_container; + } + + public function data_check_ban(): array + { + return [ + [ + [], + false + ], + [ + ['user_ip' => '127.0.0.1'], + [ + 'item' => '127.0.0.1', + 'end' => '0', + 'reason' => '1', + 'mode' => 'ip', + ], + ], + [ + ['user_ip' => '10.0.0.1'], // first IP for 10.0.0.1/28 range + [ + 'item' => '10.0.0.1/28', + 'end' => '0', + 'reason' => '1', + 'mode' => 'ip', + ], + ], + [ + ['user_ip' => '10.0.0.14'], // last IP for 10.0.0.1/28 range + [ + 'item' => '10.0.0.1/28', + 'end' => '0', + 'reason' => '1', + 'mode' => 'ip', + ], + ], + [ + ['user_ip' => '10.0.0.15'], // first IP outside 10.0.0.1/28 range + [ + 'item' => '10.0.0.1/28', + 'end' => '0', + 'reason' => '1', + 'mode' => 'ip', + ], + ], + [ + ['user_ip' => '2001:4860:4860::8888'], // first IP in 2001:4860:4860::8888/12 range + [ + 'item' => '2001:4860:4860::8888/12', + 'end' => '0', + 'reason' => '1', + 'mode' => 'ip', + ], + ], + [ + ['user_ip' => '200F:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF'], // last IP in 2001:4860:4860::8888/12 range + [ + 'item' => '2001:4860:4860::8888/12', + 'end' => '0', + 'reason' => '1', + 'mode' => 'ip', + ], + ], + [ + ['user_ip' => '2010:4860:4860::1'], // IP outside the 2001:4860:4860::8888/12 range + false, + ], + [ + ['user_id' => 2], + false, + ], + [ + ['user_id' => 5], // there is only an expired ban + false, + ], + [ + ['user_id' => 4], + [ + 'item' => '4', + 'end' => '0', + 'reason' => '1', + 'mode' => 'user', + ], + ], + [ + ['user_email' => 'test@phpbb.com'], + false, + ], + [ + ['user_email' => 'bar@example.org'], + [ + 'item' => 'bar@example.org', + 'end' => '0', + 'reason' => '1', + 'mode' => 'email', + ], + ], + [ + ['user_email' => 'test@foo.bar'], + [ + 'item' => '*@foo.bar', + 'end' => '0', + 'reason' => '1', + 'mode' => 'email', + ], + ], + ]; + } + + /** + * @dataProvider data_check_ban + */ + public function test_check_ban($user_data, $expected) + { + $this->assertEquals($expected, $this->ban_manager->check($user_data)); + } + + public function data_get_bans(): array + { + return [ + [ + 'foo', + '', + type_not_found_exception::class + ], + [ + 'ip', + [ + [ + 'ban_id' => '6', + 'ban_userid' => 0, + 'ban_item' => '10.0.0.1/28', + 'ban_start' => '1111', + 'ban_end' => '0', + 'ban_reason' => 'HAHAHA', + 'ban_reason_display' => '1', + 'ban_mode' => 'ip', + ], + [ + 'ban_id' => '2', + 'ban_userid' => 0, + 'ban_item' => '127.0.0.1', + 'ban_start' => '1111', + 'ban_end' => '0', + 'ban_reason' => 'HAHAHA', + 'ban_reason_display' => '1', + 'ban_mode' => 'ip', + ], + [ + 'ban_id' => '3', + 'ban_userid' => 0, + 'ban_item' => '127.1.1.1', + 'ban_start' => '1111', + 'ban_end' => '0', + 'ban_reason' => 'HAHAHA', + 'ban_reason_display' => '1', + 'ban_mode' => 'ip', + ], + [ + 'ban_id' => '7', + 'ban_userid' => 0, + 'ban_item' => '2001:4860:4860::8888/12', + 'ban_start' => '1111', + 'ban_end' => '0', + 'ban_reason' => 'HAHAHA', + 'ban_reason_display' => '1', + 'ban_mode' => 'ip', + ], + ], + ], + [ + 'email', + [ + [ + 'ban_id' => '9', + 'ban_userid' => 0, + 'ban_item' => '*@foo.bar', + 'ban_start' => '1111', + 'ban_end' => '0', + 'ban_reason' => 'HAHAHA', + 'ban_reason_display' => '1', + 'ban_mode' => 'email', + ], + [ + 'ban_id' => '5', + 'ban_userid' => 0, + 'ban_item' => 'bar@example.org', + 'ban_start' => '1111', + 'ban_end' => '0', + 'ban_reason' => 'HAHAHA', + 'ban_reason_display' => '1', + 'ban_mode' => 'email', + ], + ], + ], + [ + 'user', + [ + [ + 'ban_id' => '4', + 'ban_item' => '4', + 'ban_start' => '1111', + 'ban_end' => '0', + 'ban_reason' => 'HAHAHA', + 'ban_reason_display' => '1', + 'ban_mode' => 'user', + 'ban_userid' => 4, + 'user_id' => '4', + 'username' => 'ipv6_user', + 'username_clean' => 'ipv6_user', + 'label' => 'ipv6_user', + ], + ], + ], + ]; + } + + /** + * @dataProvider data_get_bans + */ + public function test_get_bans($ban_type, $expected, $expected_exception = false) + { + if ($expected_exception !== false) + { + $this->expectException($expected_exception); + } + + $actual = $this->ban_manager->get_bans($ban_type); + // Sort both arrays by ban_item to be synced + if (is_array($expected) && !empty($actual)) + { + usort($expected, function($a, $b) + { + return strcmp($a['ban_item'], $b['ban_item']) <=> 0; + } + ); + usort($actual, function($a, $b) + { + return strcmp($a['ban_item'], $b['ban_item']) <=> 0; + } + ); + } + $this->assertEquals($expected, $actual); + } + + public function data_get_ban_end(): array + { + return [ + [ + 0, + 20, + 0, + ], + [ + 80, // 1 minute plus 20 seconds + 20, + 1, + ], + [ + 20, + 20, + -1, + ], + [ + 2 * 86400, // Ban end should be before this time + 20, + -1, + '1970-01-02', + ], + [ + 0, + 20, + -1, + '1970-01-02-15:30', // wrong format + invalid_length_exception::class, + ], + ]; + } + + /** + * @dataProvider data_get_ban_end + */ + public function test_get_ban_end($expected, $ban_start, $length, $end_date = '', $expected_exception = false) + { + if ($expected_exception) + { + $this->expectException($expected_exception); + } + + $start_time = new \DateTime(); + $start_time->setTimestamp($ban_start); + + $expected_end = new \DateTime(); + $expected_end->setTimestamp($expected); + + $ban_end = $this->ban_manager->get_ban_end($start_time, $length, $end_date); + + if ($length >= 0 || !$end_date) + { + $this->assertEquals($expected_end, $ban_end); + } + else + { + $this->assertLessThan($expected_end, $ban_end); + $this->assertGreaterThan($start_time, $ban_end); + } + } + + public function test_get_banned_users() + { + $banned_users = $this->ban_manager->get_banned_users(); + $this->assertEquals( + [ + 4 => 0, + 5 => 0 + ], + $banned_users + ); + } + + public function test_get_banned_users_own_method() + { + global $phpbb_root_path, $phpEx; + + $phpbb_container = new \phpbb_mock_container_builder(); + $ban_type_email = new \phpbb\ban\type\email($this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $ban_type_user = new \phpbb\ban\type\user($this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $ban_type_ip = $this->getMockBuilder(\phpbb\ban\type\ip::class) + ->setConstructorArgs([$this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys']) + ->getMock(); + $ban_type_ip->method('get_banned_users') + ->willReturn([19 => 1234, 20 => 0]); + $phpbb_container->set('ban.type.email', $ban_type_email); + $phpbb_container->set('ban.type.user', $ban_type_user); + $phpbb_container->set('ban.type.ip', $ban_type_ip); + $collection = new \phpbb\di\service_collection($phpbb_container); + $collection->add('ban.type.email'); + $collection->add('ban.type.user'); + $collection->add('ban.type.ip'); + + $language = new \phpbb\language\language(new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx)); + $user = new \phpbb\user($language, '\phpbb\datetime'); + $phpbb_log = new \phpbb\log\dummy(); + + $ban_manager = new \phpbb\ban\manager($collection, new \phpbb\cache\driver\dummy(), $this->db, $language, $phpbb_log, $user, 'phpbb_bans', 'phpbb_users'); + + $this->assertEquals( + [ + 4 => 0, + 5 => 0, + 19 => 1234, + 20 => 0, + ], + $ban_manager->get_banned_users() + ); + + $ban_type_ip_reflection = new \ReflectionClass($ban_type_ip); + $get_excluded_reflection = $ban_type_ip_reflection->getMethod('get_excluded'); + $get_excluded_reflection->setAccessible(true); + $this->assertFalse($get_excluded_reflection->invoke($ban_type_ip)); + } + + public function test_ban_empty_ban_items() + { + global $phpbb_root_path, $phpEx; + + $phpbb_container = new \phpbb_mock_container_builder(); + $ban_type_email = new \phpbb\ban\type\email($this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $ban_type_user = new \phpbb\ban\type\user($this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $ban_type_ip = $this->getMockBuilder(\phpbb\ban\type\ip::class) + ->setConstructorArgs([$this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys']) + ->getMock(); + $ban_type_ip->method('prepare_for_storage') + ->willReturn([]); + $ban_type_ip->method('get_type') + ->willReturn('ip'); + $phpbb_container->set('ban.type.email', $ban_type_email); + $phpbb_container->set('ban.type.user', $ban_type_user); + $phpbb_container->set('ban.type.ip', $ban_type_ip); + $collection = new \phpbb\di\service_collection($phpbb_container); + $collection->add('ban.type.email'); + $collection->add('ban.type.user'); + $collection->add('ban.type.ip'); + + $language = new \phpbb\language\language(new \phpbb\language\language_file_loader($phpbb_root_path, $phpEx)); + $user = new \phpbb\user($language, '\phpbb\datetime'); + $phpbb_log = new \phpbb\log\dummy(); + + $ban_manager = new \phpbb\ban\manager($collection, new \phpbb\cache\driver\dummy(), $this->db, $language, $phpbb_log, $user, 'phpbb_bans', 'phpbb_users'); + + $start_time = new \DateTime(); + $start_time->setTimestamp(1000); + $end_time = new \DateTime(); + $end_time->setTimestamp(0); + + $this->assertFalse($ban_manager->ban( + 'ip', + ['192.168.1.1'], + $start_time, + $end_time, + '' + )); + } + + public function data_test_ban(): array + { + return [ + [ + 'user', + ['normal_user'], + 1000, + 500, // end before start + '', + '', + false, + invalid_length_exception::class, + ], + [ + 'foo', // invalid ban type + ['normal_user'], + 1000, + 0, // end before start + '', + '', + false, + type_not_found_exception::class, + ], + [ + 'user', + [], // empty user list + 1000, + 0, + '', + '', + false, + no_valid_users_exception::class, + ], + [ + 'user', + ['founder'], // user same as current user + 1000, + 0, + '', + '', + false, + no_valid_users_exception::class, + ], + [ + 'user', + ['normal_user'], + 1000, + 0, + '', + '', + true, + ], + [ + 'user', + ['normal_u*'], + 1000, + 0, + '', + '', + true, + ], + [ + 'ip', + [], + 1000, + 0, + '', + '', + false, + no_valid_ips_exception::class, + ], + [ + 'ip', + ['192.168.I.1'], // invalid IP + 1000, + 0, + '', + '', + false, + no_valid_ips_exception::class, + ], + [ + 'ip', + ['192.168.1.1'], + 1000, + 0, + '', + '', + true, + ], + [ + 'email', + ['this_is_not_an_email'], + 1000, + 0, + '', + '', + false, + no_valid_emails_exception::class + ], + [ + 'email', + ['test@example.com'], + 1000, + 0, + '', + '', + true, + ], + [ + 'email', + ['*@foo.bar'], + 1000, + 0, + '', + '', + true, + ], + [ + 'email', + ['test@example.com', str_repeat('a', 100) . '@example.com'], // one email too long, shouldn't cause any issues though + 1000, + 0, + '', + '', + true, + ], + ]; + } + + /** + * @dataProvider data_test_ban + */ + public function test_ban($mode, $items, $start, $end, $reason, $display_reason, $expected, $expected_exception = '') + { + if ($expected_exception) + { + $this->expectException($expected_exception); + } + + $start_time = new \DateTime(); + $start_time->setTimestamp($start); + $end_time = new \DateTime(); + $end_time->setTimestamp($end); + + $ban_return = $this->ban_manager->ban($mode, $items, $start_time, $end_time, $reason, $display_reason); + + $this->assertEquals($expected, $ban_return); + } + + public function test_ban_actual() + { + $start_time = new \DateTime(); + $start_time->setTimestamp(1000); + $end_time = new \DateTime(); + $end_time->setTimestamp(0); + + $ban_return = $this->ban_manager->ban('ip', ['121.122.123.124'], $start_time, $end_time, '', 'because'); + + $this->assertTrue($ban_return); + + $this->assertEquals( + [ + 'item' => '121.122.123.124', + 'end' => 0, + 'reason' => 'because', + 'mode' => 'ip' + ], + $this->ban_manager->check(['user_ip' => '121.122.123.124']) + ); + } + + public function data_test_unban(): array + { + return [ + [ + 'does_not_exist', + [10], + [], + type_not_found_exception::class + ], + [ + 'user', + [4], + [ + [ + 'ban_id' => '4', + 'ban_userid' => '4', + 'ban_item' => '4', + 'ban_start' => '1111', + 'ban_end' => '0', + 'ban_reason' => 'HAHAHA', + 'ban_reason_display' => '1', + 'ban_mode' => 'user', + 'user_id' => '4', + 'username' => 'ipv6_user', + 'username_clean' => 'ipv6_user', + 'label' => 'ipv6_user', + ], + ], + ], + ]; + } + + /** + * @dataProvider data_test_unban + */ + public function test_unban($mode, $items, $expected, $expected_exception = '') + { + if ($expected_exception) + { + $this->expectException($expected_exception); + } + + $before_bans = $this->ban_manager->get_bans($mode); + + $this->ban_manager->unban($mode, $items); + + $after_bans = $this->ban_manager->get_bans($mode); + + $ban_diff = array_diff_assoc($before_bans, count($after_bans) ? $after_bans : []); + + $this->assertEquals($expected, $ban_diff); + } + + public function test_unban_invalid_type() + { + $this->expectException(type_not_found_exception::class); + + $this->ban_manager->unban('does_not_exist', []); + } + + public function test_base_type_methods() + { + $ban_type_ip = $this->phpbb_container->get('ban.type.ip'); + $base_type_reflection = new \ReflectionClass(\phpbb\ban\type\base::class); + $after_unban = $base_type_reflection->getMethod('after_unban'); + $this->assertEquals([], $after_unban->invoke($ban_type_ip, ['items' => ['foo']])); + + $check = $base_type_reflection->getMethod('check'); + $this->assertFalse($check->invoke($ban_type_ip, [], [])); + } + + public function data_get_ban_message(): array + { + return [ + [ + [ + 'end' => 0, + ], + 'foobar', + 'http://foo.bar', + 'You have been permanently banned from this board.

Please contact the Board Administrator for more information.

BAN_TRIGGERED_BY_FOOBAR', + ], + [ + [ + 'end' => 1, + ], + 'foobar', + 'http://foo.bar', + 'You have been banned from this board until .

Please contact the Board Administrator for more information.

BAN_TRIGGERED_BY_FOOBAR', + ], + [ + [ + 'end' => 1, + 'reason' => 'just because', + ], + 'foobar', + 'http://foo.bar', + 'You have been banned from this board until .

Please contact the Board Administrator for more information.

Reason given for ban: just because

BAN_TRIGGERED_BY_FOOBAR', + ], + ]; + } + + /** + * @dataProvider data_get_ban_message + */ + public function test_get_ban_message($ban_row, $ban_triggered_by, $contact_link, $expected) + { + $this->assertEquals($expected, $this->ban_manager->get_ban_message($ban_row, $ban_triggered_by, $contact_link)); + } + + public function test_get_ban_options_user() + { + $foo = $this->ban_manager->get_bans('user'); + + $this->assertEquals( + [ + [ + 'ban_id' => 4, + 'ban_userid' => '4', + 'ban_mode' => 'user', + 'ban_item' => '4', + 'ban_start' => '1111', + 'ban_end' => '0', + 'ban_reason' => 'HAHAHA', + 'ban_reason_display' => '1', + 'user_id' => '4', + 'username' => 'ipv6_user', + 'username_clean' => 'ipv6_user', + 'label' => 'ipv6_user', + ], + ], + $foo + ); + } +} diff --git a/tests/ban/fixtures/sessions_banlist.xml b/tests/ban/fixtures/sessions_banlist.xml new file mode 100644 index 0000000000..c6c882fcf9 --- /dev/null +++ b/tests/ban/fixtures/sessions_banlist.xml @@ -0,0 +1,177 @@ + + + + user_id + username + username_clean + user_permissions + user_sig + user_email + user_ip + user_type + + 1 + anonymous + anonymous + + + + 127.0.0.1 + 2 + + + 2 + founder + founder + + + admin@foo.bar + 21.22.23.24 + 3 + + + 3 + normal_user + normal_user + + + normal_user@foo.bar + 21.22.23.25 + 0 + + + 4 + ipv6_user + ipv6_user + + + normal_user@foo.bar + 2345:0425:2CA1:0000:0000:0567:5673:23b5 + 0 + + + 5 + another_user + another_user + + + bar@example.org + 123.124.125.126 + 0 + +
+ + session_id + session_user_id + session_ip + session_browser + session_admin + + bar_session000000000000000000000 + 4 + 127.0.0.1 + user agent + 1 + +
+ + ban_id + ban_userid + ban_mode + ban_item + ban_start + ban_end + ban_reason + ban_reason_display + + 2 + 0 + ip + 127.0.0.1 + 1111 + 0 + HAHAHA + 1 + + + 3 + 0 + ip + 127.1.1.1 + 1111 + 0 + HAHAHA + 1 + + + 4 + 4 + user + 4 + 1111 + 0 + HAHAHA + 1 + + + 5 + 0 + email + bar@example.org + 1111 + 0 + HAHAHA + 1 + + + 6 + 0 + ip + 10.0.0.1/28 + 1111 + 0 + HAHAHA + 1 + + + 7 + 0 + ip + 2001:4860:4860::8888/12 + 1111 + 0 + HAHAHA + 1 + + + 8 + 0 + invalid_mode + foo + 1111 + 0 + HAHAHA + 1 + + + 9 + 0 + email + *@foo.bar + 1111 + 0 + HAHAHA + 1 + + + 10 + 10 + user + 3 + 1111 + 1234 + expired + 1 + +
+
diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 2aa9c6c986..7556dd8c90 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -34,6 +34,8 @@ $phpbb_class_loader_ext = new \phpbb\class_loader('\\', $phpbb_root_path . 'ext/ $phpbb_class_loader_ext->register(); $phpbb_class_loader = new \phpbb\class_loader('phpbb\\', $phpbb_root_path . 'phpbb/', "php"); $phpbb_class_loader->register(); +$phpbb_class_loader_tests = new \phpbb\class_loader('phpbb\\tests\\', $phpbb_root_path . '../tests/', 'php'); +$phpbb_class_loader_tests->register(); require_once 'test_framework/phpbb_test_case_helpers.php'; require_once 'test_framework/phpbb_test_case.php'; diff --git a/tests/dbal/boolean_processor_test.php b/tests/dbal/boolean_processor_test.php index 517c1dcaf8..3352b99c82 100644 --- a/tests/dbal/boolean_processor_test.php +++ b/tests/dbal/boolean_processor_test.php @@ -153,9 +153,9 @@ class phpbb_boolean_processor_test extends phpbb_database_test_case 'LEFT_JOIN' => array( array( 'FROM' => array( - 'phpbb_banlist' => 'b', + 'phpbb_bans' => 'b', ), - 'ON' => 'u.user_id = b.ban_userid', + 'ON' => 'b.ban_item = ' . $db->cast_expr_to_string('u.user_id'), ), ), 'WHERE' => array('AND', @@ -172,6 +172,7 @@ class phpbb_boolean_processor_test extends phpbb_database_test_case array( array('ug.group_id', '=', 1), array('b.ban_id', 'IS_NOT', NULL), + array('b.ban_mode', '=', "'user'"), ), ), ), @@ -290,9 +291,9 @@ class phpbb_boolean_processor_test extends phpbb_database_test_case 'LEFT_JOIN' => array( array( 'FROM' => array( - 'phpbb_banlist' => 'b', + 'phpbb_bans' => 'b', ), - 'ON' => 'u.user_id = b.ban_userid', + 'ON' => 'b.ban_item = ' . $db->cast_expr_to_string('u.user_id'), ), ), 'WHERE' => array('AND', diff --git a/tests/dbal/cross_join_test.php b/tests/dbal/cross_join_test.php index 95bcc0be53..83c4b03a60 100644 --- a/tests/dbal/cross_join_test.php +++ b/tests/dbal/cross_join_test.php @@ -36,9 +36,9 @@ class phpbb_dbal_cross_join_test extends phpbb_database_test_case 'LEFT_JOIN' => array( array( 'FROM' => array( - 'phpbb_banlist' => 'b', + 'phpbb_bans' => 'b', ), - 'ON' => 'u.user_id = b.ban_userid', + 'ON' => 'b.ban_item = ' . $db->cast_expr_to_string('u.user_id'), ), ), 'WHERE' => 'ug.group_id = 1 diff --git a/tests/dbal/fixtures/boolean_processor.xml b/tests/dbal/fixtures/boolean_processor.xml index d31d679f45..9bab747fcf 100644 --- a/tests/dbal/fixtures/boolean_processor.xml +++ b/tests/dbal/fixtures/boolean_processor.xml @@ -1,10 +1,12 @@ - +
ban_id - ban_userid + ban_mode + ban_item 1 + user 2
diff --git a/tests/dbal/fixtures/massmail_crossjoin.xml b/tests/dbal/fixtures/massmail_crossjoin.xml index 1050ba067e..d453d5e9fd 100644 --- a/tests/dbal/fixtures/massmail_crossjoin.xml +++ b/tests/dbal/fixtures/massmail_crossjoin.xml @@ -1,10 +1,12 @@ - +
ban_id - ban_userid + ban_mode + ban_item 1 + user 2
diff --git a/tests/functions/fixtures/banned_users.xml b/tests/functions/fixtures/banned_users.xml index cec3f4e51f..082f210ca3 100644 --- a/tests/functions/fixtures/banned_users.xml +++ b/tests/functions/fixtures/banned_users.xml @@ -1,38 +1,69 @@ - +
ban_userid - ban_exclude + ban_modeban_end - - 1 - 1 - 0 - 2 - 0 + user 0 3 - 0 + user 0 4 - 0 + user 2 5 - 0 - 999999999999999999999 + user + 2147485547 6 - 0 + user 3
+ + user_id + username_clean + user_permissions + user_sig + + 2 + admin + + + + + 3 + user3 + + + + + 4 + user4 + + + + + 5 + user5 + + + + + 6 + user6 + + + +
diff --git a/tests/functions/fixtures/validate_email.xml b/tests/functions/fixtures/validate_email.xml index 985050cedc..4fa9bd03d0 100644 --- a/tests/functions/fixtures/validate_email.xml +++ b/tests/functions/fixtures/validate_email.xml @@ -1,26 +1,29 @@ - +
ban_id - ban_userid - ban_exclude + ban_mode + ban_item + ban_startban_end - ban_email - ban_give_reason + ban_reason + ban_reason_display 1 - 0 - 0 - 0 + email banned@example.com + 0 + 0 + 2 - 0 - 0 - 0 + email banned2@example.com + 0 + 0 + just because just because
diff --git a/tests/functions/phpbb_get_banned_user_ids.php b/tests/functions/phpbb_get_banned_user_ids.php index 0877a3526e..6aaa5f8641 100644 --- a/tests/functions/phpbb_get_banned_user_ids.php +++ b/tests/functions/phpbb_get_banned_user_ids.php @@ -20,6 +20,47 @@ class phpbb_get_banned_user_ids_test extends phpbb_database_test_case return $this->createXMLDataSet(__DIR__ . '/fixtures/banned_users.xml'); } + protected function setUp(): void + { + global $db, $phpbb_container, $phpbb_root_path, $phpEx; + + $db = $this->new_dbal(); + + parent::setUp(); + + $phpbb_container = new phpbb_mock_container_builder(); + $config = new \phpbb\config\config([]); + $phpbb_dispatcher = new phpbb_mock_event_dispatcher(); + $language = new phpbb\language\language(new phpbb\language\language_file_loader($phpbb_root_path, $phpEx)); + $user = new phpbb\user($language, '\phpbb\datetime'); + $user->data['user_email'] = ''; + + $cache = new \phpbb\cache\service( + new \phpbb\cache\driver\dummy(), + $config, + $db, + $phpbb_dispatcher, + $phpbb_root_path, + $phpEx + ); + $cache->get_driver()->purge(); + + $ban_type_email = new \phpbb\ban\type\email($db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $ban_type_user = new \phpbb\ban\type\user($db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $ban_type_ip = new \phpbb\ban\type\ip($db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $phpbb_container->set('ban.type.email', $ban_type_email); + $phpbb_container->set('ban.type.user', $ban_type_user); + $phpbb_container->set('ban.type.ip', $ban_type_ip); + $collection = new \phpbb\di\service_collection($phpbb_container); + $collection->add('ban.type.email'); + $collection->add('ban.type.user'); + $collection->add('ban.type.ip'); + $phpbb_log = new \phpbb\log\dummy(); + + $ban_manager = new \phpbb\ban\manager($collection, $cache->get_driver(), $db, $language, $phpbb_log, $user, 'phpbb_bans', 'phpbb_users'); + $phpbb_container->set('ban.manager', $ban_manager); + } + public function phpbb_get_banned_user_ids_data() { return array( @@ -35,6 +76,11 @@ class phpbb_get_banned_user_ids_test extends phpbb_database_test_case array(array(1, 2, 4, 5, 6), false), array(2 => 2), ), + array( + // True to get users currently banned, but should only return passed user IDs + array(array(5, 6, 7), true), + array(5 => 5), + ), array( // Unix timestamp to get users banned until that time array(array(1, 2, 4, 5, 6), 2), @@ -43,15 +89,6 @@ class phpbb_get_banned_user_ids_test extends phpbb_database_test_case ); } - protected function setUp(): void - { - global $db; - - $db = $this->new_dbal(); - - parent::setUp(); - } - /** * @dataProvider phpbb_get_banned_user_ids_data */ diff --git a/tests/functions/validate_user_email_test.php b/tests/functions/validate_user_email_test.php index fb70c3014a..a43244bd50 100644 --- a/tests/functions/validate_user_email_test.php +++ b/tests/functions/validate_user_email_test.php @@ -28,18 +28,43 @@ class phpbb_functions_validate_user_email_test extends phpbb_database_test_case protected function setUp(): void { - global $cache, $phpbb_dispatcher, $phpbb_root_path, $phpEx; + global $cache, $phpbb_container, $phpbb_dispatcher, $phpbb_root_path, $phpEx; parent::setUp(); - $cache = new \phpbb\cache\driver\file(); - $cache->purge(); + $phpbb_container = new phpbb_mock_container_builder(); + $config = new \phpbb\config\config([]); $this->db = $this->new_dbal(); $phpbb_dispatcher = new phpbb_mock_event_dispatcher(); $language = new phpbb\language\language(new phpbb\language\language_file_loader($phpbb_root_path, $phpEx)); $this->user = new phpbb\user($language, '\phpbb\datetime'); $this->user->data['user_email'] = ''; $this->helper = new phpbb_functions_validate_data_helper($this); + + $cache = new \phpbb\cache\service( + new \phpbb\cache\driver\dummy(), + $config, + $this->db, + $phpbb_dispatcher, + $phpbb_root_path, + $phpEx + ); + $cache->get_driver()->purge(); + + $ban_type_email = new \phpbb\ban\type\email($this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $ban_type_user = new \phpbb\ban\type\user($this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $ban_type_ip = new \phpbb\ban\type\ip($this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $phpbb_container->set('ban.type.email', $ban_type_email); + $phpbb_container->set('ban.type.user', $ban_type_user); + $phpbb_container->set('ban.type.ip', $ban_type_ip); + $collection = new \phpbb\di\service_collection($phpbb_container); + $collection->add('ban.type.email'); + $collection->add('ban.type.user'); + $collection->add('ban.type.ip'); + $phpbb_log = new \phpbb\log\dummy(); + + $ban_manager = new \phpbb\ban\manager($collection, $cache->get_driver(), $this->db, $language, $phpbb_log, $this->user, 'phpbb_bans', 'phpbb_users'); + $phpbb_container->set('ban.manager', $ban_manager); } /** diff --git a/tests/functions_user/delete_user_test.php b/tests/functions_user/delete_user_test.php index 76a0aa50ce..9f4f69d0c2 100644 --- a/tests/functions_user/delete_user_test.php +++ b/tests/functions_user/delete_user_test.php @@ -369,7 +369,7 @@ class phpbb_functions_user_delete_user_test extends phpbb_database_test_case $this->db->sql_freeresult($result); $sql = 'SELECT ban_id - FROM ' . BANLIST_TABLE . ' + FROM ' . BANS_TABLE . ' ORDER BY ban_id ASC'; $result = $this->db->sql_query($sql); $this->assertEquals($expected_ban, $this->db->sql_fetchrowset($result), 'Ban table content is mismatching after deleting a user.'); diff --git a/tests/functions_user/fixtures/delete_user.xml b/tests/functions_user/fixtures/delete_user.xml index 8de2659722..c149a42429 100644 --- a/tests/functions_user/fixtures/delete_user.xml +++ b/tests/functions_user/fixtures/delete_user.xml @@ -36,30 +36,42 @@ - +
ban_idban_userid - ban_email + ban_mode + ban_item + ban_start + ban_endban_reason - ban_give_reason + ban_reason_display 1 2 - + user + 2 + 0 + 0 2 3 - + user + 3 + 0 + 0 3 0 - + user + 0 + 0 + 0 diff --git a/tests/session/check_ban_test.php b/tests/session/check_ban_test.php index 7b0aac060c..44a4012d3b 100644 --- a/tests/session/check_ban_test.php +++ b/tests/session/check_ban_test.php @@ -33,8 +33,6 @@ class phpbb_session_check_ban_test extends phpbb_session_test_case false, false, false, false, /* should be banned? -> */ false), array('Matching values in the database, should be banned', 4, '127.0.0.1', 'bar@example.org', true, /* should be banned? -> */ true), - array('IP Banned, should be banned', - false, '127.1.1.1', false, false, /* should be banned? -> */ true), ); } @@ -53,7 +51,10 @@ class phpbb_session_check_ban_test extends phpbb_session_test_case 'BAN_TRIGGERED_BY_USER' => 'BAN_TRIGGERED_BY_USER', ]; - global $cache, $config, $phpbb_root_path, $phpEx, $phpbb_filesystem; + global $cache, $config, $phpbb_root_path, $phpEx, $phpbb_filesystem, $phpbb_container, $user; + + $language = new phpbb\language\language(new phpbb\language\language_file_loader($phpbb_root_path, $phpEx)); + $user = new \phpbb\user($language, '\phpbb\datetime'); $phpbb_filesystem = new \phpbb\filesystem\filesystem(); @@ -73,6 +74,22 @@ class phpbb_session_check_ban_test extends phpbb_session_test_case $phpbb_root_path, $phpEx ); + + $phpbb_container = new phpbb_mock_container_builder(); + $ban_type_email = new \phpbb\ban\type\email($this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $ban_type_user = new \phpbb\ban\type\user($this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $ban_type_ip = new \phpbb\ban\type\ip($this->db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $phpbb_container->set('ban.type.email', $ban_type_email); + $phpbb_container->set('ban.type.user', $ban_type_user); + $phpbb_container->set('ban.type.ip', $ban_type_ip); + $collection = new \phpbb\di\service_collection($phpbb_container); + $collection->add('ban.type.email'); + $collection->add('ban.type.user'); + $collection->add('ban.type.ip'); + $phpbb_log = new \phpbb\log\dummy(); + + $ban_manager = new \phpbb\ban\manager($collection, $cache->get_driver(), $this->db, $language, $phpbb_log, $user, 'phpbb_bans', 'phpbb_users'); + $phpbb_container->set('ban.manager', $ban_manager); } protected function tearDown(): void diff --git a/tests/session/fixtures/sessions_banlist.xml b/tests/session/fixtures/sessions_banlist.xml index e720e35f0a..b6c123e119 100644 --- a/tests/session/fixtures/sessions_banlist.xml +++ b/tests/session/fixtures/sessions_banlist.xml @@ -26,34 +26,46 @@ 1
- +
ban_id - ban_userid - ban_ip - ban_email + ban_mode + ban_itemban_startban_end - ban_excludeban_reason - ban_give_reason + ban_reason_display 2 - 4 + ip 127.0.0.1 - bar@example.org 1111 0 - 0 HAHAHA 1 3 - 0 + ip 127.1.1.1 - 1111 0 + HAHAHA + 1 + + + 4 + user + 4 + 1111 + 0 + HAHAHA + 1 + + + 5 + email + bar@example.org + 1111 0 HAHAHA 1 diff --git a/tests/session/fixtures/sessions_empty.xml b/tests/session/fixtures/sessions_empty.xml index 068951dc4c..4df982ab7f 100644 --- a/tests/session/fixtures/sessions_empty.xml +++ b/tests/session/fixtures/sessions_empty.xml @@ -30,11 +30,13 @@ session_ip session_browser
- +
ban_id - ban_userid - ban_email + ban_mode + ban_item + ban_start + ban_endban_reason - ban_give_reason + ban_reason_display
diff --git a/tests/session/testable_factory.php b/tests/session/testable_factory.php index bd25fa02f0..36e9fe6d3f 100644 --- a/tests/session/testable_factory.php +++ b/tests/session/testable_factory.php @@ -38,7 +38,7 @@ class phpbb_session_testable_factory public function __construct() { // default configuration values - $this->config_data = array( + $this->config_data = [ 'allow_autologin' => false, 'auth_method' => 'db', 'forwarded_for_check' => true, @@ -53,13 +53,14 @@ class phpbb_session_testable_factory 'limit_search_load' => 0, 'ip_check' => 3, 'browser_check' => 1, - ); + ]; - $this->cache_data = array( - '_bots' => array(), - ); + $this->cache_data = [ + '_bots' => [], + '_ban_info' => [], + ]; - $this->cookies = array(); + $this->cookies = []; $this->server_data = $_SERVER; } @@ -73,7 +74,8 @@ class phpbb_session_testable_factory public function get_session(\phpbb\db\driver\driver_interface $dbal) { // set up all the global variables used by session - global $SID, $_SID, $db, $config, $cache, $request, $phpbb_container, $phpbb_root_path; + global $SID, $_SID, $db, $config, $cache, $request, $phpbb_container, $phpbb_dispatcher; + global $user, $phpbb_root_path, $phpEx; $request = $this->request = new phpbb_mock_request( array(), @@ -88,6 +90,8 @@ class phpbb_session_testable_factory $cache = $this->cache = new phpbb_mock_cache($this->get_cache_data()); $SID = $_SID = null; + $language = new phpbb\language\language(new phpbb\language\language_file_loader($phpbb_root_path, $phpEx)); + $user = new \phpbb\user($language, '\phpbb\datetime'); $phpbb_container = $this->container = new phpbb_mock_container_builder(); $phpbb_container->set( @@ -103,6 +107,24 @@ class phpbb_session_testable_factory $provider_collection ); + $phpbb_dispatcher = new phpbb_mock_event_dispatcher(); + + $ban_type_email = new \phpbb\ban\type\email($db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $ban_type_user = new \phpbb\ban\type\user($db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $ban_type_ip = new \phpbb\ban\type\ip($db, 'phpbb_bans', 'phpbb_users', 'phpbb_sessions', 'phpbb_sessions_keys'); + $phpbb_container->set('ban.type.email', $ban_type_email); + $phpbb_container->set('ban.type.user', $ban_type_user); + $phpbb_container->set('ban.type.ip', $ban_type_ip); + + $collection = new \phpbb\di\service_collection($phpbb_container); + $collection->add('ban.type.email'); + $collection->add('ban.type.user'); + $collection->add('ban.type.ip'); + $phpbb_log = new \phpbb\log\dummy(); + + $ban_manager = new \phpbb\ban\manager($collection, $cache, $db, $language, $phpbb_log, $user,'phpbb_bans', 'phpbb_users'); + $phpbb_container->set('ban.manager', $ban_manager); + $session = new phpbb_mock_session_testable; return $session; } diff --git a/tests/test_framework/phpbb_session_test_case.php b/tests/test_framework/phpbb_session_test_case.php index e3904f1746..79ac2d9c48 100644 --- a/tests/test_framework/phpbb_session_test_case.php +++ b/tests/test_framework/phpbb_session_test_case.php @@ -29,7 +29,7 @@ abstract class phpbb_session_test_case extends phpbb_database_test_case { parent::setUp(); - global $symfony_request, $phpbb_path_helper, $request, $phpbb_root_path, $phpEx; + global $symfony_request, $phpbb_path_helper, $phpbb_root_path, $phpEx; $symfony_request = new \phpbb\symfony_request( new phpbb_mock_request() );