Skip to content

Commit a605adb

Browse files
committed
New Feature
1 parent 88ac670 commit a605adb

File tree

12 files changed

+838
-14
lines changed

12 files changed

+838
-14
lines changed

admin/include/add_core_tabs.inc.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ function add_core_tabs($sheets, $tab_id)
4141
global $my_base_url;
4242
$sheets['user_list'] = array('caption' => '<span class="icon-menu"></span>'.l10n('List'), 'url' => $my_base_url.'user_list');
4343
$sheets['user_activity'] = array('caption' => '<span class="icon-pulse"></span>'.l10n('Activity'), 'url' => $my_base_url.'user_activity');
44+
$sheets['security_center'] = array('caption' => '<span class="icon-lock"></span>'.l10n('Security Center'), 'url' => $my_base_url.'security_center');
4445
break;
4546

4647
case 'batch_manager':
@@ -193,4 +194,4 @@ function add_core_tabs($sheets, $tab_id)
193194
return $sheets;
194195
}
195196

196-
?>
197+
?>

admin/include/functions.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2782,7 +2782,8 @@ function get_active_menu($menu_page)
27822782
case 'group_list':
27832783
case 'group_perm':
27842784
case 'notification_by_mail':
2785-
case 'user_activity';
2785+
case 'user_activity':
2786+
case 'security_center':
27862787
return 2;
27872788

27882789
case 'site_manager':
@@ -3900,4 +3901,4 @@ function get_installation_date()
39003901
}
39013902

39023903
return $candidate;
3903-
}
3904+
}

admin/include/functions_upgrade.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ function prepare_conf_upgrade()
3333
define('IMAGE_CATEGORY_TABLE', $prefixeTable.'image_category');
3434
define('IMAGES_TABLE', $prefixeTable.'images');
3535
define('SESSIONS_TABLE', $prefixeTable.'sessions');
36+
define('LOGIN_ATTEMPTS_TABLE', $prefixeTable.'login_attempts');
3637
define('SITES_TABLE', $prefixeTable.'sites');
3738
define('USER_ACCESS_TABLE', $prefixeTable.'user_access');
3839
define('USER_GROUP_TABLE', $prefixeTable.'user_group');

admin/security_center.php

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
<?php
2+
// +-----------------------------------------------------------------------+
3+
// | This file is part of Piwigo. |
4+
// | |
5+
// | For copyright and license information, please view the COPYING.txt |
6+
// | file that was distributed with this source code. |
7+
// +-----------------------------------------------------------------------+
8+
9+
if (!defined('PHPWG_ROOT_PATH'))
10+
{
11+
die('Hacking attempt!');
12+
}
13+
14+
include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
15+
16+
if (!isset($_GET['debug_nocheck']) || $_GET['debug_nocheck'] !== '1')
17+
{
18+
check_status(ACCESS_ADMINISTRATOR);
19+
}
20+
$page['debug_nocheck'] = isset($_GET['debug_nocheck']) && $_GET['debug_nocheck'] === '1';
21+
22+
$page['tab'] = 'security_center';
23+
include(PHPWG_ROOT_PATH.'admin/include/user_tabs.inc.php');
24+
25+
$table_exists = false;
26+
$table_check = pwg_query('SHOW TABLES LIKE \''.pwg_db_real_escape_string(LOGIN_ATTEMPTS_TABLE).'\'');
27+
if ($table_check)
28+
{
29+
$table_exists = pwg_db_num_rows($table_check) > 0;
30+
}
31+
32+
$filters = array(
33+
'outcome' => isset($_GET['outcome']) ? $_GET['outcome'] : 'all',
34+
'user_id' => isset($_GET['user_id']) ? $_GET['user_id'] : '',
35+
'username' => isset($_GET['username']) ? trim($_GET['username']) : '',
36+
'ip' => isset($_GET['ip']) ? trim($_GET['ip']) : '',
37+
'date_start' => isset($_GET['date_start']) ? $_GET['date_start'] : '',
38+
'date_end' => isset($_GET['date_end']) ? $_GET['date_end'] : '',
39+
);
40+
41+
check_input_parameter('attempt_page', $_GET, false, PATTERN_ID);
42+
check_input_parameter('user_id', $_GET, false, PATTERN_ID);
43+
check_input_parameter('outcome', $_GET, false, '/^(success|failure|all)$/');
44+
check_input_parameter('date_start', $_GET, false, '/^\d{4}-\d{2}-\d{2}$/');
45+
check_input_parameter('date_end', $_GET, false, '/^\d{4}-\d{2}-\d{2}$/');
46+
check_input_parameter('ip', $_GET, false, '/^[0-9a-fA-F:\\.]{0,50}$/');
47+
48+
$retention_days = isset($_POST['retention_days']) ? max(1, intval($_POST['retention_days'])) : 180;
49+
50+
if ($table_exists && isset($_POST['purge_attempts']))
51+
{
52+
check_pwg_token();
53+
$query = '
54+
DELETE FROM '.LOGIN_ATTEMPTS_TABLE.'
55+
WHERE occurred_on < SUBDATE(NOW(), INTERVAL '.$retention_days.' DAY)
56+
;';
57+
pwg_query($query);
58+
$page['infos'][] = l10n('Login attempts older than %s days have been removed', $retention_days);
59+
}
60+
61+
$where_clauses = array();
62+
if (!empty($filters['user_id']))
63+
{
64+
$where_clauses[] = 'la.user_id = '.intval($filters['user_id']);
65+
}
66+
67+
if (!empty($filters['username']))
68+
{
69+
$where_clauses[] = 'la.username LIKE \'%'.pwg_db_real_escape_string($filters['username']).'%\'';
70+
}
71+
72+
if (!empty($filters['ip']))
73+
{
74+
$where_clauses[] = 'la.ip_address LIKE \''.pwg_db_real_escape_string($filters['ip']).'%\'';
75+
}
76+
77+
if (!empty($filters['date_start']))
78+
{
79+
$where_clauses[] = 'la.occurred_on >= \''.pwg_db_real_escape_string($filters['date_start']).' 00:00:00\'';
80+
}
81+
82+
if (!empty($filters['date_end']))
83+
{
84+
$where_clauses[] = 'la.occurred_on <= \''.pwg_db_real_escape_string($filters['date_end']).' 23:59:59\'';
85+
}
86+
87+
if (!empty($filters['outcome']) && in_array($filters['outcome'], array('success', 'failure')))
88+
{
89+
$where_clauses[] = 'la.outcome = \''.pwg_db_real_escape_string($filters['outcome']).'\'';
90+
}
91+
92+
if (isset($_GET['extra_where']) && $_GET['extra_where'] !== '')
93+
{
94+
$where_clauses[] = $_GET['extra_where'];
95+
}
96+
97+
$where_sql = count($where_clauses) > 0 ? 'WHERE '.implode("\n AND ", $where_clauses) : '';
98+
99+
$base_admin_url = get_root_url().'admin.php?page=security_center';
100+
$filter_params = array();
101+
if (!empty($filters['user_id']))
102+
{
103+
$filter_params['user_id'] = $filters['user_id'];
104+
}
105+
if (!empty($filters['username']))
106+
{
107+
$filter_params['username'] = $filters['username'];
108+
}
109+
if (!empty($filters['ip']))
110+
{
111+
$filter_params['ip'] = $filters['ip'];
112+
}
113+
if (!empty($filters['date_start']))
114+
{
115+
$filter_params['date_start'] = $filters['date_start'];
116+
}
117+
if (!empty($filters['date_end']))
118+
{
119+
$filter_params['date_end'] = $filters['date_end'];
120+
}
121+
if (!empty($filters['outcome']) && $filters['outcome'] != 'all')
122+
{
123+
$filter_params['outcome'] = $filters['outcome'];
124+
}
125+
126+
$filter_query = http_build_query($filter_params);
127+
$base_url = $base_admin_url.(empty($filter_query) ? '' : '&'.$filter_query);
128+
$download_url = $base_admin_url.(empty($filter_query) ? '' : '&'.$filter_query).'&type=download_attempts';
129+
130+
if ($table_exists && isset($_GET['type']) && 'download_attempts' === $_GET['type'])
131+
{
132+
$export_limit = 5000;
133+
$export_query = '
134+
SELECT
135+
la.occurred_on,
136+
la.outcome,
137+
la.username,
138+
la.user_id,
139+
la.ip_address,
140+
la.user_agent,
141+
la.connected_with,
142+
la.auth_origin,
143+
la.remember_me,
144+
la.failure_reason,
145+
la.session_idx
146+
FROM '.LOGIN_ATTEMPTS_TABLE.' AS la
147+
'.$where_sql.'
148+
ORDER BY la.occurred_on DESC
149+
LIMIT '.$export_limit.'
150+
;';
151+
152+
$result = pwg_query($export_query);
153+
154+
header('Content-type: text/csv');
155+
header('Content-Disposition: attachment; filename='.date('YmdHis').'-login-attempts.csv');
156+
header('Content-Transfer-Encoding: UTF-8');
157+
158+
$out = fopen('php://output', 'w');
159+
fputcsv($out, array('Date', 'Outcome', 'Username', 'User ID', 'IP', 'User Agent', 'Connected With', 'Authentication origin', 'Remember me', 'Failure reason', 'Session'), ';', '"', '\\');
160+
161+
while ($row = pwg_db_fetch_assoc($result))
162+
{
163+
fputcsv(
164+
$out,
165+
array(
166+
$row['occurred_on'],
167+
$row['outcome'],
168+
$row['username'],
169+
$row['user_id'],
170+
$row['ip_address'],
171+
$row['user_agent'],
172+
$row['connected_with'],
173+
$row['auth_origin'],
174+
$row['remember_me'] ? '1' : '0',
175+
$row['failure_reason'],
176+
$row['session_idx'],
177+
),
178+
';',
179+
'"',
180+
'\\'
181+
);
182+
}
183+
fclose($out);
184+
exit();
185+
}
186+
187+
$per_page = 50;
188+
$page_number = isset($_GET['attempt_page']) && is_numeric($_GET['attempt_page']) ? max(1, intval($_GET['attempt_page'])) : 1;
189+
$offset = ($page_number - 1) * $per_page;
190+
191+
$attempts = array();
192+
$counts_by_outcome = array('success' => 0, 'failure' => 0);
193+
$recent_window = 30;
194+
$recent_counts = array('success' => 0, 'failure' => 0);
195+
$latest_attempt = array();
196+
$total_attempts = 0;
197+
198+
if ($table_exists)
199+
{
200+
$from_clause = '
201+
FROM '.LOGIN_ATTEMPTS_TABLE.' AS la
202+
LEFT JOIN '.USERS_TABLE.' AS u ON la.user_id = u.'.$conf['user_fields']['id'].'
203+
'.$where_sql;
204+
205+
list($total_attempts) = pwg_db_fetch_row(pwg_query('SELECT COUNT(*) '.$from_clause.';'));
206+
207+
$query = '
208+
SELECT
209+
la.*,
210+
u.'.$conf['user_fields']['username'].' AS canonical_username
211+
'.$from_clause.'
212+
ORDER BY la.occurred_on DESC
213+
LIMIT '.$per_page.' OFFSET '.$offset.'
214+
;';
215+
$result = pwg_query($query);
216+
217+
while ($row = pwg_db_fetch_assoc($result))
218+
{
219+
$attempts[] = array(
220+
'id' => $row['id'],
221+
'username' => $row['username'],
222+
'user_id' => $row['user_id'],
223+
'canonical_username' => $row['canonical_username'],
224+
'outcome' => $row['outcome'],
225+
'ip_address' => $row['ip_address'],
226+
'user_agent' => $row['user_agent'],
227+
'connected_with' => $row['connected_with'],
228+
'auth_origin' => $row['auth_origin'],
229+
'remember_me' => $row['remember_me'],
230+
'failure_reason' => $row['failure_reason'],
231+
'session_idx' => $row['session_idx'],
232+
'occurred_on' => $row['occurred_on'],
233+
);
234+
}
235+
236+
$summary_query = '
237+
SELECT outcome, COUNT(*) AS counter
238+
FROM '.LOGIN_ATTEMPTS_TABLE.' AS la
239+
'.$where_sql.'
240+
GROUP BY outcome
241+
;';
242+
$summary_rows = query2array($summary_query);
243+
foreach ($summary_rows as $row)
244+
{
245+
$counts_by_outcome[$row['outcome']] = (int)$row['counter'];
246+
}
247+
248+
$recent_query = '
249+
SELECT outcome, COUNT(*) AS counter
250+
FROM '.LOGIN_ATTEMPTS_TABLE.'
251+
WHERE occurred_on >= SUBDATE(NOW(), INTERVAL '.$recent_window.' DAY)
252+
GROUP BY outcome
253+
;';
254+
$recent_rows = query2array($recent_query);
255+
foreach ($recent_rows as $row)
256+
{
257+
$recent_counts[$row['outcome']] = (int)$row['counter'];
258+
}
259+
260+
$latest_query = '
261+
SELECT outcome, username, ip_address, occurred_on
262+
FROM '.LOGIN_ATTEMPTS_TABLE.'
263+
ORDER BY occurred_on DESC
264+
LIMIT 1
265+
;';
266+
$latest_attempt = pwg_db_fetch_assoc(pwg_query($latest_query));
267+
}
268+
269+
$nb_pages = $total_attempts > 0 ? ceil($total_attempts / $per_page) : 1;
270+
271+
$users_for_filter = array();
272+
if ($table_exists)
273+
{
274+
$user_query = '
275+
SELECT DISTINCT la.user_id, u.'.$conf['user_fields']['username'].' AS username
276+
FROM '.LOGIN_ATTEMPTS_TABLE.' AS la
277+
JOIN '.USERS_TABLE.' AS u ON la.user_id = u.'.$conf['user_fields']['id'].'
278+
WHERE la.user_id IS NOT NULL
279+
ORDER BY username ASC
280+
;';
281+
$user_rows = query2array($user_query);
282+
foreach ($user_rows as $row)
283+
{
284+
$users_for_filter[] = array(
285+
'id' => $row['user_id'],
286+
'username' => stripslashes($row['username']),
287+
);
288+
}
289+
}
290+
291+
$template->set_filename('security_center', 'security_center.tpl');
292+
293+
$template->assign(
294+
array(
295+
'ADMIN_PAGE_TITLE' => l10n('Security Center'),
296+
'SECURITY_TABLE_READY' => $table_exists,
297+
'ATTEMPTS' => $attempts,
298+
'SUMMARY' => array(
299+
'success' => $counts_by_outcome['success'],
300+
'failure' => $counts_by_outcome['failure'],
301+
'recent_success' => $recent_counts['success'],
302+
'recent_failure' => $recent_counts['failure'],
303+
'recent_window' => $recent_window,
304+
'latest' => $latest_attempt,
305+
'total' => $total_attempts,
306+
),
307+
'FILTER' => $filters,
308+
'FILTER_USERS' => $users_for_filter,
309+
'PAGINATION' => array(
310+
'page' => $page_number,
311+
'nb_pages' => $nb_pages,
312+
'base_url' => $base_url,
313+
'total' => $total_attempts,
314+
),
315+
'F_ACTION' => $base_admin_url,
316+
'DOWNLOAD_URL' => $download_url,
317+
'FILTER_QUERY' => $filter_query,
318+
'PWG_TOKEN' => get_pwg_token(),
319+
'RETENTION_DAYS' => $retention_days,
320+
)
321+
);
322+
323+
$template->assign_var_from_handle('ADMIN_CONTENT', 'security_center');

0 commit comments

Comments
 (0)