Commit | Line | Data |
---|---|---|
dfef03a8 AE |
1 | <?php |
2 | namespace wcf\acp\form; | |
3 | use wcf\data\application\Application; | |
4 | use wcf\data\application\ApplicationAction; | |
5 | use wcf\data\application\ApplicationEditor; | |
6 | use wcf\data\application\ApplicationList; | |
7 | use wcf\data\user\authentication\failure\UserAuthenticationFailure; | |
8 | use wcf\data\user\authentication\failure\UserAuthenticationFailureAction; | |
9 | use wcf\data\user\User; | |
10 | use wcf\form\AbstractCaptchaForm; | |
11 | use wcf\system\application\ApplicationHandler; | |
12 | use wcf\system\exception\IllegalLinkException; | |
13 | use wcf\system\exception\NamedUserException; | |
14 | use wcf\system\exception\UserInputException; | |
15 | use wcf\system\style\StyleHandler; | |
16 | use wcf\system\user\authentication\EmailUserAuthentication; | |
17 | use wcf\system\user\authentication\UserAuthenticationFactory; | |
18 | use wcf\system\WCF; | |
19 | use wcf\system\WCFACP; | |
20 | use wcf\util\FileUtil; | |
21 | use wcf\util\HeaderUtil; | |
22 | use wcf\util\StringUtil; | |
23 | use wcf\util\UserUtil; | |
24 | ||
25 | /** | |
26 | * Shows the rescue mode form. | |
27 | * | |
28 | * @author Alexander Ebert | |
c839bd49 | 29 | * @copyright 2001-2018 WoltLab GmbH |
dfef03a8 | 30 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
e71525e4 | 31 | * @package WoltLabSuite\Core\Acp\Form |
dfef03a8 AE |
32 | */ |
33 | class RescueModeForm extends AbstractCaptchaForm { | |
34 | /** | |
893aace3 | 35 | * @var Application[] |
dfef03a8 AE |
36 | */ |
37 | public $applications; | |
38 | ||
39 | /** | |
fea72e74 | 40 | * @var string[][] |
dfef03a8 AE |
41 | */ |
42 | public $applicationValues = []; | |
43 | ||
44 | /** | |
45 | * login password | |
46 | * @var string | |
47 | */ | |
48 | public $password = ''; | |
49 | ||
50 | /** | |
893aace3 | 51 | * @var User |
dfef03a8 AE |
52 | */ |
53 | public $user; | |
54 | ||
55 | /** | |
56 | * login username | |
57 | * @var string | |
58 | */ | |
59 | public $username = ''; | |
60 | ||
61 | /** | |
62 | * @inheritDoc | |
63 | */ | |
64 | public $useCaptcha = false; | |
65 | ||
66 | /** | |
67 | * @inheritDoc | |
68 | */ | |
69 | public function __run() { | |
70 | if (!WCFACP::inRescueMode()) { | |
71 | // redirect to currently active application's ACP | |
72 | HeaderUtil::redirect(ApplicationHandler::getInstance()->getActiveApplication()->getPageURL() . 'acp/'); | |
73 | exit; | |
74 | } | |
75 | ||
76 | parent::__run(); | |
77 | } | |
78 | ||
79 | /** | |
80 | * @inheritDoc | |
81 | */ | |
82 | public function readParameters() { | |
83 | parent::readParameters(); | |
84 | ||
85 | // request style generation to prevent issues when using the proxy parameter | |
86 | StyleHandler::getInstance()->getStylesheet(true); | |
87 | ||
88 | if (isset($_GET['proxy'])) { | |
89 | switch ($_GET['proxy']) { | |
90 | case 'css': | |
91 | $file = WCF_DIR . 'acp/style/style.css'; | |
92 | ||
93 | header('Content-Type: text/css'); | |
94 | break; | |
95 | ||
96 | case 'logo': | |
97 | $file = WCF_DIR . 'images/default-logo.png'; | |
98 | ||
99 | header('Content-Type: image/png'); | |
100 | break; | |
101 | ||
102 | default: | |
103 | throw new IllegalLinkException(); | |
104 | break; | |
105 | } | |
106 | ||
107 | header('Expires: '.gmdate('D, d M Y H:i:s', time() + 3600).' GMT'); | |
108 | header('Last-Modified: Mon, 26 Jul 1997 05:00:00 GMT'); | |
109 | header('Cache-Control: public, max-age=3600'); | |
110 | ||
111 | readfile($file); | |
112 | exit; | |
113 | } | |
114 | ||
115 | // check authentication failures | |
116 | if (ENABLE_USER_AUTHENTICATION_FAILURE) { | |
117 | $failures = UserAuthenticationFailure::countIPFailures(UserUtil::getIpAddress()); | |
118 | if (USER_AUTHENTICATION_FAILURE_IP_BLOCK && $failures >= USER_AUTHENTICATION_FAILURE_IP_BLOCK) { | |
119 | throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.user.login.blocked')); | |
120 | } | |
121 | if (USER_AUTHENTICATION_FAILURE_IP_CAPTCHA && $failures >= USER_AUTHENTICATION_FAILURE_IP_CAPTCHA) { | |
122 | $this->useCaptcha = true; | |
123 | } | |
124 | else if (USER_AUTHENTICATION_FAILURE_USER_CAPTCHA) { | |
125 | if (isset($_POST['username'])) { | |
126 | $user = User::getUserByUsername(StringUtil::trim($_POST['username'])); | |
127 | if (!$user->userID) $user = User::getUserByEmail(StringUtil::trim($_POST['username'])); | |
128 | ||
129 | if ($user->userID) { | |
130 | $failures = UserAuthenticationFailure::countUserFailures($user->userID); | |
131 | if (USER_AUTHENTICATION_FAILURE_USER_CAPTCHA && $failures >= USER_AUTHENTICATION_FAILURE_USER_CAPTCHA) { | |
132 | $this->useCaptcha = true; | |
133 | } | |
134 | } | |
135 | } | |
136 | } | |
137 | } | |
138 | ||
139 | // read applications | |
140 | $applicationList = new ApplicationList(); | |
141 | $applicationList->readObjects(); | |
142 | $this->applications = $applicationList->getObjects(); | |
143 | } | |
144 | ||
145 | /** | |
146 | * @inheritDoc | |
147 | */ | |
148 | public function readFormParameters() { | |
149 | parent::readFormParameters(); | |
150 | ||
151 | if (isset($_POST['username'])) $this->username = StringUtil::trim($_POST['username']); | |
152 | if (isset($_POST['password'])) $this->password = $_POST['password']; | |
153 | if (isset($_POST['applicationValues']) && is_array($_POST['applicationValues'])) $this->applicationValues = $_POST['applicationValues']; | |
154 | } | |
155 | ||
156 | /** | |
157 | * Validates the user access data. | |
158 | */ | |
159 | protected function validateUser() { | |
160 | try { | |
161 | $this->user = UserAuthenticationFactory::getInstance()->getUserAuthentication()->loginManually($this->username, $this->password); | |
162 | } | |
163 | catch (UserInputException $e) { | |
164 | if ($e->getField() == 'username') { | |
165 | try { | |
166 | $this->user = EmailUserAuthentication::getInstance()->loginManually($this->username, $this->password); | |
167 | } | |
168 | catch (UserInputException $e2) { | |
169 | if ($e2->getField() == 'username') throw $e; | |
170 | throw $e2; | |
171 | } | |
172 | } | |
173 | else { | |
174 | throw $e; | |
175 | } | |
176 | } | |
177 | ||
178 | // simulate login in order to access permissions | |
179 | WCF::getSession()->disableUpdate(); | |
180 | WCF::getSession()->changeUser($this->user, true); | |
181 | ||
6476e7a1 | 182 | if (!WCF::getSession()->getPermission('admin.configuration.canManageApplication')) { |
dfef03a8 AE |
183 | throw new UserInputException('username', 'notAuthorized'); |
184 | } | |
185 | } | |
186 | ||
187 | protected function validateApplications() { | |
188 | $usedPaths = []; | |
189 | foreach ($this->applications as $application) { | |
190 | $packageID = $application->packageID; | |
191 | ||
192 | $domainName = $this->applicationValues[$packageID]['domainName']; | |
193 | $domainName = preg_replace('~^https?://~', '', $domainName); | |
194 | $domainName = FileUtil::removeTrailingSlash($domainName); | |
195 | $domainName = StringUtil::trim($domainName); | |
196 | ||
197 | if (empty($domainName)) { | |
198 | throw new UserInputException("application_{$packageID}_domainName"); | |
199 | } | |
200 | else if (preg_match('~[/#\?&]~', $domainName)) { | |
201 | throw new UserInputException("application_{$packageID}_domainName", 'containsPath'); | |
202 | } | |
203 | ||
204 | $domainPath = FileUtil::addLeadingSlash(FileUtil::addTrailingSlash($this->applicationValues[$packageID]['domainPath'])); | |
205 | ||
206 | $this->applicationValues[$packageID]['domainName'] = $domainName; | |
207 | $this->applicationValues[$packageID]['domainPath'] = $domainPath; | |
208 | ||
209 | if (isset($usedPaths[$domainName])) { | |
d38697d0 | 210 | if (isset($usedPaths[$domainName][$domainPath])) { |
dfef03a8 AE |
211 | WCF::getTPL()->assign('conflictApplication', $this->applications[$usedPaths[$domainName][$domainPath]]->getPackage()); |
212 | throw new UserInputException("application_{$packageID}_domainPath", 'conflict'); | |
213 | } | |
214 | } | |
215 | else { | |
216 | $usedPaths[$domainName] = []; | |
217 | } | |
218 | ||
219 | $usedPaths[$domainName][$domainPath] = $packageID; | |
220 | } | |
221 | } | |
222 | ||
223 | /** | |
224 | * @inheritDoc | |
225 | */ | |
226 | public function submit() { | |
227 | parent::submit(); | |
228 | ||
229 | // save authentication failure | |
230 | if (ENABLE_USER_AUTHENTICATION_FAILURE) { | |
231 | if ($this->errorField == 'username' || $this->errorField == 'password') { | |
232 | $action = new UserAuthenticationFailureAction([], 'create', [ | |
233 | 'data' => [ | |
234 | 'environment' => 'admin', | |
63b9817b | 235 | 'userID' => $this->user !== null ? $this->user->userID : null, |
dfef03a8 AE |
236 | 'username' => $this->username, |
237 | 'time' => TIME_NOW, | |
238 | 'ipAddress' => UserUtil::getIpAddress(), | |
239 | 'userAgent' => UserUtil::getUserAgent() | |
240 | ] | |
241 | ]); | |
242 | $action->executeAction(); | |
243 | ||
244 | if ($this->captchaObjectType) { | |
245 | $this->captchaObjectType->getProcessor()->reset(); | |
246 | } | |
247 | } | |
248 | } | |
249 | } | |
250 | ||
251 | /** | |
252 | * @inheritDoc | |
253 | */ | |
254 | public function validate() { | |
255 | // bypass security token validation | |
256 | $_POST['t'] = WCF::getSession()->getSecurityToken(); | |
257 | ||
258 | parent::validate(); | |
259 | ||
260 | // error handling | |
261 | if (empty($this->username)) { | |
262 | throw new UserInputException('username'); | |
263 | } | |
264 | ||
265 | if (empty($this->password)) { | |
266 | throw new UserInputException('password'); | |
267 | } | |
268 | ||
269 | $this->validateUser(); | |
270 | $this->validateApplications(); | |
271 | } | |
272 | ||
273 | /** | |
274 | * @inheritDoc | |
275 | */ | |
276 | public function save() { | |
277 | parent::save(); | |
278 | ||
279 | // update applications | |
280 | foreach ($this->applications as $application) { | |
281 | $applicationEditor = new ApplicationEditor($application); | |
282 | $applicationEditor->update([ | |
283 | 'domainName' => $this->applicationValues[$application->packageID]['domainName'], | |
284 | 'domainPath' => $this->applicationValues[$application->packageID]['domainPath'], | |
285 | 'cookieDomain' => $this->applicationValues[$application->packageID]['domainName'] | |
286 | ]); | |
287 | } | |
288 | ||
289 | // rebuild cookie domain and paths | |
290 | $applicationAction = new ApplicationAction([], 'rebuild'); | |
291 | $applicationAction->executeAction(); | |
292 | ||
293 | // reload currently active application to avoid outdated cache data | |
294 | $application = ApplicationHandler::getInstance()->getActiveApplication(); | |
295 | $application = new Application($application->packageID); | |
296 | ||
297 | // redirect to ACP of currently active application | |
298 | HeaderUtil::redirect($application->getPageURL() . 'acp/'); | |
299 | exit; | |
300 | } | |
301 | ||
302 | /** | |
303 | * @inheritDoc | |
304 | */ | |
305 | public function readData() { | |
306 | parent::readData(); | |
307 | ||
308 | // get preferred username | |
309 | if (empty($_POST)) { | |
310 | if (isset($_COOKIE[COOKIE_PREFIX.'userID'])) { | |
311 | $user = new User(intval($_COOKIE[COOKIE_PREFIX.'userID'])); | |
312 | if ($user->userID) $this->username = $user->username; | |
313 | } | |
314 | ||
315 | foreach ($this->applications as $application) { | |
316 | $this->applicationValues[$application->packageID] = [ | |
317 | 'domainName' => $application->domainName, | |
318 | 'domainPath' => $application->domainPath | |
319 | ]; | |
320 | } | |
321 | } | |
322 | } | |
323 | ||
324 | /** | |
325 | * @inheritDoc | |
326 | */ | |
327 | public function assignVariables() { | |
328 | parent::assignVariables(); | |
329 | ||
330 | WCF::getTPL()->assign([ | |
331 | 'applications' => $this->applications, | |
332 | 'applicationValues' => $this->applicationValues, | |
333 | 'pageURL' => WCFACP::getRescueModePageURL() . 'acp/index.php?rescue-mode/', | |
334 | 'password' => $this->password, | |
335 | 'username' => $this->username | |
336 | ]); | |
337 | } | |
338 | } |