Passed
Push — master ( 60be72...4c6eb9 )
by Morris
12:37
created
lib/public/AppFramework/Http/TooManyRequestsResponse.php 1 patch
Indentation   +16 added lines, -16 removed lines patch added patch discarded remove patch
@@ -31,22 +31,22 @@
 block discarded – undo
31 31
  */
32 32
 class TooManyRequestsResponse extends Response {
33 33
 
34
-	/**
35
-	 * @since 19.0.0
36
-	 */
37
-	public function __construct() {
38
-		parent::__construct();
34
+    /**
35
+     * @since 19.0.0
36
+     */
37
+    public function __construct() {
38
+        parent::__construct();
39 39
 
40
-		$this->setContentSecurityPolicy(new ContentSecurityPolicy());
41
-		$this->setStatus(429);
42
-	}
40
+        $this->setContentSecurityPolicy(new ContentSecurityPolicy());
41
+        $this->setStatus(429);
42
+    }
43 43
 
44
-	/**
45
-	 * @return string
46
-	 * @since 19.0.0
47
-	 */
48
-	public function render() {
49
-		$template = new Template('core', '429', 'blank');
50
-		return $template->fetchPage();
51
-	}
44
+    /**
45
+     * @return string
46
+     * @since 19.0.0
47
+     */
48
+    public function render() {
49
+        $template = new Template('core', '429', 'blank');
50
+        return $template->fetchPage();
51
+    }
52 52
 }
Please login to merge, or discard this patch.
lib/private/legacy/OC_Template.php 1 patch
Indentation   +307 added lines, -307 removed lines patch added patch discarded remove patch
@@ -48,311 +48,311 @@
 block discarded – undo
48 48
  */
