migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Security\Bruteforce\Backend;
use OCP\IDBConnection;
class DatabaseBackend implements IBackend {
private const TABLE_NAME = 'bruteforce_attempts';
public function __construct(
private IDBConnection $db,
) {
}
/**
* {@inheritDoc}
*/
public function getAttempts(
string $ipSubnet,
int $maxAgeTimestamp,
?string $action = null,
?array $metadata = null,
): int {
$query = $this->db->getQueryBuilder();
$query->select($query->func()->count('*', 'attempts'))
->from(self::TABLE_NAME)
->where($query->expr()->gt('occurred', $query->createNamedParameter($maxAgeTimestamp)))
->andWhere($query->expr()->eq('subnet', $query->createNamedParameter($ipSubnet)));
if ($action !== null) {
$query->andWhere($query->expr()->eq('action', $query->createNamedParameter($action)));
if ($metadata !== null) {
$query->andWhere($query->expr()->eq('metadata', $query->createNamedParameter(json_encode($metadata))));
}
}
$result = $query->executeQuery();
$row = $result->fetch();
$result->closeCursor();
return (int) $row['attempts'];
}
/**
* {@inheritDoc}
*/
public function resetAttempts(
string $ipSubnet,
?string $action = null,
?array $metadata = null,
): void {
$query = $this->db->getQueryBuilder();
$query->delete(self::TABLE_NAME)
->where($query->expr()->eq('subnet', $query->createNamedParameter($ipSubnet)));
if ($action !== null) {
$query->andWhere($query->expr()->eq('action', $query->createNamedParameter($action)));
if ($metadata !== null) {
$query->andWhere($query->expr()->eq('metadata', $query->createNamedParameter(json_encode($metadata))));
}
}
$query->executeStatement();
}
/**
* {@inheritDoc}
*/
public function registerAttempt(
string $ip,
string $ipSubnet,
int $timestamp,
string $action,
array $metadata = [],
): void {
$values = [
'ip' => $ip,
'subnet' => $ipSubnet,
'action' => $action,
'metadata' => json_encode($metadata),
'occurred' => $timestamp,
];
$qb = $this->db->getQueryBuilder();
$qb->insert(self::TABLE_NAME);
foreach ($values as $column => $value) {
$qb->setValue($column, $qb->createNamedParameter($value));
}
$qb->executeStatement();
}
}
@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Security\Bruteforce\Backend;
/**
* Interface IBackend defines a storage backend for the bruteforce data. It
* should be noted that writing and reading brute force data is an expensive
* operation and one should thus make sure to only use sufficient fast backends.
*/
interface IBackend {
/**
* Gets the number of attempts for the specified subnet (and further filters)
*
* @param string $ipSubnet
* @param int $maxAgeTimestamp
* @param ?string $action Optional action to further limit attempts
* @param ?array $metadata Optional metadata stored to further limit attempts (Only considered when $action is set)
* @return int
* @since 28.0.0
*/
public function getAttempts(
string $ipSubnet,
int $maxAgeTimestamp,
?string $action = null,
?array $metadata = null,
): int;
/**
* Reset the attempts for the specified subnet (and further filters)
*
* @param string $ipSubnet
* @param ?string $action Optional action to further limit attempts
* @param ?array $metadata Optional metadata stored to further limit attempts(Only considered when $action is set)
* @since 28.0.0
*/
public function resetAttempts(
string $ipSubnet,
?string $action = null,
?array $metadata = null,
): void;
/**
* Register a failed attempt to bruteforce a security control
*
* @param string $ip
* @param string $ipSubnet
* @param int $timestamp
* @param string $action
* @param array $metadata Optional metadata stored to further limit attempts when getting
* @since 28.0.0
*/
public function registerAttempt(
string $ip,
string $ipSubnet,
int $timestamp,
string $action,
array $metadata = [],
): void;
}
@@ -0,0 +1,161 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Security\Bruteforce\Backend;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\ICache;
use OCP\ICacheFactory;
class MemoryCacheBackend implements IBackend {
private ICache $cache;
public function __construct(
ICacheFactory $cacheFactory,
private ITimeFactory $timeFactory,
) {
$this->cache = $cacheFactory->createDistributed(__CLASS__);
}
private function hash(
null|string|array $data,
): ?string {
if ($data === null) {
return null;
}
if (!is_string($data)) {
$data = json_encode($data);
}
return hash('sha1', $data);
}
private function getExistingAttempts(string $identifier): array {
$cachedAttempts = $this->cache->get($identifier);
if ($cachedAttempts === null) {
return [];
}
$cachedAttempts = json_decode($cachedAttempts, true);
if (\is_array($cachedAttempts)) {
return $cachedAttempts;
}
return [];
}
/**
* {@inheritDoc}
*/
public function getAttempts(
string $ipSubnet,
int $maxAgeTimestamp,
?string $action = null,
?array $metadata = null,
): int {
$identifier = $this->hash($ipSubnet);
$actionHash = $this->hash($action);
$metadataHash = $this->hash($metadata);
$existingAttempts = $this->getExistingAttempts($identifier);
$count = 0;
foreach ($existingAttempts as $info) {
[$occurredTime, $attemptAction, $attemptMetadata] = explode('#', $info, 3);
if ($action === null || $attemptAction === $actionHash) {
if ($metadata === null || $attemptMetadata === $metadataHash) {
if ($occurredTime > $maxAgeTimestamp) {
$count++;
}
}
}
}
return $count;
}
/**
* {@inheritDoc}
*/
public function resetAttempts(
string $ipSubnet,
?string $action = null,
?array $metadata = null,
): void {
$identifier = $this->hash($ipSubnet);
if ($action === null) {
$this->cache->remove($identifier);
} else {
$actionHash = $this->hash($action);
$metadataHash = $this->hash($metadata);
$existingAttempts = $this->getExistingAttempts($identifier);
$maxAgeTimestamp = $this->timeFactory->getTime() - 12 * 3600;
foreach ($existingAttempts as $key => $info) {
[$occurredTime, $attemptAction, $attemptMetadata] = explode('#', $info, 3);
if ($attemptAction === $actionHash) {
if ($metadata === null || $attemptMetadata === $metadataHash) {
unset($existingAttempts[$key]);
} elseif ($occurredTime < $maxAgeTimestamp) {
unset($existingAttempts[$key]);
}
}
}
if (!empty($existingAttempts)) {
$this->cache->set($identifier, json_encode($existingAttempts), 12 * 3600);
} else {
$this->cache->remove($identifier);
}
}
}
/**
* {@inheritDoc}
*/
public function registerAttempt(
string $ip,
string $ipSubnet,
int $timestamp,
string $action,
array $metadata = [],
): void {
$identifier = $this->hash($ipSubnet);
$existingAttempts = $this->getExistingAttempts($identifier);
$maxAgeTimestamp = $this->timeFactory->getTime() - 12 * 3600;
// Unset all attempts that are already expired
foreach ($existingAttempts as $key => $info) {
[$occurredTime,] = explode('#', $info, 3);
if ($occurredTime < $maxAgeTimestamp) {
unset($existingAttempts[$key]);
}
}
$existingAttempts = array_values($existingAttempts);
// Store the new attempt
$existingAttempts[] = $timestamp . '#' . $this->hash($action) . '#' . $this->hash($metadata);
$this->cache->set($identifier, json_encode($existingAttempts), 12 * 3600);
}
}