Compare commits

..

1 commit

Author SHA1 Message Date
mr❤️🤢
55455c21c5
Merge 5898b9a455 into a3634c19cf 2025-06-25 20:02:40 +00:00
9 changed files with 90 additions and 83 deletions

View file

@ -620,6 +620,10 @@ final class Wall extends VKAPIRequestHandler
return (object) ["post_id" => $post->getVirtualId()];
}
if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->getUser(), "wall.post", false)) {
$this->failTooOften();
}
$anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"];
if ($wallOwner instanceof Club && $from_group == 1 && $signed != 1 && $anon) {
$manager = $wallOwner->getManager($this->getUser());
@ -713,10 +717,6 @@ final class Wall extends VKAPIRequestHandler
$post->setSuggested(1);
}
if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->getUser(), "wall.post")) {
$this->failTooOften();
}
$post->save();
} catch (\LogicException $ex) {
$this->fail(100, "One of the parameters specified was missing or invalid");
@ -730,6 +730,8 @@ final class Wall extends VKAPIRequestHandler
(new WallPostNotification($wallOwner, $post, $this->getUser()))->emit();
}
\openvk\Web\Util\EventRateLimiter::i()->writeEvent("wall.post", $this->getUser(), $wallOwner);
return (object) ["post_id" => $post->getVirtualId()];
}

View file

@ -34,6 +34,7 @@ trait TSubscribable
"target" => $this->getId(),
];
$sub = $ctx->table("subscriptions")->where($data);
if (!($sub->fetch())) {
$ctx->table("subscriptions")->insert($data);

View file

@ -114,6 +114,7 @@ class User extends RowModel
public function getChandlerUser(): ChandlerUser
{
# TODO cache this function
return new ChandlerUser($this->getRecord()->ref("ChandlerUsers", "user"));
}
@ -1739,51 +1740,41 @@ class User extends RowModel
return DatabaseConnection::i()->getContext()->table("blacklist_relations")->where("author", $this->getId())->count();
}
public function getEventCounters(array $list): array
public function recieveEventsData(array $list): array
{
$count_of_keys = sizeof(array_keys($list));
$ev_str = $this->getRecord()->events_counters;
$counters = [];
$values = [];
if (!$ev_str) {
for ($i = 0; $i < sizeof(array_keys($list)); $i++) {
$counters[] = 0;
$values[] = 0;
}
} else {
$counters = unpack("S" . $count_of_keys, base64_decode($ev_str, true));
$keys = array_keys($list);
$values = unpack("S*", base64_decode($ev_str));
}
return [
'counters' => array_combine(array_keys($list), $counters),
'counters' => $values,
'refresh_time' => $this->getRecord()->events_refresh_time,
];
}
public function stateEvents(array $state_list): void
public function stateEvents(array $list): void
{
$pack_str = "";
foreach ($state_list as $item => $id) {
$pack_str .= "S";
}
$this->stateChanges("events_counters", base64_encode(pack($pack_str, ...array_values($state_list))));
if (!$this->getRecord()->events_refresh_time) {
$this->stateChanges("events_refresh_time", time());
}
$this->stateChanges("events_counters", base64_encode(pack("S*", array_values($list))));
}
public function resetEvents(array $list): void
public function resetEvents(array $list, int $restriction_length)
{
$values = [];
foreach ($list as $key => $val) {
$values[$key] = 0;
for ($i = 0; $i < sizeof(array_keys($list)); $i++) {
$values[] = 0;
}
$this->stateEvents($values);
$this->stateChanges("events_refresh_time", time());
$this->stateChanges("events_refresh_time", $restriction_length + time());
$this->save();
}
}

View file

@ -106,7 +106,7 @@ final class GiftsPresenter extends OpenVKPresenter
return;
}
if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->user->identity, "gifts.send")) {
if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->user->identity, "gifts.send", false)) {
$this->flashFail("err", tr("error"), tr("limit_exceed_exception"));
}

View file

@ -63,15 +63,15 @@ final class GroupPresenter extends OpenVKPresenter
if ($_SERVER["REQUEST_METHOD"] === "POST") {
if (!empty($this->postParam("name")) && mb_strlen(trim($this->postParam("name"))) > 0) {
if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->user->identity, "groups.create")) {
$this->flashFail("err", tr("error"), tr("limit_exceed_exception"));
}
$club = new Club();
$club->setName($this->postParam("name"));
$club->setAbout(empty($this->postParam("about")) ? null : $this->postParam("about"));
$club->setOwner($this->user->id);
if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->user->identity, "groups.create")) {
$this->flashFail("err", tr("error"), tr("limit_exceed_exception"));
}
try {
$club->save();
} catch (\PDOException $ex) {
@ -108,12 +108,6 @@ final class GroupPresenter extends OpenVKPresenter
$this->flashFail("err", tr("error"), tr("forbidden"));
}
if (!$club->getSubscriptionStatus($this->user->identity)) {
if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->user->identity, "groups.sub")) {
$this->flashFail("err", tr("error"), tr("limit_exceed_exception"));
}
}
$club->toggleSubscription($this->user->identity);
$this->redirect($club->getURL());

View file

@ -418,12 +418,6 @@ final class UserPresenter extends OpenVKPresenter
if ($this->postParam("act") == "rej") {
$user->changeFlags($this->user->identity, 0b10000000, true);
} else {
if ($user->getSubscriptionStatus($this->user->identity) == \openvk\Web\Models\Entities\User::SUBSCRIPTION_ABSENT) {
if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->user->identity, "friends.outgoing_sub")) {
$this->flashFail("err", tr("error"), tr("limit_exceed_exception"));
}
}
$user->toggleSubscription($this->user->identity);
}

View file

@ -356,10 +356,6 @@ final class WallPresenter extends OpenVKPresenter
$this->flashFail("err", tr("failed_to_publish_post"), tr("post_is_empty_or_too_big"));
}
if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->user->identity, "wall.post")) {
$this->flashFail("err", tr("error"), tr("limit_exceed_exception"));
}
$should_be_suggested = $wall < 0 && !$wallOwner->canBeModifiedBy($this->user->identity) && $wallOwner->getWallType() == 2;
try {
$post = new Post();

View file

@ -13,29 +13,28 @@ class EventRateLimiter
use TSimpleSingleton;
private $config;
private $availableFields;
public function __construct()
{
$this->config = OPENVK_ROOT_CONF["openvk"]["preferences"]["security"]["rateLimits"]["eventsLimit"];
$this->availableFields = array_keys($this->config['list']);
}
public function tryToLimit(?User $user, string $event_type, bool $is_update = true): bool
/*
Checks count of actions for last hours
Uses config path OPENVK_ROOT_CONF["openvk"]["preferences"]["security"]["rateLimits"]["eventsLimit"]
Returns:
true limit has exceed and the action must be restricted
false the action can be performed
*/
public function tryToLimit(?User $user, string $event_type, bool $distinct = true): bool
{
/*
Checks count of actions for last x seconds.
Uses OPENVK_ROOT_CONF["openvk"]["preferences"]["security"]["rateLimits"]["eventsLimit"]
This check should be peformed only after checking other conditions cuz by default it increments counter
Returns:
true limit has exceed and the action must be restricted
false the action can be performed
*/
$isEnabled = $this->config['enable'];
$isIgnoreForAdmins = $this->config['ignoreForAdmins'];
$restrictionTime = $this->config['restrictionTime'];
@ -49,45 +48,75 @@ class EventRateLimiter
return false;
}
$eventsStats = $user->getEventCounters($eventsList);
$limitForThatEvent = $eventsList[$event_type];
$stat = $this->getEvent($event_type, $user);
bdump($stat);
$counters = $eventsStats["counters"];
$refresh_time = $eventsStats["refresh_time"];
$is_restrict_over = $refresh_time < (time() - $restrictionTime);
$event_counter = $counters[$event_type];
$is_restrict_over = $stat["refresh_time"] < time() - $restrictionTime;
if ($refresh_time && $is_restrict_over) {
$user->resetEvents($eventsList);
if ($is_restrict_over) {
$user->resetEvents($eventsList, $restrictionTime);
return false;
}
$is_limit_exceed = $event_counter >= $limitForThatEvent;
$is = $stat["compared"] > $limitForThatEvent;
if (!$is_limit_exceed && $is_update) {
$this->incrementEvent($counters, $event_type, $user);
if ($is === false) {
$this->incrementEvent($event_type, $user);
}
return $is_limit_exceed;
return $is;
}
public function incrementEvent(array $old_values, string $event_type, User $initiator): bool
public function getEvent(string $event_type, User $by_user): array
{
$ev_data = $by_user->recieveEventsData($this->config['list']);
$values = $ev_data['counters'];
$i = 0;
$compared = [];
bdump($values);
foreach ($this->config['list'] as $name => $value) {
bdump($value);
$compared[$name] = $values[$i];
$i += 1;
}
return [
"compared" => $compared,
"refresh_time" => $ev_data["refresh_time"]
];
}
/*
Updates counter for user
*/
public function incrementEvent(string $event_type, User $initiator): bool
{
/*
Updates counter for user
*/
$isEnabled = $this->config['enable'];
$eventsList = $this->config['list'];
$eventsList = OPENVK_ROOT_CONF["openvk"]["preferences"]["security"]["rateLimits"]["eventsLimit"];
if (!$isEnabled) {
return false;
}
$old_values[$event_type] += 1;
$ev_data = $initiator->recieveEventsData($eventsList);
$values = $ev_data['counters'];
$i = 0;
$initiator->stateEvents($old_values);
$initiator->save();
$compared = [];
foreach ($eventsList as $name => $value) {
$compared[$name] = $values[$i];
$i += 1;
}
$compared[$event_type] += 1;
bdump($compared);
$initiator->stateEvents($compared);
return true;
}

View file

@ -50,7 +50,7 @@ openvk:
groups.sub: 50
friends.outgoing_sub: 25
wall.post: 5000
gifts.send: 30
gifts.send: 20
blacklists:
limit: 100
applyToAdmins: true