49 49
 class OC_Template extends \OC\Template\Base {
50 50
 
51
-	/** @var string */
52
-	private $renderAs; // Create a full page?
53
-
54
-	/** @var string */
55
-	private $path; // The path to the template
56
-
57
-	/** @var array */
58
-	private $headers = []; //custom headers
59
-
60
-	/** @var string */
61
-	protected $app; // app id
62
-
63
-	protected static $initTemplateEngineFirstRun = true;
64
-
65
-	/**
66
-	 * Constructor
67
-	 *
68
-	 * @param string $app app providing the template
69
-	 * @param string $name of the template file (without suffix)
70
-	 * @param string $renderAs If $renderAs is set, OC_Template will try to
71
-	 *                         produce a full page in the according layout. For
72
-	 *                         now, $renderAs can be set to "guest", "user" or
73
-	 *                         "admin".
74
-	 * @param bool $registerCall = true
75
-	 */
76
-	public function __construct($app, $name, $renderAs = TemplateResponse::RENDER_AS_BLANK, $registerCall = true) {
77
-		// Read the selected theme from the config file
78
-		self::initTemplateEngine($renderAs);
79
-
80
-		$theme = OC_Util::getTheme();
81
-
82
-		$requestToken = (OC::$server->getSession() && $registerCall) ? \OCP\Util::callRegister() : '';
83
-
84
-		$parts = explode('/', $app); // fix translation when app is something like core/lostpassword
85
-		$l10n = \OC::$server->getL10N($parts[0]);
86
-		/** @var \OCP\Defaults $themeDefaults */
87
-		$themeDefaults = \OC::$server->query(\OCP\Defaults::class);
88
-
89
-		list($path, $template) = $this->findTemplate($theme, $app, $name);
90
-
91
-		// Set the private data
92
-		$this->renderAs = $renderAs;
93
-		$this->path = $path;
94
-		$this->app = $app;
95
-
96
-		parent::__construct($template, $requestToken, $l10n, $themeDefaults);
97
-	}
98
-
99
-	/**
100
-	 * @param string $renderAs
101
-	 */
102
-	public static function initTemplateEngine($renderAs) {
103
-		if (self::$initTemplateEngineFirstRun) {
104
-
105
-			//apps that started before the template initialization can load their own scripts/styles
106
-			//so to make sure this scripts/styles here are loaded first we use OC_Util::addScript() with $prepend=true
107
-			//meaning the last script/style in this list will be loaded first
108
-			if (\OC::$server->getSystemConfig()->getValue('installed', false) && $renderAs !== TemplateResponse::RENDER_AS_ERROR && !\OCP\Util::needUpgrade()) {
109
-				if (\OC::$server->getConfig()->getAppValue('core', 'backgroundjobs_mode', 'ajax') == 'ajax') {
110
-					OC_Util::addScript('backgroundjobs', null, true);
111
-				}
112
-			}
113
-			OC_Util::addStyle('css-variables', null, true);
114
-			OC_Util::addStyle('server', null, true);
115
-			OC_Util::addTranslations('core', null, true);
116
-
117
-			if (\OC::$server->getSystemConfig()->getValue('installed', false)) {
118
-				OC_Util::addScript('merged-template-prepend', null, true);
119
-				OC_Util::addScript('dist/files_client', null, true);
120
-				OC_Util::addScript('dist/files_fileinfo', null, true);
121
-			}
122
-			OC_Util::addScript('core', 'dist/main', true);
123
-
124
-			if (\OC::$server->getRequest()->isUserAgent([\OC\AppFramework\Http\Request::USER_AGENT_IE])) {
125
-				// shim for the davclient.js library
126
-				\OCP\Util::addScript('dist/files_iedavclient');
127
-			}
128
-
129
-			self::$initTemplateEngineFirstRun = false;
130
-		}
131
-	}
132
-
133
-
134
-	/**
135
-	 * find the template with the given name
136
-	 * @param string $name of the template file (without suffix)
137
-	 *
138
-	 * Will select the template file for the selected theme.
139
-	 * Checking all the possible locations.
140
-	 * @param string $theme
141
-	 * @param string $app
142
-	 * @return string[]
143
-	 */
144
-	protected function findTemplate($theme, $app, $name) {
145
-		// Check if it is a app template or not.
146
-		if ($app !== '') {
147
-			$dirs = $this->getAppTemplateDirs($theme, $app, OC::$SERVERROOT, OC_App::getAppPath($app));
148
-		} else {
149
-			$dirs = $this->getCoreTemplateDirs($theme, OC::$SERVERROOT);
150
-		}
151
-		$locator = new \OC\Template\TemplateFileLocator($dirs);
152
-		$template = $locator->find($name);
153
-		$path = $locator->getPath();
154
-		return [$path, $template];
155
-	}
156
-
157
-	/**
158
-	 * Add a custom element to the header
159
-	 * @param string $tag tag name of the element
160
-	 * @param array $attributes array of attributes for the element
161
-	 * @param string $text the text content for the element. If $text is null then the
162
-	 * element will be written as empty element. So use "" to get a closing tag.
163
-	 */
164
-	public function addHeader($tag, $attributes, $text=null) {
165
-		$this->headers[]= [
166
-			'tag' => $tag,
167
-			'attributes' => $attributes,
168
-			'text' => $text
169
-		];
170
-	}
171
-
172
-	/**
173
-	 * Process the template
174
-	 * @return string
175
-	 *
176
-	 * This function process the template. If $this->renderAs is set, it
177
-	 * will produce a full page.
178
-	 */
179
-	public function fetchPage($additionalParams = null) {
180
-		$data = parent::fetchPage($additionalParams);
181
-
182
-		if ($this->renderAs) {
183
-			$page = new TemplateLayout($this->renderAs, $this->app);
184
-
185
-			if (is_array($additionalParams)) {
186
-				foreach ($additionalParams as $key => $value) {
187
-					$page->assign($key, $value);
188
-				}
189
-			}
190
-
191
-			// Add custom headers
192
-			$headers = '';
193
-			foreach (OC_Util::$headers as $header) {
194
-				$headers .= '<'.\OCP\Util::sanitizeHTML($header['tag']);
195
-				if (strcasecmp($header['tag'], 'script') === 0 && in_array('src', array_map('strtolower', array_keys($header['attributes'])))) {
196
-					$headers .= ' defer';
197
-				}
198
-				foreach ($header['attributes'] as $name=>$value) {
199
-					$headers .= ' '.\OCP\Util::sanitizeHTML($name).'="'.\OCP\Util::sanitizeHTML($value).'"';
200
-				}
201
-				if ($header['text'] !== null) {
202
-					$headers .= '>'.\OCP\Util::sanitizeHTML($header['text']).'</'.\OCP\Util::sanitizeHTML($header['tag']).'>';
203
-				} else {
204
-					$headers .= '/>';
205
-				}
206
-			}
207
-
208
-			$page->assign('headers', $headers);
209
-
210
-			$page->assign('content', $data);
211
-			return $page->fetchPage($additionalParams);
212
-		}
213
-
214
-		return $data;
215
-	}
216
-
217
-	/**
218
-	 * Include template
219
-	 *
220
-	 * @param string $file
221
-	 * @param array|null $additionalParams
222
-	 * @return string returns content of included template
223
-	 *
224
-	 * Includes another template. use <?php echo $this->inc('template'); ?> to
225
-	 * do this.
226
-	 */
227
-	public function inc($file, $additionalParams = null) {
228
-		return $this->load($this->path.$file.'.php', $additionalParams);
229
-	}
230
-
231
-	/**
232
-	 * Shortcut to print a simple page for users
233
-	 * @param string $application The application we render the template for
234
-	 * @param string $name Name of the template
235
-	 * @param array $parameters Parameters for the template
236
-	 * @return boolean|null
237
-	 */
238
-	public static function printUserPage($application, $name, $parameters = []) {
239
-		$content = new OC_Template($application, $name, "user");
240
-		foreach ($parameters as $key => $value) {
241
-			$content->assign($key, $value);
242
-		}
243
-		print $content->printPage();
244
-	}
245
-
246
-	/**
247
-	 * Shortcut to print a simple page for admins
248
-	 * @param string $application The application we render the template for
249
-	 * @param string $name Name of the template
250
-	 * @param array $parameters Parameters for the template
251
-	 * @return bool
252
-	 */
253
-	public static function printAdminPage($application, $name, $parameters = []) {
254
-		$content = new OC_Template($application, $name, "admin");
255
-		foreach ($parameters as $key => $value) {
256
-			$content->assign($key, $value);
257
-		}
258
-		return $content->printPage();
259
-	}
260
-
261
-	/**
262
-	 * Shortcut to print a simple page for guests
263
-	 * @param string $application The application we render the template for
264
-	 * @param string $name Name of the template
265
-	 * @param array|string $parameters Parameters for the template
266
-	 * @return bool
267
-	 */
268
-	public static function printGuestPage($application, $name, $parameters = []) {
269
-		$content = new OC_Template($application, $name, $name === 'error' ? $name : 'guest');
270
-		foreach ($parameters as $key => $value) {
271
-			$content->assign($key, $value);
272
-		}
273
-		return $content->printPage();
274
-	}
275
-
276
-	/**
277
-	 * Print a fatal error page and terminates the script
278
-	 * @param string $error_msg The error message to show
279
-	 * @param string $hint An optional hint message - needs to be properly escape
280
-	 * @param int $statusCode
281
-	 * @suppress PhanAccessMethodInternal
282
-	 */
283
-	public static function printErrorPage($error_msg, $hint = '', $statusCode = 500) {
284
-		if (\OC::$server->getAppManager()->isEnabledForUser('theming') && !\OC_App::isAppLoaded('theming')) {
285
-			\OC_App::loadApp('theming');
286
-		}
287
-
288
-
289
-		if ($error_msg === $hint) {
290
-			// If the hint is the same as the message there is no need to display it twice.
291
-			$hint = '';
292
-		}
293
-
294
-		http_response_code($statusCode);
295
-		try {
296
-			$content = new \OC_Template('', 'error', 'error', false);
297
-			$errors = [['error' => $error_msg, 'hint' => $hint]];
298
-			$content->assign('errors', $errors);
299
-			$content->printPage();
300
-		} catch (\Exception $e) {
301
-			$logger = \OC::$server->getLogger();
302
-			$logger->error("$error_msg $hint", ['app' => 'core']);
303
-			$logger->logException($e, ['app' => 'core']);
304
-
305
-			header('Content-Type: text/plain; charset=utf-8');
306
-			print("$error_msg $hint");
307
-		}
308
-		die();
309
-	}
310
-
311
-	/**
312
-	 * print error page using Exception details
313
-	 * @param Exception|Throwable $exception
314
-	 * @param int $statusCode
315
-	 * @return bool|string
316
-	 * @suppress PhanAccessMethodInternal
317
-	 */
318
-	public static function printExceptionErrorPage($exception, $statusCode = 503) {
319
-		http_response_code($statusCode);
320
-		try {
321
-			$request = \OC::$server->getRequest();
322
-			$content = new \OC_Template('', 'exception', 'error', false);
323
-			$content->assign('errorClass', get_class($exception));
324
-			$content->assign('errorMsg', $exception->getMessage());
325
-			$content->assign('errorCode', $exception->getCode());
326
-			$content->assign('file', $exception->getFile());
327
-			$content->assign('line', $exception->getLine());
328
-			$content->assign('trace', $exception->getTraceAsString());
329
-			$content->assign('debugMode', \OC::$server->getSystemConfig()->getValue('debug', false));
330
-			$content->assign('remoteAddr', $request->getRemoteAddress());
331
-			$content->assign('requestID', $request->getId());
332
-			$content->printPage();
333
-		} catch (\Exception $e) {
334
-			try {
335
-				$logger = \OC::$server->getLogger();
336
-				$logger->logException($exception, ['app' => 'core']);
337
-				$logger->logException($e, ['app' => 'core']);
338
-			} catch (Throwable $e) {
339
-				// no way to log it properly - but to avoid a white page of death we send some output
340
-				header('Content-Type: text/plain; charset=utf-8');
341
-				print("Internal Server Error\n\n");
342
-				print("The server encountered an internal error and was unable to complete your request.\n");
343
-				print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n");
344
-				print("More details can be found in the server log.\n");
345
-
346
-				// and then throw it again to log it at least to the web server error log
347
-				throw $e;
348
-			}
349
-
350
-			header('Content-Type: text/plain; charset=utf-8');
351
-			print("Internal Server Error\n\n");
352
-			print("The server encountered an internal error and was unable to complete your request.\n");
353
-			print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n");
354
-			print("More details can be found in the server log.\n");
355
-		}
356
-		die();
357
-	}
51
+    /** @var string */
52
+    private $renderAs; // Create a full page?
53
+
54
+    /** @var string */
55
+    private $path; // The path to the template
56
+
57
+    /** @var array */
58
+    private $headers = []; //custom headers
59
+
60
+    /** @var string */
61
+    protected $app; // app id
62
+
63
+    protected static $initTemplateEngineFirstRun = true;
64
+
65
+    /**
66
+     * Constructor
67
+     *
68
+     * @param string $app app providing the template
69
+     * @param string $name of the template file (without suffix)
70
+     * @param string $renderAs If $renderAs is set, OC_Template will try to
71
+     *                         produce a full page in the according layout. For
72
+     *                         now, $renderAs can be set to "guest", "user" or
73
+     *                         "admin".
74
+     * @param bool $registerCall = true
75
+     */
76
+    public function __construct($app, $name, $renderAs = TemplateResponse::RENDER_AS_BLANK, $registerCall = true) {
77
+        // Read the selected theme from the config file
78
+        self::initTemplateEngine($renderAs);
79
+
80
+        $theme = OC_Util::getTheme();
81
+
82
+        $requestToken = (OC::$server->getSession() && $registerCall) ? \OCP\Util::callRegister() : '';
83
+
84
+        $parts = explode('/', $app); // fix translation when app is something like core/lostpassword
85
+        $l10n = \OC::$server->getL10N($parts[0]);
86
+        /** @var \OCP\Defaults $themeDefaults */
87
+        $themeDefaults = \OC::$server->query(\OCP\Defaults::class);
88
+
89
+        list($path, $template) = $this->findTemplate($theme, $app, $name);
90
+
91
+        // Set the private data
92
+        $this->renderAs = $renderAs;
93
+        $this->path = $path;
94
+        $this->app = $app;
95
+
96
+        parent::__construct($template, $requestToken, $l10n, $themeDefaults);
97
+    }
98
+
99
+    /**
100
+     * @param string $renderAs
101
+     */
102
+    public static function initTemplateEngine($renderAs) {
103
+        if (self::$initTemplateEngineFirstRun) {
104
+
105
+            //apps that started before the template initialization can load their own scripts/styles
106
+            //so to make sure this scripts/styles here are loaded first we use OC_Util::addScript() with $prepend=true
107
+            //meaning the last script/style in this list will be loaded first
108
+            if (\OC::$server->getSystemConfig()->getValue('installed', false) && $renderAs !== TemplateResponse::RENDER_AS_ERROR && !\OCP\Util::needUpgrade()) {
109
+                if (\OC::$server->getConfig()->getAppValue('core', 'backgroundjobs_mode', 'ajax') == 'ajax') {
110
+                    OC_Util::addScript('backgroundjobs', null, true);
111
+                }
112
+            }
113
+            OC_Util::addStyle('css-variables', null, true);
114
+            OC_Util::addStyle('server', null, true);
115
+            OC_Util::addTranslations('core', null, true);
116
+
117
+            if (\OC::$server->getSystemConfig()->getValue('installed', false)) {
118
+                OC_Util::addScript('merged-template-prepend', null, true);
119
+                OC_Util::addScript('dist/files_client', null, true);
120
+                OC_Util::addScript('dist/files_fileinfo', null, true);
121
+            }
122
+            OC_Util::addScript('core', 'dist/main', true);
123
+
124
+            if (\OC::$server->getRequest()->isUserAgent([\OC\AppFramework\Http\Request::USER_AGENT_IE])) {
125
+                // shim for the davclient.js library
126
+                \OCP\Util::addScript('dist/files_iedavclient');
127
+            }
128
+
129
+            self::$initTemplateEngineFirstRun = false;
130
+        }
131
+    }
132
+
133
+
134
+    /**
135
+     * find the template with the given name
136
+     * @param string $name of the template file (without suffix)
137
+     *
138
+     * Will select the template file for the selected theme.
139
+     * Checking all the possible locations.
140
+     * @param string $theme
141
+     * @param string $app
142
+     * @return string[]
143
+     */
144
+    protected function findTemplate($theme, $app, $name) {
145
+        // Check if it is a app template or not.
146
+        if ($app !== '') {
147
+            $dirs = $this->getAppTemplateDirs($theme, $app, OC::$SERVERROOT, OC_App::getAppPath($app));
148
+        } else {
149
+            $dirs = $this->getCoreTemplateDirs($theme, OC::$SERVERROOT);
150
+        }
151
+        $locator = new \OC\Template\TemplateFileLocator($dirs);
152
+        $template = $locator->find($name);
153
+        $path = $locator->getPath();
154
+        return [$path, $template];
155
+    }
156
+
157
+    /**
158
+     * Add a custom element to the header
159
+     * @param string $tag tag name of the element
160
+     * @param array $attributes array of attributes for the element
161
+     * @param string $text the text content for the element. If $text is null then the
162
+     * element will be written as empty element. So use "" to get a closing tag.
163
+     */
164
+    public function addHeader($tag, $attributes, $text=null) {
165
+        $this->headers[]= [
166
+            'tag' => $tag,
167
+            'attributes' => $attributes,
168
+            'text' => $text
169
+        ];
170
+    }
171
+
172
+    /**
173
+     * Process the template
174
+     * @return string
175
+     *
176
+     * This function process the template. If $this->renderAs is set, it
177
+     * will produce a full page.
178
+     */
179
+    public function fetchPage($additionalParams = null) {
180
+        $data = parent::fetchPage($additionalParams);
181
+
182
+        if ($this->renderAs) {
183
+            $page = new TemplateLayout($this->renderAs, $this->app);
184
+
185
+            if (is_array($additionalParams)) {
186
+                foreach ($additionalParams as $key => $value) {
187
+                    $page->assign($key, $value);
188
+                }
189
+            }
190
+
191
+            // Add custom headers
192
+            $headers = '';
193
+            foreach (OC_Util::$headers as $header) {
194
+                $headers .= '<'.\OCP\Util::sanitizeHTML($header['tag']);
195
+                if (strcasecmp($header['tag'], 'script') === 0 && in_array('src', array_map('strtolower', array_keys($header['attributes'])))) {
196
+                    $headers .= ' defer';
197
+                }
198
+                foreach ($header['attributes'] as $name=>$value) {
199
+                    $headers .= ' '.\OCP\Util::sanitizeHTML($name).'="'.\OCP\Util::sanitizeHTML($value).'"';
200
+                }
201
+                if ($header['text'] !== null) {
202
+                    $headers .= '>'.\OCP\Util::sanitizeHTML($header['text']).'</'.\OCP\Util::sanitizeHTML($header['tag']).'>';
203
+                } else {
204
+                    $headers .= '/>';
205
+                }
206
+            }
207
+
208
+            $page->assign('headers', $headers);
209
+
210
+            $page->assign('content', $data);
211
+            return $page->fetchPage($additionalParams);
212
+        }
213
+
214
+        return $data;
215
+    }
216
+
217
+    /**
218
+     * Include template
219
+     *
220
+     * @param string $file
221
+     * @param array|null $additionalParams
222
+     * @return string returns content of included template
223
+     *
224
+     * Includes another template. use <?php echo $this->inc('template'); ?> to
225
+     * do this.
226
+     */
227
+    public function inc($file, $additionalParams = null) {
228
+        return $this->load($this->path.$file.'.php', $additionalParams);
229
+    }
230
+
231
+    /**
232
+     * Shortcut to print a simple page for users
233
+     * @param string $application The application we render the template for
234
+     * @param string $name Name of the template
235
+     * @param array $parameters Parameters for the template
236
+     * @return boolean|null
237
+     */
238
+    public static function printUserPage($application, $name, $parameters = []) {
239
+        $content = new OC_Template($application, $name, "user");
240
+        foreach ($parameters as $key => $value) {
241
+            $content->assign($key, $value);
242
+        }
243
+        print $content->printPage();
244
+    }
245
+
246
+    /**
247
+     * Shortcut to print a simple page for admins
248
+     * @param string $application The application we render the template for
249
+     * @param string $name Name of the template
250
+     * @param array $parameters Parameters for the template
251
+     * @return bool
252
+     */
253
+    public static function printAdminPage($application, $name, $parameters = []) {
254
+        $content = new OC_Template($application, $name, "admin");
255
+        foreach ($parameters as $key => $value) {
256
+            $content->assign($key, $value);
257
+        }
258
+        return $content->printPage();
259
+    }
260
+
261
+    /**
262
+     * Shortcut to print a simple page for guests
263
+     * @param string $application The application we render the template for
264
+     * @param string $name Name of the template
265
+     * @param array|string $parameters Parameters for the template
266
+     * @return bool
267
+     */
268
+    public static function printGuestPage($application, $name, $parameters = []) {
269
+        $content = new OC_Template($application, $name, $name === 'error' ? $name : 'guest');
270
+        foreach ($parameters as $key => $value) {
271
+            $content->assign($key, $value);
272
+        }
273
+        return $content->printPage();
274
+    }
275
+
276
+    /**
277
+     * Print a fatal error page and terminates the script
278
+     * @param string $error_msg The error message to show
279
+     * @param string $hint An optional hint message - needs to be properly escape
280
+     * @param int $statusCode
281
+     * @suppress PhanAccessMethodInternal
282
+     */
283
+    public static function printErrorPage($error_msg, $hint = '', $statusCode = 500) {
284
+        if (\OC::$server->getAppManager()->isEnabledForUser('theming') && !\OC_App::isAppLoaded('theming')) {
285
+            \OC_App::loadApp('theming');
286
+        }
287
+
288
+
289
+        if ($error_msg === $hint) {
290
+            // If the hint is the same as the message there is no need to display it twice.
291
+            $hint = '';
292
+        }
293
+
294
+        http_response_code($statusCode);
295
+        try {
296
+            $content = new \OC_Template('', 'error', 'error', false);
297
+            $errors = [['error' => $error_msg, 'hint' => $hint]];
298
+            $content->assign('errors', $errors);
299
+            $content->printPage();
300
+        } catch (\Exception $e) {
301
+            $logger = \OC::$server->getLogger();
302
+            $logger->error("$error_msg $hint", ['app' => 'core']);
303
+            $logger->logException($e, ['app' => 'core']);
304
+
305
+            header('Content-Type: text/plain; charset=utf-8');
306
+            print("$error_msg $hint");
307
+        }
308
+        die();
309
+    }
310
+
311
+    /**
312
+     * print error page using Exception details
313
+     * @param Exception|Throwable $exception
314
+     * @param int $statusCode
315
+     * @return bool|string
316
+     * @suppress PhanAccessMethodInternal
317
+     */
318
+    public static function printExceptionErrorPage($exception, $statusCode = 503) {
319
+        http_response_code($statusCode);
320
+        try {
321
+            $request = \OC::$server->getRequest();
322
+            $content = new \OC_Template('', 'exception', 'error', false);
323
+            $content->assign('errorClass', get_class($exception));
324
+            $content->assign('errorMsg', $exception->getMessage());
325
+            $content->assign('errorCode', $exception->getCode());
326
+            $content->assign('file', $exception->getFile());
327
+            $content->assign('line', $exception->getLine());
328
+            $content->assign('trace', $exception->getTraceAsString());
329
+            $content->assign('debugMode', \OC::$server->getSystemConfig()->getValue('debug', false));
330
+            $content->assign('remoteAddr', $request->getRemoteAddress());
331
+            $content->assign('requestID', $request->getId());
332
+            $content->printPage();
333
+        } catch (\Exception $e) {
334
+            try {
335
+                $logger = \OC::$server->getLogger();
336
+                $logger->logException($exception, ['app' => 'core']);
337
+                $logger->logException($e, ['app' => 'core']);
338
+            } catch (Throwable $e) {
339
+                // no way to log it properly - but to avoid a white page of death we send some output
340
+                header('Content-Type: text/plain; charset=utf-8');
341
+                print("Internal Server Error\n\n");
342
+                print("The server encountered an internal error and was unable to complete your request.\n");
343
+                print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n");
344
+                print("More details can be found in the server log.\n");
345
+
346
+                // and then throw it again to log it at least to the web server error log
347
+                throw $e;
348
+            }
349
+
350
+            header('Content-Type: text/plain; charset=utf-8');
351
+            print("Internal Server Error\n\n");
352
+            print("The server encountered an internal error and was unable to complete your request.\n");
353
+            print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n");
354
+            print("More details can be found in the server log.\n");
355
+        }
356
+        die();
357
+    }
358 358
 }
