Compare commits
6 Commits
c362aa1283
...
23232f4e6a
Author | SHA1 | Date | |
---|---|---|---|
23232f4e6a
|
|||
c38f0146dc
|
|||
bbac217aa0
|
|||
ced5a65122
|
|||
52aeedd31e
|
|||
f477401114
|
19
exceptions/MDCoordinateOutOfRange.php
Normal file
19
exceptions/MDCoordinateOutOfRange.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?PHP
|
||||
declare(strict_types = 1);
|
||||
|
||||
/**
|
||||
* Custom exception class for coordinates (longitude, latitude) that are out
|
||||
* of range (-90 - 90 for latitudes, -180 to 180 for longitudes).
|
||||
*/
|
||||
final class MDCoordinateOutOfRange extends MDExpectedException {
|
||||
/**
|
||||
* Error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function errorMessage() {
|
||||
//error message
|
||||
return 'Parameter: <b>' . $this->getMessage() . '</b> is not a valid, numeric values.';
|
||||
|
||||
}
|
||||
}
|
@ -12,8 +12,7 @@ final class MDJailSecurityOptionNotSetException extends Exception {
|
||||
*/
|
||||
public function errorMessage() {
|
||||
//error message
|
||||
$errorMsg = 'A security option of MD_JAIL has not been set: <b>' . $this->getMessage() . '</b>).';
|
||||
return $errorMsg;
|
||||
return 'A security option of MD_JAIL has not been set: <b>' . $this->getMessage() . '</b>).';
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,7 @@ final class MDJsonEncodingFailedException extends Exception {
|
||||
*/
|
||||
public function errorMessage() {
|
||||
//error message
|
||||
$errorMsg = 'Failed to encode JSON data.';
|
||||
return $errorMsg;
|
||||
return 'Failed to encode JSON data.';
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -132,12 +132,10 @@ final class MDFormatter {
|
||||
*/
|
||||
public static function formatMarkdownCodeBlock(string $content, string $language = ''):string {
|
||||
|
||||
$output = '```' . $language . '
|
||||
return '```' . $language . '
|
||||
' . $content . '
|
||||
```' . PHP_EOL;
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -386,11 +386,12 @@ final class MD_STD {
|
||||
} while($running > 0);
|
||||
|
||||
$res = [];
|
||||
foreach($urls as $i => $url) {
|
||||
$keys = \array_keys($urls);
|
||||
foreach($keys as $i){
|
||||
$res[$i] = \curl_multi_getcontent($curl_array[$i]) ?: '';
|
||||
}
|
||||
|
||||
foreach($urls as $i => $url){
|
||||
foreach($keys as $i){
|
||||
\curl_multi_remove_handle($mh, $curl_array[$i]);
|
||||
}
|
||||
\curl_multi_close($mh);
|
||||
@ -579,8 +580,7 @@ final class MD_STD {
|
||||
*/
|
||||
public static function openssl_random_pseudo_bytes(int $length):string {
|
||||
|
||||
$output = \openssl_random_pseudo_bytes($length);
|
||||
return $output;
|
||||
return \openssl_random_pseudo_bytes($length);
|
||||
|
||||
}
|
||||
|
||||
@ -750,8 +750,7 @@ final class MD_STD {
|
||||
*/
|
||||
public static function string_to_color_code(string $str):string {
|
||||
|
||||
$code = \substr(\dechex(\crc32($str)), 0, 6);
|
||||
return $code;
|
||||
return \substr(\dechex(\crc32($str)), 0, 6);
|
||||
|
||||
}
|
||||
|
||||
|
@ -55,18 +55,25 @@ final class MD_STD_CACHE {
|
||||
* Caches and serves a page through redis. Should be called at the start
|
||||
* of the script generating a page.
|
||||
*
|
||||
* @param string $redisKey Key to cache by in redis.
|
||||
* @param integer $expiry Expiration time in seconds.
|
||||
* @param string $redisKey Key to cache by in redis.
|
||||
* @param integer $expiry Expiration time in seconds.
|
||||
* @param Redis|null $redis Redis connection already opened, if one exists.
|
||||
* If this parameter is not provided, a separate
|
||||
* redis connection is opened for this function.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function serve_page_through_redis_cache(string $redisKey, int $expiry = 3600):string {
|
||||
public static function serve_page_through_redis_cache(string $redisKey, int $expiry = 3600, ?Redis $redis = null):string {
|
||||
|
||||
if (PHP_SAPI === 'cli') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$redis = self::open_redis_default();
|
||||
if ($redis === null) {
|
||||
$redis = self::open_redis_default();
|
||||
$closeRedis = true;
|
||||
}
|
||||
else $closeRedis = false;
|
||||
|
||||
if ($redis->ping() !== false) {
|
||||
|
||||
@ -94,7 +101,10 @@ final class MD_STD_CACHE {
|
||||
}
|
||||
|
||||
}
|
||||
$redis->close();
|
||||
|
||||
if ($closeRedis === true) {
|
||||
$redis->close();
|
||||
}
|
||||
|
||||
return '';
|
||||
|
||||
|
@ -295,6 +295,46 @@ final class MD_STD_IN {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a coordinate.
|
||||
*
|
||||
* @param string|integer $input Input string.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public static function validate_longitude(string|int $input):float {
|
||||
|
||||
if (is_string($input)) $output = self::sanitize_float($input);
|
||||
else $output = $input;
|
||||
|
||||
if ($output < -180 || $output > 180) {
|
||||
throw new MDCoordinateOutOfRange("Longitude out of range");
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a coordinate.
|
||||
*
|
||||
* @param string|integer $input Input string.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public static function validate_latitude(string|int $input):float {
|
||||
|
||||
if (is_string($input)) $output = self::sanitize_float($input);
|
||||
else $output = $input;
|
||||
|
||||
if ($output < -90 || $output > 90) {
|
||||
throw new MDCoordinateOutOfRange("Latitude out of range");
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates ISBNs. Empty strings are accepted as well.
|
||||
*
|
||||
|
@ -14,9 +14,9 @@ final class MD_STD_SEC {
|
||||
const REFRESH_TIME_IP = 180; // Time until the comp. with the same IP is cleared. This should be lower than the user-level one, as people working together may be using a common IP.
|
||||
|
||||
const BRUTE_FORCE_DELAY_DEFAULT = 2000; // 2000 microseconds = 2 milliseconds
|
||||
const BRUTE_FORCE_DELAY_MULTIPLIER_COMMON = 1.08;
|
||||
const BRUTE_FORCE_DELAY_MULTIPLIER_PER_USER = 2.8;
|
||||
const BRUTE_FORCE_DELAY_MULTIPLIER_PER_IP = 2;
|
||||
const BRUTE_FORCE_DELAY_MULTIPLIER_COMMON = 1.04;
|
||||
const BRUTE_FORCE_DELAY_MULTIPLIER_PER_USER = 2.0;
|
||||
const BRUTE_FORCE_DELAY_MULTIPLIER_PER_IP = 1.6;
|
||||
|
||||
/**
|
||||
* Function for retrieving the anti-csrf token or generating it if need be.
|
||||
@ -56,14 +56,52 @@ final class MD_STD_SEC {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent brute force attacks by delaying the login .
|
||||
* Computes the delay for preventing brute force attacks.
|
||||
*
|
||||
* @param string $tool_name Identifier of the login.
|
||||
* @param string $username Username to look for.
|
||||
* @param integer $counter_common General counter.
|
||||
* @param integer $counter_by_user Username-specific counter.
|
||||
* @param integer $counter_by_ip IP-specific counter.
|
||||
* @param integer $max Optional script-specific maximum execution time.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public static function computeAntiBruteForceDelay(int $counter_common, int $counter_by_user, int $counter_by_ip, int $max = 0):int {
|
||||
|
||||
$counter_common = min($counter_common, 40);
|
||||
$counter_by_user = min($counter_by_user, 40);
|
||||
$counter_by_ip = min($counter_by_ip, 40);
|
||||
|
||||
// Calculate delay
|
||||
$delay_micoseconds = \abs(\intval(self::BRUTE_FORCE_DELAY_DEFAULT *
|
||||
(self::BRUTE_FORCE_DELAY_MULTIPLIER_COMMON ** $counter_common) *
|
||||
(self::BRUTE_FORCE_DELAY_MULTIPLIER_PER_USER ** $counter_by_user) *
|
||||
(self::BRUTE_FORCE_DELAY_MULTIPLIER_PER_IP ** $counter_by_ip)));
|
||||
|
||||
$max_execution_time = \abs((int)\ini_get("max_execution_time"));
|
||||
if ($max_execution_time !== 0) {
|
||||
$max_execution_microseconds = $max_execution_time * 1000000;
|
||||
$delay_micoseconds = min($delay_micoseconds, \abs($max_execution_microseconds - 1000000));
|
||||
}
|
||||
|
||||
if ($max !== 0 && $max * 1000000 < $delay_micoseconds) {
|
||||
$delay_micoseconds = max($max - 1, 1) * 1000000;
|
||||
}
|
||||
|
||||
return max(0, rand($delay_micoseconds - 100000, $delay_micoseconds));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent brute force attacks by delaying the login.
|
||||
*
|
||||
* @param string $tool_name Identifier of the login.
|
||||
* @param string $username Username to look for.
|
||||
* @param integer $max_execution_time Optional maximum execution time to stay below.
|
||||
* In seconds.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function preventBruteForce(string $tool_name, string $username):bool {
|
||||
public static function preventBruteForce(string $tool_name, string $username, int $max_execution_time = 0):bool {
|
||||
|
||||
// Unstable but working way to get the user's IP. If the IP is falsified,
|
||||
// this can't be found out anyway and security is established by _common.
|
||||
@ -112,18 +150,17 @@ final class MD_STD_SEC {
|
||||
$delay_multiplier_per_user = $loginLog['usr'][$hash_user]['count'];
|
||||
$delay_multiplier_per_ip = $loginLog['ip'][$hash_ip]['count'];
|
||||
|
||||
// Calculate delay
|
||||
$delay_micoseconds = \intval(self::BRUTE_FORCE_DELAY_DEFAULT *
|
||||
(self::BRUTE_FORCE_DELAY_MULTIPLIER_COMMON ** $delay_multiplier_common) *
|
||||
(self::BRUTE_FORCE_DELAY_MULTIPLIER_PER_USER ** $delay_multiplier_per_user) *
|
||||
(self::BRUTE_FORCE_DELAY_MULTIPLIER_PER_IP ** $delay_multiplier_per_ip));
|
||||
$delay_micoseconds = self::computeAntiBruteForceDelay($delay_multiplier_common,
|
||||
$delay_multiplier_per_user,
|
||||
$delay_multiplier_per_ip,
|
||||
$max_execution_time);
|
||||
|
||||
$max_execution_microseconds = \abs((int)\ini_get("max_execution_time")) * 1000000;
|
||||
\error_log("Logged in: " . $username . "; Delay: " . ($delay_micoseconds / 1000000) . " seconds");
|
||||
|
||||
// Sleep
|
||||
\usleep(min($delay_micoseconds, \abs($max_execution_microseconds - 1000000)));
|
||||
\usleep($delay_micoseconds);
|
||||
|
||||
if ($delay_micoseconds > \abs($max_execution_microseconds - 1000000)) {
|
||||
if ($delay_micoseconds > \abs($max_execution_time - 1000000)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
37
tests/MD_STD_SECTest.php
Normal file
37
tests/MD_STD_SECTest.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?PHP
|
||||
/**
|
||||
* Tests for MD_STD_SEC.
|
||||
*
|
||||
* @author Joshua Ramon Enslin <joshua@museum-digital.de>
|
||||
*/
|
||||
declare(strict_types = 1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
require __DIR__ . '/../src/MD_STD_SEC.php';
|
||||
|
||||
/**
|
||||
* Tests for MD_STD_SEC.
|
||||
*/
|
||||
final class MD_STD_SECTest extends TestCase {
|
||||
/**
|
||||
* Function for testing if the page can be opened using invalid values for objektnum.
|
||||
*
|
||||
* @author Joshua Ramon Enslin <joshua@museum-digital.de>
|
||||
* @group MissingInputs
|
||||
* @group SafeForProduction
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testComputeAntiBruteForceDelayDoesNotGoOverMax():void {
|
||||
|
||||
$delay = MD_STD_SEC::computeAntiBruteForceDelay(100, 100, 100);
|
||||
self::assertGreaterThan(0, $delay);
|
||||
# self::assertLessThan(10 * 1000000, $delay); // Smaller than 10 seconds
|
||||
|
||||
$delay_reduced = MD_STD_SEC::computeAntiBruteForceDelay(100, 100, 100, 3);
|
||||
self::assertGreaterThan(0, $delay_reduced);
|
||||
self::assertLessThan(3 * 1000000, $delay_reduced); // Smaller than 10 seconds
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user