Passed
Pull Request — master (#5)
by Cody
03:17
created

Auth_Internal::authenticate()   D

Complexity

Conditions 19
Paths 71

Size

Total Lines 158
Code Lines 80

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 1
Metric Value
cc 19
eloc 80
c 3
b 1
f 1
nc 71
nop 3
dl 0
loc 158
rs 4.5166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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