Please login to merge, or discard this patch.
lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php 1 patch
Indentation   +56 added lines, -56 removed lines patch added patch discarded remove patch
@@ -46,68 +46,68 @@
 block discarded – undo
46 46
  * @package OC\AppFramework\Middleware\Security
47 47
  */
48 48
 class BruteForceMiddleware extends Middleware {
49
-	/** @var ControllerMethodReflector */
50
-	private $reflector;
51
-	/** @var Throttler */
52
-	private $throttler;
53
-	/** @var IRequest */
54
-	private $request;
49
+    /** @var ControllerMethodReflector */
50
+    private $reflector;
51
+    /** @var Throttler */
52
+    private $throttler;
53
+    /** @var IRequest */
54
+    private $request;
55 55
 
56
-	/**
57
-	 * @param ControllerMethodReflector $controllerMethodReflector
58
-	 * @param Throttler $throttler
59
-	 * @param IRequest $request
60
-	 */
61
-	public function __construct(ControllerMethodReflector $controllerMethodReflector,
62
-								Throttler $throttler,
63
-								IRequest $request) {
64
-		$this->reflector = $controllerMethodReflector;
65
-		$this->throttler = $throttler;
66
-		$this->request = $request;
67
-	}
56
+    /**
57
+     * @param ControllerMethodReflector $controllerMethodReflector
58
+     * @param Throttler $throttler
59
+     * @param IRequest $request
60
+     */
61
+    public function __construct(ControllerMethodReflector $controllerMethodReflector,
62
+                                Throttler $throttler,
63
+                                IRequest $request) {
64
+        $this->reflector = $controllerMethodReflector;
65
+        $this->throttler = $throttler;
66
+        $this->request = $request;
67
+    }
68 68
 
69
-	/**
70
-	 * {@inheritDoc}
71
-	 */
72
-	public function beforeController($controller, $methodName) {
73
-		parent::beforeController($controller, $methodName);
69
+    /**
70
+     * {@inheritDoc}
71
+     */
72
+    public function beforeController($controller, $methodName) {
73
+        parent::beforeController($controller, $methodName);
74 74
 
75
-		if ($this->reflector->hasAnnotation('BruteForceProtection')) {
76
-			$action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
77
-			$this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), $action);
78
-		}
79
-	}
75
+        if ($this->reflector->hasAnnotation('BruteForceProtection')) {
76
+            $action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
77
+            $this->throttler->sleepDelayOrThrowOnMax($this->request->getRemoteAddress(), $action);
78
+        }
79
+    }
80 80
 
81
-	/**
82
-	 * {@inheritDoc}
83
-	 */
84
-	public function afterController($controller, $methodName, Response $response) {
85
-		if ($this->reflector->hasAnnotation('BruteForceProtection') && $response->isThrottled()) {
86
-			$action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
87
-			$ip = $this->request->getRemoteAddress();
88
-			$this->throttler->sleepDelay($ip, $action);
89
-			$this->throttler->registerAttempt($action, $ip, $response->getThrottleMetadata());
90
-		}
81
+    /**
82
+     * {@inheritDoc}
83
+     */
84
+    public function afterController($controller, $methodName, Response $response) {
85
+        if ($this->reflector->hasAnnotation('BruteForceProtection') && $response->isThrottled()) {
86
+            $action = $this->reflector->getAnnotationParameter('BruteForceProtection', 'action');
87
+            $ip = $this->request->getRemoteAddress();
88
+            $this->throttler->sleepDelay($ip, $action);
89
+            $this->throttler->registerAttempt($action, $ip, $response->getThrottleMetadata());
90
+        }
91 91
 
92
-		return parent::afterController($controller, $methodName, $response);
93
-	}
92
+        return parent::afterController($controller, $methodName, $response);
93
+    }
94 94
 
95
-	/**
96
-	 * @param Controller $controller
97
-	 * @param string $methodName
98
-	 * @param \Exception $exception
99
-	 * @throws \Exception
100
-	 * @return Response
101
-	 */
102
-	public function afterException($controller, $methodName, \Exception $exception): Response {
103
-		if ($exception instanceof MaxDelayReached) {
104
-			if ($controller instanceof OCSController) {
105
-				throw new OCSException($exception->getMessage(), Http::STATUS_TOO_MANY_REQUESTS);
106
-			}
95
+    /**
96
+     * @param Controller $controller
97
+     * @param string $methodName
98
+     * @param \Exception $exception
99
+     * @throws \Exception
100
+     * @return Response
101
+     */
102
+    public function afterException($controller, $methodName, \Exception $exception): Response {
103
+        if ($exception instanceof MaxDelayReached) {
104
+            if ($controller instanceof OCSController) {
105
+                throw new OCSException($exception->getMessage(), Http::STATUS_TOO_MANY_REQUESTS);
106
+            }
107 107
 
108
-			return new TooManyRequestsResponse();
109
-		}
108
+            return new TooManyRequestsResponse();
109
+        }
110 110
 
111
-		throw $exception;
112
-	}
111
+        throw $exception;
112
+    }
113 113
 }
Please login to merge, or discard this patch.
lib/private/Security/Bruteforce/Throttler.php 2 patches
Indentation   +295 added lines, -295 removed lines patch added patch discarded remove patch
@@ -52,299 +52,299 @@
 block discarded – undo
52 52
  * @package OC\Security\Bruteforce
53 53
  */
54 54
 class Throttler {
55
-	public const LOGIN_ACTION = 'login';
56
-	public const MAX_DELAY = 25;
57
-	public const MAX_DELAY_MS = 25000; // in milliseconds
58
-	public const MAX_ATTEMPTS = 10;
59
-
60
-	/** @var IDBConnection */
61
-	private $db;
62
-	/** @var ITimeFactory */
63
-	private $timeFactory;
64
-	/** @var ILogger */
65
-	private $logger;
66
-	/** @var IConfig */
67
-	private $config;
68
-
69
-	/**
70
-	 * @param IDBConnection $db
71
-	 * @param ITimeFactory $timeFactory
72
-	 * @param ILogger $logger
73
-	 * @param IConfig $config
74
-	 */
75
-	public function __construct(IDBConnection $db,
76
-								ITimeFactory $timeFactory,
77
-								ILogger $logger,
78
-								IConfig $config) {
79
-		$this->db = $db;
80
-		$this->timeFactory = $timeFactory;
81
-		$this->logger = $logger;
82
-		$this->config = $config;
83
-	}
84
-
85
-	/**
86
-	 * Convert a number of seconds into the appropriate DateInterval
87
-	 *
88
-	 * @param int $expire
89
-	 * @return \DateInterval
90
-	 */
91
-	private function getCutoff(int $expire): \DateInterval {
92
-		$d1 = new \DateTime();
93
-		$d2 = clone $d1;
94
-		$d2->sub(new \DateInterval('PT' . $expire . 'S'));
95
-		return $d2->diff($d1);
96
-	}
97
-
98
-	/**
99
-	 *  Calculate the cut off timestamp
100
-	 *
101
-	 * @param float $maxAgeHours
102
-	 * @return int
103
-	 */
104
-	private function getCutoffTimestamp(float $maxAgeHours = 12.0): int {
105
-		return (new \DateTime())
106
-			->sub($this->getCutoff((int) ($maxAgeHours * 3600)))
107
-			->getTimestamp();
108
-	}
109
-
110
-	/**
111
-	 * Register a failed attempt to bruteforce a security control
112
-	 *
113
-	 * @param string $action
114
-	 * @param string $ip
115
-	 * @param array $metadata Optional metadata logged to the database
116
-	 * @suppress SqlInjectionChecker
117
-	 */
118
-	public function registerAttempt(string $action,
119
-									string $ip,
120
-									array $metadata = []): void {
121
-		// No need to log if the bruteforce protection is disabled
122
-		if ($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
123
-			return;
124
-		}
125
-
126
-		$ipAddress = new IpAddress($ip);
127
-		$values = [
128
-			'action' => $action,
129
-			'occurred' => $this->timeFactory->getTime(),
130
-			'ip' => (string)$ipAddress,
131
-			'subnet' => $ipAddress->getSubnet(),
132
-			'metadata' => json_encode($metadata),
133
-		];
134
-
135
-		$this->logger->notice(
136
-			sprintf(
137
-				'Bruteforce attempt from "%s" detected for action "%s".',
138
-				$ip,
139
-				$action
140
-			),
141
-			[
142
-				'app' => 'core',
143
-			]
144
-		);
145
-
146
-		$qb = $this->db->getQueryBuilder();
147
-		$qb->insert('bruteforce_attempts');
148
-		foreach ($values as $column => $value) {
149
-			$qb->setValue($column, $qb->createNamedParameter($value));
150
-		}
151
-		$qb->execute();
152
-	}
153
-
154
-	/**
155
-	 * Check if the IP is whitelisted
156
-	 *
157
-	 * @param string $ip
158
-	 * @return bool
159
-	 */
160
-	private function isIPWhitelisted(string $ip): bool {
161
-		if ($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
162
-			return true;
163
-		}
164
-
165
-		$keys = $this->config->getAppKeys('bruteForce');
166
-		$keys = array_filter($keys, function ($key) {
167
-			return 0 === strpos($key, 'whitelist_');
168
-		});
169
-
170
-		if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
171
-			$type = 4;
172
-		} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
173
-			$type = 6;
174
-		} else {
175
-			return false;
176
-		}
177
-
178
-		$ip = inet_pton($ip);
179
-
180
-		foreach ($keys as $key) {
181
-			$cidr = $this->config->getAppValue('bruteForce', $key, null);
182
-
183
-			$cx = explode('/', $cidr);
184
-			$addr = $cx[0];
185
-			$mask = (int)$cx[1];
186
-
187
-			// Do not compare ipv4 to ipv6
188
-			if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ||
189
-				($type === 6 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))) {
190
-				continue;
191
-			}
192
-
193
-			$addr = inet_pton($addr);
194
-
195
-			$valid = true;
196
-			for ($i = 0; $i < $mask; $i++) {
197
-				$part = ord($addr[(int)($i/8)]);
198
-				$orig = ord($ip[(int)($i/8)]);
199
-
200
-				$bitmask = 1 << (7 - ($i % 8));
201
-
202
-				$part = $part & $bitmask;
203
-				$orig = $orig & $bitmask;
204
-
205
-				if ($part !== $orig) {
206
-					$valid = false;
207
-					break;
208
-				}
209
-			}
210
-
211
-			if ($valid === true) {
212
-				return true;
213
-			}
214
-		}
215
-
216
-		return false;
217
-	}
218
-
219
-	/**
220
-	 * Get the throttling delay (in milliseconds)
221
-	 *
222
-	 * @param string $ip
223
-	 * @param string $action optionally filter by action
224
-	 * @param float $maxAgeHours
225
-	 * @return int
226
-	 */
227
-	public function getAttempts(string $ip, string $action = '', float $maxAgeHours = 12): int {
228
-		$ipAddress = new IpAddress($ip);
229
-		if ($this->isIPWhitelisted((string)$ipAddress)) {
230
-			return 0;
231
-		}
232
-
233
-		$cutoffTime = $this->getCutoffTimestamp($maxAgeHours);
234
-
235
-		$qb = $this->db->getQueryBuilder();
236
-		$qb->select($qb->func()->count('*', 'attempts'))
237
-			->from('bruteforce_attempts')
238
-			->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
239
-			->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())));
240
-
241
-		if ($action !== '') {
242
-			$qb->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)));
243
-		}
244
-
245
-		$result = $qb->execute();
246
-		$row = $result->fetch();
247
-		$result->closeCursor();
248
-
249
-		return (int) $row['attempts'];
250
-	}
251
-
252
-	/**
253
-	 * Get the throttling delay (in milliseconds)
254
-	 *
255
-	 * @param string $ip
256
-	 * @param string $action optionally filter by action
257
-	 * @return int
258
-	 */
259
-	public function getDelay(string $ip, string $action = ''): int {
260
-		$attempts = $this->getAttempts($ip, $action);
261
-		if ($attempts === 0) {
262
-			return 0;
263
-		}
264
-
265
-		$firstDelay = 0.1;
266
-		if ($attempts > self::MAX_ATTEMPTS) {
267
-			// Don't ever overflow. Just assume the maxDelay time:s
268
-			return self::MAX_DELAY_MS;
269
-		}
270
-
271
-		$delay = $firstDelay * 2**$attempts;
272
-		if ($delay > self::MAX_DELAY) {
273
-			return self::MAX_DELAY_MS;
274
-		}
275
-		return (int) \ceil($delay * 1000);
276
-	}
277
-
278
-	/**
279
-	 * Reset the throttling delay for an IP address, action and metadata
280
-	 *
281
-	 * @param string $ip
282
-	 * @param string $action
283
-	 * @param array $metadata
284
-	 */
285
-	public function resetDelay(string $ip, string $action, array $metadata): void {
286
-		$ipAddress = new IpAddress($ip);
287
-		if ($this->isIPWhitelisted((string)$ipAddress)) {
288
-			return;
289
-		}
290
-
291
-		$cutoffTime = $this->getCutoffTimestamp();
292
-
293
-		$qb = $this->db->getQueryBuilder();
294
-		$qb->delete('bruteforce_attempts')
295
-			->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
296
-			->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())))
297
-			->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)))
298
-			->andWhere($qb->expr()->eq('metadata', $qb->createNamedParameter(json_encode($metadata))));
299
-
300
-		$qb->execute();
301
-	}
302
-
303
-	/**
304
-	 * Reset the throttling delay for an IP address
305
-	 *
306
-	 * @param string $ip
307
-	 */
308
-	public function resetDelayForIP($ip) {
309
-		$cutoffTime = $this->getCutoffTimestamp();
310
-
311
-		$qb = $this->db->getQueryBuilder();
312
-		$qb->delete('bruteforce_attempts')
313
-			->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
314
-			->andWhere($qb->expr()->eq('ip', $qb->createNamedParameter($ip)));
315
-
316
-		$qb->execute();
317
-	}
318
-
319
-	/**
320
-	 * Will sleep for the defined amount of time
321
-	 *
322
-	 * @param string $ip
323
-	 * @param string $action optionally filter by action
324
-	 * @return int the time spent sleeping
325
-	 */
326
-	public function sleepDelay(string $ip, string $action = ''): int {
327
-		$delay = $this->getDelay($ip, $action);
328
-		usleep($delay * 1000);
329
-		return $delay;
330
-	}
331
-
332
-	/**
333
-	 * Will sleep for the defined amount of time unless maximum was reached in the last 30 minutes
334
-	 * In this case a "429 Too Many Request" exception is thrown
335
-	 *
336
-	 * @param string $ip
337
-	 * @param string $action optionally filter by action
338
-	 * @return int the time spent sleeping
339
-	 * @throws MaxDelayReached when reached the maximum
340
-	 */
341
-	public function sleepDelayOrThrowOnMax(string $ip, string $action = ''): int {
342
-		$delay = $this->getDelay($ip, $action);
343
-		if (($delay === self::MAX_DELAY_MS) && $this->getAttempts($ip, $action, 0.5) > self::MAX_ATTEMPTS) {
344
-			// If the ip made too many attempts within the last 30 mins we don't execute anymore
345
-			throw new MaxDelayReached('Reached maximum delay');
346
-		}
347
-		usleep($delay * 1000);
348
-		return $delay;
349
-	}
55
+    public const LOGIN_ACTION = 'login';
56
+    public const MAX_DELAY = 25;
57
+    public const MAX_DELAY_MS = 25000; // in milliseconds
58
+    public const MAX_ATTEMPTS = 10;
59
+
60
+    /** @var IDBConnection */
61
+    private $db;
62
+    /** @var ITimeFactory */
63
+    private $timeFactory;
64
+    /** @var ILogger */
65
+    private $logger;
66
+    /** @var IConfig */
67
+    private $config;
68
+
69
+    /**
70
+     * @param IDBConnection $db
71
+     * @param ITimeFactory $timeFactory
72
+     * @param ILogger $logger
73
+     * @param IConfig $config
74
+     */
75
+    public function __construct(IDBConnection $db,
76
+                                ITimeFactory $timeFactory,
77
+                                ILogger $logger,
78
+                                IConfig $config) {
79
+        $this->db = $db;
80
+        $this->timeFactory = $timeFactory;
81
+        $this->logger = $logger;
82
+        $this->config = $config;
83
+    }
84
+
85
+    /**
86
+     * Convert a number of seconds into the appropriate DateInterval
87
+     *
88
+     * @param int $expire
89
+     * @return \DateInterval
90
+     */
91
+    private function getCutoff(int $expire): \DateInterval {
92
+        $d1 = new \DateTime();
93
+        $d2 = clone $d1;
94
+        $d2->sub(new \DateInterval('PT' . $expire . 'S'));
95
+        return $d2->diff($d1);
96
+    }
97
+
98
+    /**
99
+     *  Calculate the cut off timestamp
100
+     *
101
+     * @param float $maxAgeHours
102
+     * @return int
103
+     */
104
+    private function getCutoffTimestamp(float $maxAgeHours = 12.0): int {
105
+        return (new \DateTime())
106
+            ->sub($this->getCutoff((int) ($maxAgeHours * 3600)))
107
+            ->getTimestamp();
108
+    }
109
+
110
+    /**
111
+     * Register a failed attempt to bruteforce a security control
112
+     *
113
+     * @param string $action
114
+     * @param string $ip
115
+     * @param array $metadata Optional metadata logged to the database
116
+     * @suppress SqlInjectionChecker
117
+     */
118
+    public function registerAttempt(string $action,
119
+                                    string $ip,
120
+                                    array $metadata = []): void {
121
+        // No need to log if the bruteforce protection is disabled
122
+        if ($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
123
+            return;
124
+        }
125
+
126
+        $ipAddress = new IpAddress($ip);
127
+        $values = [
128
+            'action' => $action,
129
+            'occurred' => $this->timeFactory->getTime(),
130
+            'ip' => (string)$ipAddress,
131
+            'subnet' => $ipAddress->getSubnet(),
132
+            'metadata' => json_encode($metadata),
133
+        ];
134
+
135
+        $this->logger->notice(
136
+            sprintf(
137
+                'Bruteforce attempt from "%s" detected for action "%s".',
138
+                $ip,
139
+                $action
140
+            ),
141
+            [
142
+                'app' => 'core',
143
+            ]
144
+        );
145
+
146
+        $qb = $this->db->getQueryBuilder();
147
+        $qb->insert('bruteforce_attempts');
148
+        foreach ($values as $column => $value) {
149
+            $qb->setValue($column, $qb->createNamedParameter($value));
150
+        }
151
+        $qb->execute();
152
+    }
153
+
154
+    /**
155
+     * Check if the IP is whitelisted
156
+     *
157
+     * @param string $ip
158
+     * @return bool
159
+     */
160
+    private function isIPWhitelisted(string $ip): bool {
161
+        if ($this->config->getSystemValue('auth.bruteforce.protection.enabled', true) === false) {
162
+            return true;
163
+        }
164
+
165
+        $keys = $this->config->getAppKeys('bruteForce');
166
+        $keys = array_filter($keys, function ($key) {
167
+            return 0 === strpos($key, 'whitelist_');
168
+        });
169
+
170
+        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
171
+            $type = 4;
172
+        } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
173
+            $type = 6;
174
+        } else {
175
+            return false;
176
+        }
177
+
178
+        $ip = inet_pton($ip);
179
+
180
+        foreach ($keys as $key) {
181
+            $cidr = $this->config->getAppValue('bruteForce', $key, null);
182
+
183
+            $cx = explode('/', $cidr);
184
+            $addr = $cx[0];
185
+            $mask = (int)$cx[1];
186
+
187
+            // Do not compare ipv4 to ipv6
188
+            if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ||
189
+                ($type === 6 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))) {
190
+                continue;
191
+            }
192
+
193
+            $addr = inet_pton($addr);
194
+
195
+            $valid = true;
196
+            for ($i = 0; $i < $mask; $i++) {
197
+                $part = ord($addr[(int)($i/8)]);
198
+                $orig = ord($ip[(int)($i/8)]);
199
+
200
+                $bitmask = 1 << (7 - ($i % 8));
201
+
202
+                $part = $part & $bitmask;
203
+                $orig = $orig & $bitmask;
204
+
205
+                if ($part !== $orig) {
206
+                    $valid = false;
207
+                    break;
208
+                }
209
+            }
210
+
211
+            if ($valid === true) {
212
+                return true;
213
+            }
214
+        }
215
+
216
+        return false;
217
+    }
218
+
219
+    /**
220
+     * Get the throttling delay (in milliseconds)
221
+     *
222
+     * @param string $ip
223
+     * @param string $action optionally filter by action
224
+     * @param float $maxAgeHours
225
+     * @return int
226
+     */
227
+    public function getAttempts(string $ip, string $action = '', float $maxAgeHours = 12): int {
228
+        $ipAddress = new IpAddress($ip);
229
+        if ($this->isIPWhitelisted((string)$ipAddress)) {
230
+            return 0;
231
+        }
232
+
233
+        $cutoffTime = $this->getCutoffTimestamp($maxAgeHours);
234
+
235
+        $qb = $this->db->getQueryBuilder();
236
+        $qb->select($qb->func()->count('*', 'attempts'))
237
+            ->from('bruteforce_attempts')
238
+            ->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
239
+            ->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())));
240
+
241
+        if ($action !== '') {
242
+            $qb->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)));
243
+        }
244
+
245
+        $result = $qb->execute();
246
+        $row = $result->fetch();
247
+        $result->closeCursor();
248
+
249
+        return (int) $row['attempts'];
250
+    }
251
+
252
+    /**
253
+     * Get the throttling delay (in milliseconds)
254
+     *
255
+     * @param string $ip
256
+     * @param string $action optionally filter by action
257
+     * @return int
258
+     */
259
+    public function getDelay(string $ip, string $action = ''): int {
260
+        $attempts = $this->getAttempts($ip, $action);
261
+        if ($attempts === 0) {
262
+            return 0;
263
+        }
264
+
265
+        $firstDelay = 0.1;
266
+        if ($attempts > self::MAX_ATTEMPTS) {
267
+            // Don't ever overflow. Just assume the maxDelay time:s
268
+            return self::MAX_DELAY_MS;
269
+        }
270
+
271
+        $delay = $firstDelay * 2**$attempts;
272
+        if ($delay > self::MAX_DELAY) {
273
+            return self::MAX_DELAY_MS;
274
+        }
275
+        return (int) \ceil($delay * 1000);
276
+    }
277
+
278
+    /**
279
+     * Reset the throttling delay for an IP address, action and metadata
280
+     *
281
+     * @param string $ip
282
+     * @param string $action
283
+     * @param array $metadata
284
+     */
285
+    public function resetDelay(string $ip, string $action, array $metadata): void {
286
+        $ipAddress = new IpAddress($ip);
287
+        if ($this->isIPWhitelisted((string)$ipAddress)) {
288
+            return;
289
+        }
290
+
291
+        $cutoffTime = $this->getCutoffTimestamp();
292
+
293
+        $qb = $this->db->getQueryBuilder();
294
+        $qb->delete('bruteforce_attempts')
295
+            ->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
296
+            ->andWhere($qb->expr()->eq('subnet', $qb->createNamedParameter($ipAddress->getSubnet())))
297
+            ->andWhere($qb->expr()->eq('action', $qb->createNamedParameter($action)))
298
+            ->andWhere($qb->expr()->eq('metadata', $qb->createNamedParameter(json_encode($metadata))));
299
+
300
+        $qb->execute();
301
+    }
302
+
303
+    /**
304
+     * Reset the throttling delay for an IP address
305
+     *
306
+     * @param string $ip
307
+     */
308
+    public function resetDelayForIP($ip) {
309
+        $cutoffTime = $this->getCutoffTimestamp();
310
+
311
+        $qb = $this->db->getQueryBuilder();
312
+        $qb->delete('bruteforce_attempts')
313
+            ->where($qb->expr()->gt('occurred', $qb->createNamedParameter($cutoffTime)))
314
+            ->andWhere($qb->expr()->eq('ip', $qb->createNamedParameter($ip)));
315
+
316
+        $qb->execute();
317
+    }
318
+
319
+    /**
320
+     * Will sleep for the defined amount of time
321
+     *
322
+     * @param string $ip
323
+     * @param string $action optionally filter by action
324
+     * @return int the time spent sleeping
325
+     */
326
+    public function sleepDelay(string $ip, string $action = ''): int {
327
+        $delay = $this->getDelay($ip, $action);
328
+        usleep($delay * 1000);
329
+        return $delay;
330
+    }
331
+
332
+    /**
333
+     * Will sleep for the defined amount of time unless maximum was reached in the last 30 minutes
334
+     * In this case a "429 Too Many Request" exception is thrown
335
+     *
336
+     * @param string $ip
337
+     * @param string $action optionally filter by action
338
+     * @return int the time spent sleeping
339
+     * @throws MaxDelayReached when reached the maximum
340
+     */
341
+    public function sleepDelayOrThrowOnMax(string $ip, string $action = ''): int {
342
+        $delay = $this->getDelay($ip, $action);
343
+        if (($delay === self::MAX_DELAY_MS) && $this->getAttempts($ip, $action, 0.5) > self::MAX_ATTEMPTS) {
344
+            // If the ip made too many attempts within the last 30 mins we don't execute anymore
345
+            throw new MaxDelayReached('Reached maximum delay');
346
+        }
347
+        usleep($delay * 1000);
348
+        return $delay;
349
+    }
350 350
 }
Please login to merge, or discard this patch.
Spacing   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -91,7 +91,7 @@  discard block
 block discarded – undo
91 91
 	private function getCutoff(int $expire): \DateInterval {
92 92
 		$d1 = new \DateTime();
93 93
 		$d2 = clone $d1;
94
-		$d2->sub(new \DateInterval('PT' . $expire . 'S'));
94
+		$d2->sub(new \DateInterval('PT'.$expire.'S'));
95 95
 		return $d2->diff($d1);
96 96
 	}
97 97
 
@@ -127,7 +127,7 @@  discard block
 block discarded – undo
127 127
 		$values = [
128 128
 			'action' => $action,
129 129
 			'occurred' => $this->timeFactory->getTime(),
130
-			'ip' => (string)$ipAddress,
130
+			'ip' => (string) $ipAddress,
131 131
 			'subnet' => $ipAddress->getSubnet(),
132 132
 			'metadata' => json_encode($metadata),
133 133
 		];
@@ -163,7 +163,7 @@  discard block
 block discarded – undo
163 163
 		}
164 164
 
165 165
 		$keys = $this->config->getAppKeys('bruteForce');
166
-		$keys = array_filter($keys, function ($key) {
166
+		$keys = array_filter($keys, function($key) {
167 167
 			return 0 === strpos($key, 'whitelist_');
168 168
 		});
169 169
 
@@ -182,7 +182,7 @@  discard block
 block discarded – undo
182 182
 
183 183
 			$cx = explode('/', $cidr);
184 184
 			$addr = $cx[0];
185
-			$mask = (int)$cx[1];
185
+			$mask = (int) $cx[1];
186 186
 
187 187
 			// Do not compare ipv4 to ipv6
188 188
 			if (($type === 4 && !filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ||
@@ -194,8 +194,8 @@  discard block
 block discarded – undo
194 194
 
195 195
 			$valid = true;
196 196
 			for ($i = 0; $i < $mask; $i++) {
197
-				$part = ord($addr[(int)($i/8)]);
198
-				$orig = ord($ip[(int)($i/8)]);
197
+				$part = ord($addr[(int) ($i / 8)]);
198
+				$orig = ord($ip[(int) ($i / 8)]);
199 199
 
200 200
 				$bitmask = 1 << (7 - ($i % 8));
201 201
 
@@ -226,7 +226,7 @@  discard block
 block discarded – undo
226 226
 	 */
227 227
 	public function getAttempts(string $ip, string $action = '', float $maxAgeHours = 12): int {
228 228
 		$ipAddress = new IpAddress($ip);
229
-		if ($this->isIPWhitelisted((string)$ipAddress)) {
229
+		if ($this->isIPWhitelisted((string) $ipAddress)) {
230 230
 			return 0;
231 231
 		}
232 232
 
@@ -284,7 +284,7 @@  discard block
 block discarded – undo
284 284
 	 */
285 285
 	public function resetDelay(string $ip, string $action, array $metadata): void {
286 286
 		$ipAddress = new IpAddress($ip);
287
-		if ($this->isIPWhitelisted((string)$ipAddress)) {
287
+		if ($this->isIPWhitelisted((string) $ipAddress)) {
288 288
 			return;
289 289
 		}
290 290
 
Please login to merge, or discard this patch.