1
|
|
|
<?php |
2
|
|
|
class Auth_Internal extends Plugin implements IAuthModule { |
3
|
|
|
|
4
|
|
|
private $host; |
5
|
|
|
|
6
|
|
|
public function about() { |
7
|
|
|
return array(1.0, |
8
|
|
|
"Authenticates against internal tt-rss database", |
9
|
|
|
"fox", |
10
|
|
|
true); |
11
|
|
|
} |
12
|
|
|
|
13
|
|
|
/* @var PluginHost $host */ |
14
|
|
|
public function init($host) { |
15
|
|
|
$this->host = $host; |
16
|
|
|
$this->pdo = Db::pdo(); |
17
|
|
|
|
18
|
|
|
$host->add_hook($host::HOOK_AUTH_USER, $this); |
19
|
|
|
} |
20
|
|
|
|
21
|
|
|
public function authenticate($login, $password, $service = '') { |
22
|
|
|
|
23
|
|
|
$pwd_hash1 = encrypt_password($password); |
24
|
|
|
$pwd_hash2 = encrypt_password($password, $login); |
25
|
|
|
$otp = $_REQUEST["otp"]; |
26
|
|
|
|
27
|
|
|
if (get_schema_version() > 96) { |
28
|
|
|
|
29
|
|
|
$sth = $this->pdo->prepare("SELECT otp_enabled,salt FROM ttrss_users WHERE |
30
|
|
|
login = ?"); |
31
|
|
|
$sth->execute([$login]); |
32
|
|
|
|
33
|
|
|
if ($row = $sth->fetch()) { |
34
|
|
|
$otp_enabled = $row['otp_enabled']; |
35
|
|
|
|
36
|
|
|
if ($otp_enabled) { |
37
|
|
|
|
38
|
|
|
// only allow app password checking if OTP is enabled |
39
|
|
|
if ($service && get_schema_version() > 138) { |
40
|
|
|
return $this->check_app_password($login, $password, $service); |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
if ($otp) { |
44
|
|
|
$base32 = new \OTPHP\Base32(); |
|
|
|
|
45
|
|
|
|
46
|
|
|
$secret = $base32->encode(mb_substr(sha1($row["salt"]), 0, 12), false); |
47
|
|
|
$secret_legacy = $base32->encode(sha1($row["salt"])); |
48
|
|
|
|
49
|
|
|
$totp = new \OTPHP\TOTP($secret); |
|
|
|
|
50
|
|
|
$otp_check = $totp->now(); |
51
|
|
|
|
52
|
|
|
$totp_legacy = new \OTPHP\TOTP($secret_legacy); |
53
|
|
|
$otp_check_legacy = $totp_legacy->now(); |
54
|
|
|
|
55
|
|
|
if ($otp != $otp_check && $otp != $otp_check_legacy) { |
56
|
|
|
return false; |
57
|
|
|
} |
58
|
|
|
} else { |
59
|
|
|
$return = urlencode($_REQUEST["return"]); |
60
|
|
|
?> |
61
|
|
|
<!DOCTYPE html> |
62
|
|
|
<html> |
63
|
|
|
<head> |
64
|
|
|
<title>Tiny Tiny RSS</title> |
65
|
|
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> |
66
|
|
|
</head> |
67
|
|
|
<?php echo stylesheet_tag("css/default.css") ?> |
|
|
|
|
68
|
|
|
<body class="ttrss_utility otp"> |
69
|
|
|
<h1><?php echo __("Authentication") ?></h1> |
70
|
|
|
<div class="content"> |
71
|
|
|
<form action="public.php?return=<?php echo $return ?>" |
72
|
|
|
method="POST" class="otpform"> |
73
|
|
|
<input type="hidden" name="op" value="login"> |
74
|
|
|
<input type="hidden" name="login" value="<?php echo htmlspecialchars($login) ?>"> |
75
|
|
|
<input type="hidden" name="password" value="<?php echo htmlspecialchars($password) ?>"> |
76
|
|
|
<input type="hidden" name="bw_limit" value="<?php echo htmlspecialchars($_POST["bw_limit"]) ?>"> |
77
|
|
|
<input type="hidden" name="remember_me" value="<?php echo htmlspecialchars($_POST["remember_me"]) ?>"> |
78
|
|
|
<input type="hidden" name="profile" value="<?php echo htmlspecialchars($_POST["profile"]) ?>"> |
79
|
|
|
|
80
|
|
|
<fieldset> |
81
|
|
|
<label><?php echo __("Please enter your one time password:") ?></label> |
82
|
|
|
<input autocomplete="off" size="6" name="otp" value=""/> |
83
|
|
|
<input type="submit" value="Continue"/> |
84
|
|
|
</fieldset> |
85
|
|
|
</form></div> |
86
|
|
|
<script type="text/javascript"> |
87
|
|
|
document.forms[0].otp.focus(); |
88
|
|
|
</script> |
89
|
|
|
<?php |
90
|
|
|
exit; |
|
|
|
|
91
|
|
|
} |
92
|
|
|
} |
93
|
|
|
} |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
// check app passwords first but allow regular password as a fallback for the time being |
97
|
|
|
// if OTP is not enabled |
98
|
|
|
|
99
|
|
|
if ($service && get_schema_version() > 138) { |
100
|
|
|
$user_id = $this->check_app_password($login, $password, $service); |
101
|
|
|
|
102
|
|
|
if ($user_id) { |
103
|
|
|
return $user_id; |
104
|
|
|
} |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
if (get_schema_version() > 87) { |
108
|
|
|
|
109
|
|
|
$sth = $this->pdo->prepare("SELECT salt FROM ttrss_users WHERE login = ?"); |
110
|
|
|
$sth->execute([$login]); |
111
|
|
|
|
112
|
|
|
if ($row = $sth->fetch()) { |
113
|
|
|
$salt = $row['salt']; |
114
|
|
|
|
115
|
|
|
if ($salt == "") { |
116
|
|
|
|
117
|
|
|
$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE |
118
|
|
|
login = ? AND (pwd_hash = ? OR pwd_hash = ?)"); |
119
|
|
|
|
120
|
|
|
$sth->execute([$login, $pwd_hash1, $pwd_hash2]); |
121
|
|
|
|
122
|
|
|
// verify and upgrade password to new salt base |
123
|
|
|
|
124
|
|
|
if ($row = $sth->fetch()) { |
125
|
|
|
// upgrade password to MODE2 |
126
|
|
|
|
127
|
|
|
$user_id = $row['id']; |
128
|
|
|
|
129
|
|
|
$salt = substr(bin2hex(get_random_bytes(125)), 0, 250); |
130
|
|
|
$pwd_hash = encrypt_password($password, $salt, true); |
131
|
|
|
|
132
|
|
|
$sth = $this->pdo->prepare("UPDATE ttrss_users SET |
133
|
|
|
pwd_hash = ?, salt = ? WHERE login = ?"); |
134
|
|
|
|
135
|
|
|
$sth->execute([$pwd_hash, $salt, $login]); |
136
|
|
|
|
137
|
|
|
return $user_id; |
138
|
|
|
|
139
|
|
|
} else { |
140
|
|
|
return false; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
} else { |
144
|
|
|
$pwd_hash = encrypt_password($password, $salt, true); |
145
|
|
|
|
146
|
|
|
$sth = $this->pdo->prepare("SELECT id |
147
|
|
|
FROM ttrss_users WHERE |
148
|
|
|
login = ? AND pwd_hash = ?"); |
149
|
|
|
$sth->execute([$login, $pwd_hash]); |
150
|
|
|
|
151
|
|
|
if ($row = $sth->fetch()) { |
152
|
|
|
return $row['id']; |
153
|
|
|
} |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
} else { |
157
|
|
|
$sth = $this->pdo->prepare("SELECT id |
158
|
|
|
FROM ttrss_users WHERE |
159
|
|
|
login = ? AND (pwd_hash = ? OR pwd_hash = ?)"); |
160
|
|
|
|
161
|
|
|
$sth->execute([$login, $pwd_hash1, $pwd_hash2]); |
162
|
|
|
|
163
|
|
|
if ($row = $sth->fetch()) { |
164
|
|
|
return $row['id']; |
165
|
|
|
} |
166
|
|
|
} |
167
|
|
|
} else { |
168
|
|
|
$sth = $this->pdo->prepare("SELECT id |
169
|
|
|
FROM ttrss_users WHERE |
170
|
|
|
login = ? AND (pwd_hash = ? OR pwd_hash = ?)"); |
171
|
|
|
|
172
|
|
|
$sth->execute([$login, $pwd_hash1, $pwd_hash2]); |
173
|
|
|
|
174
|
|
|
if ($row = $sth->fetch()) { |
175
|
|
|
return $row['id']; |
176
|
|
|
} |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
return false; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
public function check_password($owner_uid, $password) { |
183
|
|
|
|
184
|
|
|
$sth = $this->pdo->prepare("SELECT salt,login,otp_enabled FROM ttrss_users WHERE |
185
|
|
|
id = ?"); |
186
|
|
|
$sth->execute([$owner_uid]); |
187
|
|
|
|
188
|
|
|
if ($row = $sth->fetch()) { |
189
|
|
|
|
190
|
|
|
$salt = $row['salt']; |
191
|
|
|
$login = $row['login']; |
192
|
|
|
|
193
|
|
|
if (!$salt) { |
194
|
|
|
$password_hash1 = encrypt_password($password); |
195
|
|
|
$password_hash2 = encrypt_password($password, $login); |
196
|
|
|
|
197
|
|
|
$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE |
198
|
|
|
id = ? AND (pwd_hash = ? OR pwd_hash = ?)"); |
199
|
|
|
|
200
|
|
|
$sth->execute([$owner_uid, $password_hash1, $password_hash2]); |
201
|
|
|
|
202
|
|
|
return $sth->fetch(); |
203
|
|
|
|
204
|
|
|
} else { |
205
|
|
|
$password_hash = encrypt_password($password, $salt, true); |
206
|
|
|
|
207
|
|
|
$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE |
208
|
|
|
id = ? AND pwd_hash = ?"); |
209
|
|
|
|
210
|
|
|
$sth->execute([$owner_uid, $password_hash]); |
211
|
|
|
|
212
|
|
|
return $sth->fetch(); |
213
|
|
|
} |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
return false; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
public function change_password($owner_uid, $old_password, $new_password) { |
220
|
|
|
|
221
|
|
|
if ($this->check_password($owner_uid, $old_password)) { |
222
|
|
|
|
223
|
|
|
$new_salt = substr(bin2hex(get_random_bytes(125)), 0, 250); |
224
|
|
|
$new_password_hash = encrypt_password($new_password, $new_salt, true); |
225
|
|
|
|
226
|
|
|
$sth = $this->pdo->prepare("UPDATE ttrss_users SET |
227
|
|
|
pwd_hash = ?, salt = ?, otp_enabled = false |
228
|
|
|
WHERE id = ?"); |
229
|
|
|
$sth->execute([$new_password_hash, $new_salt, $owner_uid]); |
230
|
|
|
|
231
|
|
|
$_SESSION["pwd_hash"] = $new_password_hash; |
232
|
|
|
|
233
|
|
|
$sth = $this->pdo->prepare("SELECT email, login FROM ttrss_users WHERE id = ?"); |
234
|
|
|
$sth->execute([$owner_uid]); |
235
|
|
|
|
236
|
|
|
if ($row = $sth->fetch()) { |
237
|
|
|
$mailer = new Mailer(); |
238
|
|
|
|
239
|
|
|
require_once "lib/MiniTemplator.class.php"; |
240
|
|
|
|
241
|
|
|
$tpl = new MiniTemplator; |
242
|
|
|
|
243
|
|
|
$tpl->readTemplateFromFile("templates/password_change_template.txt"); |
244
|
|
|
|
245
|
|
|
$tpl->setVariable('LOGIN', $row["login"]); |
246
|
|
|
$tpl->setVariable('TTRSS_HOST', SELF_URL_PATH); |
|
|
|
|
247
|
|
|
|
248
|
|
|
$tpl->addBlock('message'); |
249
|
|
|
|
250
|
|
|
$tpl->generateOutputToString($message); |
251
|
|
|
|
252
|
|
|
$mailer->mail(["to_name" => $row["login"], |
253
|
|
|
"to_address" => $row["email"], |
254
|
|
|
"subject" => "[tt-rss] Password change notification", |
255
|
|
|
"message" => $message]); |
256
|
|
|
|
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
return __("Password has been changed."); |
260
|
|
|
} else { |
261
|
|
|
return "ERROR: ".__('Old password is incorrect.'); |
262
|
|
|
} |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
private function check_app_password($login, $password, $service) { |
266
|
|
|
$sth = $this->pdo->prepare("SELECT p.id, p.pwd_hash, u.id AS uid |
267
|
|
|
FROM ttrss_app_passwords p, ttrss_users u |
268
|
|
|
WHERE p.owner_uid = u.id AND u.login = ? AND service = ?"); |
269
|
|
|
$sth->execute([$login, $service]); |
270
|
|
|
|
271
|
|
|
while ($row = $sth->fetch()) { |
272
|
|
|
list ($algo, $hash, $salt) = explode(":", $row["pwd_hash"]); |
273
|
|
|
|
274
|
|
|
if ($algo == "SSHA-512") { |
275
|
|
|
$test_hash = hash('sha512', $salt.$password); |
276
|
|
|
|
277
|
|
|
if ($test_hash == $hash) { |
278
|
|
|
$usth = $this->pdo->prepare("UPDATE ttrss_app_passwords SET last_used = NOW() WHERE id = ?"); |
279
|
|
|
$usth->execute([$row['id']]); |
280
|
|
|
|
281
|
|
|
return $row['uid']; |
282
|
|
|
} |
283
|
|
|
} else { |
284
|
|
|
user_error("Got unknown algo of app password for user $login: $algo"); |
285
|
|
|
} |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
return false; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
public function api_version() { |
292
|
|
|
return 2; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
} |
296
|
|
|
|
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths