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
|
|
|
if (get_schema_version() > 87) { |
107
|
|
|
|
108
|
|
|
$sth = $this->pdo->prepare("SELECT salt FROM ttrss_users WHERE login = ?"); |
109
|
|
|
$sth->execute([$login]); |
110
|
|
|
|
111
|
|
|
if ($row = $sth->fetch()) { |
112
|
|
|
$salt = $row['salt']; |
113
|
|
|
|
114
|
|
|
if ($salt == "") { |
115
|
|
|
|
116
|
|
|
$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE |
117
|
|
|
login = ? AND (pwd_hash = ? OR pwd_hash = ?)"); |
118
|
|
|
|
119
|
|
|
$sth->execute([$login, $pwd_hash1, $pwd_hash2]); |
120
|
|
|
|
121
|
|
|
// verify and upgrade password to new salt base |
122
|
|
|
|
123
|
|
|
if ($row = $sth->fetch()) { |
124
|
|
|
// upgrade password to MODE2 |
125
|
|
|
|
126
|
|
|
$user_id = $row['id']; |
127
|
|
|
|
128
|
|
|
$salt = substr(bin2hex(get_random_bytes(125)), 0, 250); |
129
|
|
|
$pwd_hash = encrypt_password($password, $salt, true); |
130
|
|
|
|
131
|
|
|
$sth = $this->pdo->prepare("UPDATE ttrss_users SET |
132
|
|
|
pwd_hash = ?, salt = ? WHERE login = ?"); |
133
|
|
|
|
134
|
|
|
$sth->execute([$pwd_hash, $salt, $login]); |
135
|
|
|
|
136
|
|
|
return $user_id; |
137
|
|
|
|
138
|
|
|
} else { |
139
|
|
|
return false; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
} else { |
143
|
|
|
$pwd_hash = encrypt_password($password, $salt, true); |
144
|
|
|
|
145
|
|
|
$sth = $this->pdo->prepare("SELECT id |
146
|
|
|
FROM ttrss_users WHERE |
147
|
|
|
login = ? AND pwd_hash = ?"); |
148
|
|
|
$sth->execute([$login, $pwd_hash]); |
149
|
|
|
|
150
|
|
|
if ($row = $sth->fetch()) { |
151
|
|
|
return $row['id']; |
152
|
|
|
} |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
} else { |
156
|
|
|
$sth = $this->pdo->prepare("SELECT id |
157
|
|
|
FROM ttrss_users WHERE |
158
|
|
|
login = ? AND (pwd_hash = ? OR pwd_hash = ?)"); |
159
|
|
|
|
160
|
|
|
$sth->execute([$login, $pwd_hash1, $pwd_hash2]); |
161
|
|
|
|
162
|
|
|
if ($row = $sth->fetch()) { |
163
|
|
|
return $row['id']; |
164
|
|
|
} |
165
|
|
|
} |
166
|
|
|
} else { |
167
|
|
|
$sth = $this->pdo->prepare("SELECT id |
168
|
|
|
FROM ttrss_users WHERE |
169
|
|
|
login = ? AND (pwd_hash = ? OR pwd_hash = ?)"); |
170
|
|
|
|
171
|
|
|
$sth->execute([$login, $pwd_hash1, $pwd_hash2]); |
172
|
|
|
|
173
|
|
|
if ($row = $sth->fetch()) { |
174
|
|
|
return $row['id']; |
175
|
|
|
} |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
return false; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
public function check_password($owner_uid, $password) { |
182
|
|
|
|
183
|
|
|
$sth = $this->pdo->prepare("SELECT salt,login,otp_enabled FROM ttrss_users WHERE |
184
|
|
|
id = ?"); |
185
|
|
|
$sth->execute([$owner_uid]); |
186
|
|
|
|
187
|
|
|
if ($row = $sth->fetch()) { |
188
|
|
|
|
189
|
|
|
$salt = $row['salt']; |
190
|
|
|
$login = $row['login']; |
191
|
|
|
|
192
|
|
|
if (!$salt) { |
193
|
|
|
$password_hash1 = encrypt_password($password); |
194
|
|
|
$password_hash2 = encrypt_password($password, $login); |
195
|
|
|
|
196
|
|
|
$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE |
197
|
|
|
id = ? AND (pwd_hash = ? OR pwd_hash = ?)"); |
198
|
|
|
|
199
|
|
|
$sth->execute([$owner_uid, $password_hash1, $password_hash2]); |
200
|
|
|
|
201
|
|
|
return $sth->fetch(); |
202
|
|
|
|
203
|
|
|
} else { |
204
|
|
|
$password_hash = encrypt_password($password, $salt, true); |
205
|
|
|
|
206
|
|
|
$sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE |
207
|
|
|
id = ? AND pwd_hash = ?"); |
208
|
|
|
|
209
|
|
|
$sth->execute([$owner_uid, $password_hash]); |
210
|
|
|
|
211
|
|
|
return $sth->fetch(); |
212
|
|
|
} |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
return false; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
public function change_password($owner_uid, $old_password, $new_password) { |
219
|
|
|
|
220
|
|
|
if ($this->check_password($owner_uid, $old_password)) { |
221
|
|
|
|
222
|
|
|
$new_salt = substr(bin2hex(get_random_bytes(125)), 0, 250); |
223
|
|
|
$new_password_hash = encrypt_password($new_password, $new_salt, true); |
224
|
|
|
|
225
|
|
|
$sth = $this->pdo->prepare("UPDATE ttrss_users SET |
226
|
|
|
pwd_hash = ?, salt = ?, otp_enabled = false |
227
|
|
|
WHERE id = ?"); |
228
|
|
|
$sth->execute([$new_password_hash, $new_salt, $owner_uid]); |
229
|
|
|
|
230
|
|
|
$_SESSION["pwd_hash"] = $new_password_hash; |
231
|
|
|
|
232
|
|
|
$sth = $this->pdo->prepare("SELECT email, login FROM ttrss_users WHERE id = ?"); |
233
|
|
|
$sth->execute([$owner_uid]); |
234
|
|
|
|
235
|
|
|
if ($row = $sth->fetch()) { |
236
|
|
|
$mailer = new Mailer(); |
237
|
|
|
|
238
|
|
|
require_once "lib/MiniTemplator.class.php"; |
239
|
|
|
|
240
|
|
|
$tpl = new MiniTemplator; |
241
|
|
|
|
242
|
|
|
$tpl->readTemplateFromFile("templates/password_change_template.txt"); |
243
|
|
|
|
244
|
|
|
$tpl->setVariable('LOGIN', $row["login"]); |
245
|
|
|
$tpl->setVariable('TTRSS_HOST', SELF_URL_PATH); |
246
|
|
|
|
247
|
|
|
$tpl->addBlock('message'); |
248
|
|
|
|
249
|
|
|
$tpl->generateOutputToString($message); |
250
|
|
|
|
251
|
|
|
$mailer->mail(["to_name" => $row["login"], |
252
|
|
|
"to_address" => $row["email"], |
253
|
|
|
"subject" => "[tt-rss] Password change notification", |
254
|
|
|
"message" => $message]); |
255
|
|
|
|
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
return __("Password has been changed."); |
259
|
|
|
} else { |
260
|
|
|
return "ERROR: ".__('Old password is incorrect.'); |
261
|
|
|
} |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
private function check_app_password($login, $password, $service) { |
265
|
|
|
$sth = $this->pdo->prepare("SELECT p.id, p.pwd_hash, u.id AS uid |
266
|
|
|
FROM ttrss_app_passwords p, ttrss_users u |
267
|
|
|
WHERE p.owner_uid = u.id AND u.login = ? AND service = ?"); |
268
|
|
|
$sth->execute([$login, $service]); |
269
|
|
|
|
270
|
|
|
while ($row = $sth->fetch()) { |
271
|
|
|
list ($algo, $hash, $salt) = explode(":", $row["pwd_hash"]); |
272
|
|
|
|
273
|
|
|
if ($algo == "SSHA-512") { |
274
|
|
|
$test_hash = hash('sha512', $salt . $password); |
275
|
|
|
|
276
|
|
|
if ($test_hash == $hash) { |
277
|
|
|
$usth = $this->pdo->prepare("UPDATE ttrss_app_passwords SET last_used = NOW() WHERE id = ?"); |
278
|
|
|
$usth->execute([$row['id']]); |
279
|
|
|
|
280
|
|
|
return $row['uid']; |
281
|
|
|
} |
282
|
|
|
} else { |
283
|
|
|
user_error("Got unknown algo of app password for user $login: $algo"); |
284
|
|
|
} |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
return false; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
public function api_version() { |
291
|
|
|
return 2; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
} |
295
|
|
|
|