Issues (1270)

classes/pref/users.php (10 issues)

1
<?php
2
class Pref_Users extends Handler_Protected {
3
        public function before($method) {
4
            if (parent::before($method)) {
5
                if ($_SESSION["access_level"] < 10) {
6
                    print __("Your access level is insufficient to open this tab.");
7
                    return false;
8
                }
9
                return true;
10
            }
11
            return false;
12
        }
13
14
        public function csrf_ignore($method) {
15
            $csrf_ignored = array("index", "edit", "userdetails");
16
17
            return array_search($method, $csrf_ignored) !== false;
18
        }
19
20
        public function edit() {
21
            global $access_level_names;
22
23
            print "<form id='user_edit_form' onsubmit='return false' dojoType='dijit.form.Form'>";
24
25
            print '<div dojoType="dijit.layout.TabContainer" style="height : 400px">
26
        		<div dojoType="dijit.layout.ContentPane" title="'.__('Edit user').'">';
27
28
            //print "<form id=\"user_edit_form\" onsubmit='return false' dojoType=\"dijit.form.Form\">";
29
30
            $id = (int) clean($_REQUEST["id"]);
31
32
            print_hidden("id", "$id");
33
            print_hidden("op", "pref-users");
34
            print_hidden("method", "editSave");
35
36
            $sth = $this->pdo->prepare("SELECT * FROM ttrss_users WHERE id = ?");
37
            $sth->execute([$id]);
38
39
            if ($row = $sth->fetch()) {
40
41
                $login = $row["login"];
42
                $access_level = $row["access_level"];
43
                $email = $row["email"];
44
45
                $sel_disabled = ($id == $_SESSION["uid"] || $login == "admin") ? "disabled" : "";
46
47
                print "<header>".__("User")."</header>";
48
                print "<section>";
49
50
                if ($sel_disabled) {
51
                    print_hidden("login", "$login");
52
                }
53
54
                print "<fieldset>";
55
                print "<label>".__("Login:")."</label>";
56
                print "<input style='font-size : 16px'
57
					dojoType='dijit.form.ValidationTextBox' required='1'
58
					$sel_disabled name='login' value=\"$login\">";
59
                print "</fieldset>";
60
61
                print "</section>";
62
63
                print "<header>".__("Authentication")."</header>";
64
                print "<section>";
65
66
                print "<fieldset>";
67
68
                print "<label>".__('Access level: ')."</label> ";
69
70
                if (!$sel_disabled) {
71
                    print_select_hash("access_level", $access_level, $access_level_names,
72
                        "dojoType=\"fox.form.Select\" $sel_disabled");
73
                } else {
74
                    print_select_hash("", $access_level, $access_level_names,
75
                        "dojoType=\"fox.form.Select\" $sel_disabled");
76
                    print_hidden("access_level", "$access_level");
77
                }
78
79
                print "</fieldset>";
80
                print "<fieldset>";
81
82
                print "<label>".__("New password:")."</label> ";
83
                print "<input dojoType='dijit.form.TextBox' type='password' size='20' placeholder='Change password'
84
					name='password'>";
85
86
                print "</fieldset>";
87
88
                print "</section>";
89
90
                print "<header>".__("Options")."</header>";
91
                print "<section>";
92
93
                print "<fieldset>";
94
                print "<label>".__("E-mail:")."</label> ";
95
                print "<input dojoType='dijit.form.TextBox' size='30' name='email'
96
					value=\"$email\">";
97
                print "</fieldset>";
98
99
                print "</section>";
100
101
                print "</table>";
102
103
            }
104
105
            print '</div>'; #tab
106
            print "<div href=\"backend.php?op=pref-users&method=userdetails&id=$id\"
107
				dojoType=\"dijit.layout.ContentPane\" title=\"".__('User details')."\">";
108
109
            print '</div>';
110
            print '</div>';
111
112
            print "<footer>
113
				<button dojoType='dijit.form.Button' class='alt-primary' type='submit' onclick=\"dijit.byId('userEditDlg').execute()\">".
114
                __('Save')."</button>
115
				<button dojoType='dijit.form.Button' onclick=\"dijit.byId('userEditDlg').hide()\">".
116
                __('Cancel')."</button>
117
				</footer>";
118
119
            print "</form>";
120
121
            return;
122
        }
123
124
        public function userdetails() {
125
            $id = (int) clean($_REQUEST["id"]);
126
127
            $sth = $this->pdo->prepare("SELECT login,
128
				".SUBSTRING_FOR_DATE."(last_login,1,16) AS last_login,
129
				access_level,
130
				(SELECT COUNT(int_id) FROM ttrss_user_entries
131
					WHERE owner_uid = id) AS stored_articles,
132
				".SUBSTRING_FOR_DATE."(created,1,16) AS created
133
				FROM ttrss_users
134
				WHERE id = ?");
135
            $sth->execute([$id]);
136
137
            if ($row = $sth->fetch()) {
138
                print "<table width='100%'>";
139
140
                $last_login = make_local_datetime(
141
                    $row["last_login"], true);
142
143
                $created = make_local_datetime(
144
                    $row["created"], true);
145
146
                $stored_articles = $row["stored_articles"];
147
148
                print "<tr><td>".__('Registered')."</td><td>$created</td></tr>";
149
                print "<tr><td>".__('Last logged in')."</td><td>$last_login</td></tr>";
150
151
                $sth = $this->pdo->prepare("SELECT COUNT(id) as num_feeds FROM ttrss_feeds
152
					WHERE owner_uid = ?");
153
                $sth->execute([$id]);
154
                $row = $sth->fetch();
155
                $num_feeds = $row["num_feeds"];
156
157
                print "<tr><td>".__('Subscribed feeds count')."</td><td>$num_feeds</td></tr>";
158
                print "<tr><td>".__('Stored articles')."</td><td>$stored_articles</td></tr>";
159
160
                print "</table>";
161
162
                print "<h1>".__('Subscribed feeds')."</h1>";
163
164
                $sth = $this->pdo->prepare("SELECT id,title,site_url FROM ttrss_feeds
165
					WHERE owner_uid = ? ORDER BY title");
166
                $sth->execute([$id]);
167
168
                print "<ul class=\"panel panel-scrollable list list-unstyled\">";
169
170
                while ($line = $sth->fetch()) {
171
172
                    $icon_file = ICONS_URL."/".$line["id"].".ico";
0 ignored issues
show
The constant ICONS_URL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
173
174
                    if (file_exists($icon_file) && filesize($icon_file) > 0) {
175
                        $feed_icon = "<img class=\"icon\" src=\"$icon_file\">";
176
                    } else {
177
                        $feed_icon = "<img class=\"icon\" src=\"images/blank_icon.gif\">";
178
                    }
179
180
                    print "<li>$feed_icon&nbsp;<a href=\"".$line["site_url"]."\">".$line["title"]."</a></li>";
181
182
                }
183
184
                print "</ul>";
185
186
187
            } else {
188
                print "<h1>".__('User not found')."</h1>";
189
            }
190
191
        }
192
193
        public function editSave() {
194
            $login = trim(clean($_REQUEST["login"]));
0 ignored issues
show
It seems like clean($_REQUEST['login']) can also be of type array; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

194
            $login = trim(/** @scrutinizer ignore-type */ clean($_REQUEST["login"]));
Loading history...
195
            $uid = clean($_REQUEST["id"]);
196
            $access_level = (int) clean($_REQUEST["access_level"]);
197
            $email = trim(clean($_REQUEST["email"]));
198
            $password = clean($_REQUEST["password"]);
199
200
            if ($password) {
201
                $salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
202
                $pwd_hash = encrypt_password($password, $salt, true);
203
                $pass_query_part = "pwd_hash = ".$this->pdo->quote($pwd_hash).",
204
					salt = ".$this->pdo->quote($salt).",";
205
            } else {
206
                $pass_query_part = "";
207
            }
208
209
            $sth = $this->pdo->prepare("UPDATE ttrss_users SET $pass_query_part login = ?,
210
				access_level = ?, email = ?, otp_enabled = false WHERE id = ?");
211
            $sth->execute([$login, $access_level, $email, $uid]);
212
213
        }
214
215
        public function remove() {
216
            $ids = explode(",", clean($_REQUEST["ids"]));
0 ignored issues
show
It seems like clean($_REQUEST['ids']) can also be of type array; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

216
            $ids = explode(",", /** @scrutinizer ignore-type */ clean($_REQUEST["ids"]));
Loading history...
217
218
            foreach ($ids as $id) {
219
                if ($id != $_SESSION["uid"] && $id != 1) {
220
                    $sth = $this->pdo->prepare("DELETE FROM ttrss_tags WHERE owner_uid = ?");
221
                    $sth->execute([$id]);
222
223
                    $sth = $this->pdo->prepare("DELETE FROM ttrss_feeds WHERE owner_uid = ?");
224
                    $sth->execute([$id]);
225
226
                    $sth = $this->pdo->prepare("DELETE FROM ttrss_users WHERE id = ?");
227
                    $sth->execute([$id]);
228
                }
229
            }
230
        }
231
232
        public function add() {
233
            $login = trim(clean($_REQUEST["login"]));
0 ignored issues
show
It seems like clean($_REQUEST['login']) can also be of type array; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

233
            $login = trim(/** @scrutinizer ignore-type */ clean($_REQUEST["login"]));
Loading history...
234
            $tmp_user_pwd = make_password();
235
            $salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
236
            $pwd_hash = encrypt_password($tmp_user_pwd, $salt, true);
237
238
            if (!$login) {
239
                return;
240
            }
241
            // no blank usernames
242
243
            $sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE
244
				login = ?");
245
            $sth->execute([$login]);
246
247
            if (!$sth->fetch()) {
248
249
                $sth = $this->pdo->prepare("INSERT INTO ttrss_users
250
					(login,pwd_hash,access_level,last_login,created, salt)
251
					VALUES (?, ?, 0, null, NOW(), ?)");
252
                $sth->execute([$login, $pwd_hash, $salt]);
253
254
                $sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE
255
					login = ? AND pwd_hash = ?");
256
                $sth->execute([$login, $pwd_hash]);
257
258
                if ($row = $sth->fetch()) {
259
260
                    $new_uid = $row['id'];
261
262
                    print T_sprintf("Added user %s with password %s",
263
                        $login, $tmp_user_pwd);
264
265
                    initialize_user($new_uid);
266
267
                } else {
268
269
                    print T_sprintf("Could not create user %s", $login);
270
271
                }
272
            } else {
273
                print T_sprintf("User %s already exists.", $login);
274
            }
275
        }
276
277
        public static function resetUserPassword($uid, $format_output = false) {
278
279
            $pdo = Db::pdo();
280
281
            $sth = $pdo->prepare("SELECT login FROM ttrss_users WHERE id = ?");
282
            $sth->execute([$uid]);
283
284
            if ($row = $sth->fetch()) {
285
286
                $login = $row["login"];
287
288
                $new_salt = substr(bin2hex(get_random_bytes(125)), 0, 250);
289
                $tmp_user_pwd = make_password();
290
291
                $pwd_hash = encrypt_password($tmp_user_pwd, $new_salt, true);
292
293
                $sth = $pdo->prepare("UPDATE ttrss_users
294
					  SET pwd_hash = ?, salt = ?, otp_enabled = false
295
					WHERE id = ?");
296
                $sth->execute([$pwd_hash, $new_salt, $uid]);
297
298
                $message = T_sprintf("Changed password of user %s to %s", "<strong>$login</strong>", "<strong>$tmp_user_pwd</strong>");
299
300
                if ($format_output) {
301
                                    print_notice($message);
0 ignored issues
show
The call to print_notice() has too many arguments starting with $message. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

301
                                    /** @scrutinizer ignore-call */ 
302
                                    print_notice($message);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Deprecated Code introduced by
The function print_notice() has been deprecated: Use twig function noticeMessage ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

301
                                    /** @scrutinizer ignore-deprecated */ print_notice($message);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
302
                } else {
303
                                    print $message;
304
                }
305
306
            }
307
        }
308
309
        public function resetPass() {
310
            $uid = clean($_REQUEST["id"]);
311
            Pref_Users::resetUserPassword($uid);
312
        }
313
314
        public function index() {
315
316
            global $access_level_names;
317
318
            print "<div dojoType='dijit.layout.BorderContainer' gutters='false'>";
319
            print "<div style='padding : 0px' dojoType='dijit.layout.ContentPane' region='top'>";
320
            print "<div dojoType='fox.Toolbar'>";
321
322
            $user_search = trim(clean($_REQUEST["search"]));
0 ignored issues
show
It seems like clean($_REQUEST['search']) can also be of type array; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

322
            $user_search = trim(/** @scrutinizer ignore-type */ clean($_REQUEST["search"]));
Loading history...
323
324
            if (array_key_exists("search", $_REQUEST)) {
325
                $_SESSION["prefs_user_search"] = $user_search;
326
            } else {
327
                $user_search = $_SESSION["prefs_user_search"];
328
            }
329
330
            print "<div style='float : right; padding-right : 4px;'>
331
				<input dojoType='dijit.form.TextBox' id='user_search' size='20' type='search'
332
					value=\"$user_search\">
333
				<button dojoType='dijit.form.Button' onclick='Users.reload()'>".
334
                    __('Search')."</button>
335
				</div>";
336
337
            $sort = clean($_REQUEST["sort"]);
338
339
            if (!$sort || $sort == "undefined") {
340
                $sort = "login";
341
            }
342
343
            print "<div dojoType='fox.form.DropDownButton'>".
344
                    "<span>".__('Select')."</span>";
345
            print "<div dojoType='dijit.Menu' style='display: none'>";
346
            print "<div onclick=\"Tables.select('prefUserList', true)\"
347
				dojoType='dijit.MenuItem'>".__('All')."</div>";
348
            print "<div onclick=\"Tables.select('prefUserList', false)\"
349
				dojoType='dijit.MenuItem'>".__('None')."</div>";
350
            print "</div></div>";
351
352
            print "<button dojoType='dijit.form.Button' onclick='Users.add()'>".__('Create user')."</button>";
353
354
            print "
355
				<button dojoType='dijit.form.Button' onclick='Users.editSelected()'>".
356
                __('Edit')."</button dojoType=\"dijit.form.Button\">
357
				<button dojoType='dijit.form.Button' onclick='Users.removeSelected()'>".
358
                __('Remove')."</button dojoType=\"dijit.form.Button\">
359
				<button dojoType='dijit.form.Button' onclick='Users.resetSelected()'>".
360
                __('Reset password')."</button dojoType=\"dijit.form.Button\">";
361
362
            PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION,
363
                "hook_prefs_tab_section", "prefUsersToolbar");
364
365
            print "</div>"; #toolbar
366
            print "</div>"; #pane
367
            print "<div style='padding : 0px' dojoType='dijit.layout.ContentPane' region='center'>";
368
369
            $sort = $this->validate_field($sort,
370
                ["login", "access_level", "created", "num_feeds", "created", "last_login"], "login");
371
372
            if ($sort != "login") {
373
                $sort = "$sort DESC";
374
            }
375
376
            $sth = $this->pdo->prepare("SELECT
377
					tu.id,
378
					login,access_level,email,
379
					".SUBSTRING_FOR_DATE."(last_login,1,16) as last_login,
380
					".SUBSTRING_FOR_DATE."(created,1,16) as created,
381
					(SELECT COUNT(id) FROM ttrss_feeds WHERE owner_uid = tu.id) AS num_feeds
382
				FROM
383
					ttrss_users tu
384
				WHERE
385
					(:search = '' OR login LIKE :search) AND tu.id > 0
386
				ORDER BY $sort");
387
            $sth->execute([":search" => $user_search ? "%$user_search%" : ""]);
388
389
            print "<p><table width='100%' cellspacing='0' class='prefUserList' id='prefUserList'>";
390
391
            print "<tr class='title'>
392
						<td align='center' width='5%'>&nbsp;</td>
393
						<td width='20%'><a href='#' onclick=\"Users.reload('login')\">".__('Login')."</a></td>
394
						<td width='20%'><a href='#' onclick=\"Users.reload('access_level')\">".__('Access Level')."</a></td>
395
						<td width='10%'><a href='#' onclick=\"Users.reload('num_feeds')\">".__('Subscribed feeds')."</a></td>
396
						<td width='20%'><a href='#' onclick=\"Users.reload('created')\">".__('Registered')."</a></td>
397
						<td width='20%'><a href='#' onclick=\"Users.reload('last_login')\">".__('Last login')."</a></td></tr>";
398
399
            $lnum = 0;
400
401
            while ($line = $sth->fetch()) {
402
403
                $uid = $line["id"];
404
405
                print "<tr data-row-id='$uid' onclick='Users.edit($uid)'>";
406
407
                $line["login"] = htmlspecialchars($line["login"]);
408
                $line["created"] = make_local_datetime($line["created"], false);
409
                $line["last_login"] = make_local_datetime($line["last_login"], false);
410
411
                print "<td align='center'><input onclick='Tables.onRowChecked(this); event.stopPropagation();'
412
					dojoType='dijit.form.CheckBox' type='checkbox'></td>";
413
414
                print "<td title='".__('Click to edit')."'><i class='material-icons'>person</i> ".$line["login"]."</td>";
415
416
                print "<td>".$access_level_names[$line["access_level"]]."</td>";
417
                print "<td>".$line["num_feeds"]."</td>";
418
                print "<td>".$line["created"]."</td>";
419
                print "<td>".$line["last_login"]."</td>";
420
421
                print "</tr>";
422
423
                ++$lnum;
424
            }
425
426
            print "</table>";
427
428
            if ($lnum == 0) {
429
                if (!$user_search) {
430
                    print_warning(__('No users defined.'));
0 ignored issues
show
Deprecated Code introduced by
The function print_warning() has been deprecated: Use twig function warningMessage ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

430
                    /** @scrutinizer ignore-deprecated */ print_warning(__('No users defined.'));

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
The call to print_warning() has too many arguments starting with __('No users defined.'). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

430
                    /** @scrutinizer ignore-call */ 
431
                    print_warning(__('No users defined.'));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
431
                } else {
432
                    print_warning(__('No matching users found.'));
0 ignored issues
show
Deprecated Code introduced by
The function print_warning() has been deprecated: Use twig function warningMessage ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

432
                    /** @scrutinizer ignore-deprecated */ print_warning(__('No matching users found.'));

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
433
                }
434
            }
435
436
            print "</div>"; #pane
437
438
            PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB,
439
                "hook_prefs_tab", "prefUsers");
440
441
            print "</div>"; #container
442
443
        }
444
445
        public function validate_field($string, $allowed, $default = "") {
446
            if (in_array($string, $allowed)) {
447
                            return $string;
448
            } else {
449
                            return $default;
450
            }
451
        }
452
453
}
454