Commit 4b58d024 authored by Dominik Hebeler's avatar Dominik Hebeler
Browse files

allow multiple verificators

parent 24f9f619
......@@ -38,10 +38,9 @@ class HumanVerification extends Controller
}
$captcha = Captcha::create("default", true);
\App\PrometheusExporter::CaptchaShown();
return view('humanverification.captcha')->with('title', 'Bestätigung notwendig')
->with('uid', $human_verification->uid)
->with('id', $human_verification->id)
->with('url', $redirect_url)
->with('correct', $captcha["key"])
->with('image', $captcha["img"])
......@@ -132,30 +131,37 @@ class HumanVerification extends Controller
public static function remove(Request $request)
{
if (!$request->has('mm')) {
if (!$request->has('hv') || !Cache::has($request->input("hv"))) {
abort(404, "Keine Katze gefunden.");
}
$human_verification = \app()->make(ModelsHumanVerification::class);
$hv_data = Cache::get($request->input("hv"));
if ($request->input("mm") === $human_verification->uid) {
$human_verification->verifyUser();
if ($hv_data !== null && is_array($hv_data)) {
foreach ($hv_data as $hv_entry) {
$verificator = $hv_entry["class"]::impersonate($hv_entry["id"], $hv_entry["uid"]);
$verificator->verifyUser();
}
}
return response(hex2bin('89504e470d0a1a0a0000000d494844520000000100000001010300000025db56ca00000003504c5445000000a77a3dda0000000174524e530040e6d8660000000a4944415408d76360000000020001e221bc330000000049454e44ae426082'), 200)
->header('Content-Type', 'image/png');
}
public static function removeGet(Request $request, $mm, $password, $url)
public static function removeGet(Request $request, $hv, $password, $url)
{
$url = \pack("H*", $url);
# If the user is correct and the password is we will delete any entry in the database
$requiredPass = md5($mm . Carbon::NOW()->day . $url . config("metager.metager.proxy.password"));
$human_verification = \app()->make(ModelsHumanVerification::class);
if ($mm === $human_verification->uid && $requiredPass == $password) {
$human_verification->verifyUser();
$requiredPass = md5(Carbon::NOW()->day . $url . config("metager.metager.proxy.password"));
if ($requiredPass == $password && !empty($hv)) {
$hv_data = Cache::get($hv);
if ($hv_data !== null && is_array($hv_data)) {
foreach ($hv_data as $hv_entry) {
$verificator = $hv_entry["class"]::impersonate($hv_entry["id"], $hv_entry["uid"]);
$verificator->verifyUser();
}
}
}
return redirect($url);
......@@ -168,30 +174,31 @@ class HumanVerification extends Controller
return view('humanverification.botOverview')
->with('title', "Bot Overview")
->with('ip', $request->ip())
->with('userList', $human_verification->getUserList())
->with('user', $human_verification->getUser())
->with('verificators', $human_verification->getVerificators())
->with('css', [mix('css/admin/bot/index.css')])
->with('js', [mix('js/admin/bot.js')]);
}
public function botOverviewChange(Request $request)
{
$human_verification = \app()->make(ModelsHumanVerification::class);
$verificator = $request->input("verificator");
$verificator = new $verificator();
if ($request->filled("locked")) {
if (\boolval($request->input("locked"))) {
$human_verification->lockUser();
$verificator->lockUser();
} else {
$human_verification->unlockUser();
$verificator->unlockUser();
}
} elseif ($request->filled("whitelist")) {
if (\boolval($request->input("whitelist"))) {
$human_verification->verifyUser();
$verificator->verifyUser();
} else {
$human_verification->unverifyUser();
$verificator->unverifyUser();
}
} elseif ($request->filled("unusedResultPages")) {
$human_verification->setUnusedResultPage(intval($request->input('unusedResultPages')));
$verificator->setUnusedResultPage(intval($request->input('unusedResultPages')));
}
return redirect('admin/bot');
......
......@@ -40,7 +40,7 @@ class HumanVerification
$value = Cache::get($token);
if (!empty($value) && intval($value) > 0) {
Cache::decrement($token);
Cache::put($token, ($value - 1), now()->addHour());
$should_skip = true;
} else {
// Token is not valid. Remove it
......@@ -75,16 +75,14 @@ class HumanVerification
* Only applies when the user itself is not whitelisted.
* Also applies RefererLock from above
*/
if (!$user->alone && $user->request_count_all_users >= 50 && !$user->isWhiteListed() && $user->not_whitelisted_accounts > $user->whitelisted_accounts) {
$user->lockUser();
}
$user->checkGroupLock();
# If the user is locked we will force a Captcha validation
if ($user->isLocked()) {
\App\Http\Controllers\HumanVerification::logCaptcha($request);
\app()->make(QueryTimer::class)->observeEnd(self::class);
$this->logCaptcha($request); // TODO remove
//return $next($request); // TODO remove
return redirect()->route('captcha_show', ["url" => URL::full()]); // TODO uncomment
}
......
......@@ -465,22 +465,20 @@ class MetaGer
{
# Let's check if we need to implement a redirect for human verification
$human_verification = \app()->make(HumanVerification::class);
$search_settings = \app()->make(SearchSettings::class);
if ($human_verification->getVerificationCount() > 10) {
if (max($human_verification->getVerificationCount()) > 10) {
foreach ($results as $result) {
$link = $result->link;
$day = Carbon::now()->day;
$verification_id = $human_verification->uid;
$pw = md5($verification_id . $day . $link . config("metager.metager.proxy.password"));
$pw = md5($day . $link . config("metager.metager.proxy.password"));
$params = [
'mm' => $verification_id,
'hv' => $human_verification->key,
'pw' => $pw,
"url" => \bin2hex($link)
];
$url = route('humanverification', $params);
$proxyPw = md5($verification_id . $day . $result->proxyLink . config("metager.metager.proxy.password"));
$proxyPw = md5($day . $result->proxyLink . config("metager.metager.proxy.password"));
$params["pw"] = $proxyPw;
$params["url"] = \bin2hex($result->proxyLink);
$proxyUrl = route('humanverification', $params);
......
......@@ -2,255 +2,158 @@
namespace App\Models\Verification;
use App\SearchSettings;
use Cache;
use URL;
class HumanVerification
{
const CACHE_PREFIX = "humanverification";
private $users = [];
private $user = [];
public string $id;
public string $uid;
public bool $alone;
public int $whitelisted_accounts;
public int $not_whitelisted_accounts;
public int $request_count_all_users = 0;
/** @var Verification[] */
private $verificators = array();
public readonly ?string $key;
public function __construct()
{
$request = \request();
$ip = $request->ip();
$this->id = hash("sha1", $ip);
$this->uid = hash("sha1", $ip . $_SERVER["AGENT"] . "uid");
# Get all Users of this IP
$this->users = Cache::get(self::CACHE_PREFIX . "." . $this->id, []);
if ($this->users === null) {
$this->users = [];
}
$this->removeOldUsers();
$this->verificators[] = new IPVerification();
if (empty($this->users[$this->uid])) {
$this->user = [
'uid' => $this->uid,
'id' => $this->id,
'unusedResultPages' => 0,
'whitelist' => false,
'locked' => false,
"expiration" => now()->addWeeks(2),
$this->key = \md5("hv.key." . microtime(true));
$ids = [];
foreach ($this->verificators as $verificator) {
$ids[] = [
"class" => $verificator::class,
"id" => $verificator->id,
"uid" => $verificator->uid,
];
$this->users[$this->uid] = $this->user;
} else {
$this->user = $this->users[$this->uid];
}
Cache::put($this->key, $ids, now()->addMinutes(15));
}
# Lock out everyone in a Bot network
# Find out how many requests this IP has made
$sum = 0;
// Defines if this is the only user using that IP Adress
/**
* Whether or not there are other users in this group
*/
public function isAlone()
{
$alone = true;
$whitelisted_accounts = 0;
$not_whitelisted_accounts = 0;
foreach ($this->users as $uidTmp => $userTmp) {
if (!$userTmp["whitelist"]) {
$not_whitelisted_accounts++;
$sum += $userTmp["unusedResultPages"];
if ($userTmp["uid"] !== $this->uid) {
$alone = false;
}
} else {
$whitelisted_accounts++;
foreach ($this->verificators as $verificator) {
if (!$verificator->alone) {
$alone = false;
break;
}
}
$this->alone = $alone;
$this->request_count_all_users = $sum;
$this->whitelisted_accounts = $whitelisted_accounts;
$this->not_whitelisted_accounts = $not_whitelisted_accounts;
}
function lockUser()
{
$this->user["locked"] = true;
$this->saveUser();
}
function unlockUser()
{
$this->user["locked"] = false;
$this->saveUser();
return $alone;
}
/**
* Returns Whether this user is locked
* Is this user whitelisted
*
* @return bool
* @return boolean
*/
function isLocked()
{
return $this->user["locked"];
}
function saveUser()
public function isWhiteListed()
{
$userList = Cache::get(self::CACHE_PREFIX . "." . $this->id, []);
$expiration_short = now()->addHours(6);
$expiration_long = now()->addWeeks(2);
$cache_expiration = $expiration_short;
// Todo remove setting expiration for all users
// Just added to apply the new expiration policy to all existing entries
// Will not be needed in the future
foreach ($userList as $index => $user) {
if ($user["whitelist"] === true) {
$cache_expiration = $expiration_long;
if ($expiration_long < $user["expiration"]) {
$userList[$index]["expiration"] = $expiration_long;
}
} else if ($expiration_short < $user["expiration"]) {
$userList[$index]["expiration"] = $expiration_short;
$whitelisted = false;
foreach ($this->verificators as $verificator) {
if ($verificator->isWhiteListed()) {
$whitelisted = true;
break;
}
}
if ($this->isWhiteListed() === true) {
$this->user["expiration"] = $expiration_long;
} else {
$this->user["expiration"] = $expiration_short;
}
$userList[$this->uid] = $this->user;
Cache::put(self::CACHE_PREFIX . "." . $this->id, $userList, $cache_expiration);
$this->users = $userList;
return $whitelisted;
}
/**
* Deletes the data for this user
* Checks whether there are many not whitelisted accounts which would lead to a captcha
* for new users
*
* @return boolean
*/
private function deleteUser()
public function checkGroupLock()
{
$userList = Cache::get(self::CACHE_PREFIX . "." . $this->id, []);
if (sizeof($userList) === 1) {
// This user is the only one for this IP
Cache::forget(self::CACHE_PREFIX . "." . $this->id);
} else {
$new_user_list = [];
$expiration = now()->addHours(72);
foreach ($userList as $user) {
if ($user["uid"] !== $this->uid) {
$new_user_list[] = $user;
if ($user["whitelist"]) {
$expiration = now()->addWeeks(2);
}
}
foreach ($this->verificators as $verificator) {
if (!$verificator->alone && $verificator->request_count_all_users >= 50 && !$verificator->isWhiteListed() && $verificator->not_whitelisted_accounts > $verificator->whitelisted_accounts) {
$verificator->lockUser();
return true;
}
Cache::put(self::CACHE_PREFIX . "." . $this->id, $new_user_list, $expiration);
}
return false;
}
/**
* Function is called for a user on specific actions
* It will either delete the data for this user or put him on a whitelist and reset his counter
* Returns true if one of the verificators is locked
*
* @return boolean
*/
public function verifyUser()
public function isLocked()
{
# Check if we have to whitelist the user or if we can simply delete the data
if ($this->alone === false) {
# Whitelist
$this->user["whitelist"] = true;
$this->user["unusedResultPages"] = 0;
$this->saveUser();
} else {
$this->deleteUser();
foreach ($this->verificators as $verificator) {
if ($verificator->isLocked()) {
return true;
}
}
return false;
}
public function unverifyUser()
public function addQuery()
{
$this->user["whitelist"] = false;
$this->saveUser();
}
public function setUnusedResultPage($unusedResultPages)
{
$this->user["unusedResultPages"] = $unusedResultPages;
$this->saveUser();
}
public function isWhiteListed()
{
return $this->user["whitelist"];
}
public function setWhiteListed(bool $whitelisted)
{
return $this->user["whitelist"] = $whitelisted;
$this->saveUser();
foreach ($this->verificators as $verificator) {
$verificator->addQuery();
}
}
function addQuery()
/**
* Reports the highest verification count
*
* @return int[]
*/
public function getVerificationCount()
{
$this->user["unusedResultPages"]++;
if ($this->alone || $this->user["whitelist"]) {
# This IP doesn't need verification yet
# The user currently isn't locked
# We have different security gates:
# 50 and then every 25 => Captcha validated Result Pages
# If the user shows activity on our result page the counter will be deleted
if ($this->user["unusedResultPages"] === 50 || ($this->user["unusedResultPages"] > 50 && $this->user["unusedResultPages"] % 25 === 0)) {
$this->lockUser();
}
$count = array();
foreach ($this->verificators as $verificator) {
$count[] = $verificator->getVerificationCount();
}
$this->saveUser();
return $count;
}
function removeOldUsers()
/**
* Returns the UIDS for all verificators
*
* @return string[]
*/
public function getUids()
{
$newUserlist = [];
$now = now();
$changed = false;
foreach ($this->users as $uid => $user) {
$id = $user["id"];
if ($now < $user["expiration"]) {
$newUserlist[$user["uid"]] = $user;
} else {
$changed = true;
}
}
if ($changed) {
Cache::put(self::CACHE_PREFIX . "." . $user["id"], $newUserlist, now()->addWeeks(2));
$this->users = $newUserlist;
$uids = array();
foreach ($this->verificators as $verificator) {
$uids[] = $verificator->uid;
}
$this->users = $newUserlist;
return $uids;
}
public function getVerificationCount()
/**
* @return Verificator[]
*/
public function getVerificators()
{
return $this->user["unusedResultPages"];
return $this->verificators;
}
/**
* Returns the number of users associated to this IP
*/
public function getUserCount()
public function getUid()
{
return sizeof($this->users);
$uid = "";
foreach ($this->verificators as $verificator) {
$uid .= $verificator->uid;
}
$uid = \sha1($uid);
return $uid;
}
public function getUser()
public function unlockUser()
{
return $this->user;
foreach ($this->verificators as $verificator) {
$verificator->unlockUser();
}
}
public function getUserList()
public function verifyUser()
{
return $this->users;
foreach ($this->verificators as $verificator) {
$verificator->verifyUser();
}
}
}
<?php
namespace App\Models\Verification;
class IPVerification extends Verification
{
public function __construct($id = null, $uid = null)
{
$this->cache_prefix = "humanverification.ip";
$request = \request();
$ip = $request->ip();
if (empty($id) || empty($uid)) {
$id = hash("sha1", $ip);
$uid = hash("sha1", $ip . $_SERVER["AGENT"] . "uid");
}
parent::__construct($id, $uid);
}
public static function impersonate($id, $uid)
{
return new IPVerification($id, $uid);
}
}
<?php
namespace App\Models\Verification;
use Cache;
abstract class Verification
{
protected $cache_prefix = "humanverification";
private $users = [];
private $user = [];
public readonly ?string $id;
public readonly ?string $uid;
public bool $alone;
public int $whitelisted_accounts;
public int $not_whitelisted_accounts;
public int $request_count_all_users = 0;
/**
* @param string $id The Group ID for the verifier
* @param string $uid The User ID for the verifier
*/
public function __construct($id, $uid)
{
$this->id = $id;
$this->uid = $uid;
# Get all Users of this IP
$this->users = Cache::get($this->cache_prefix . "." . $this->id, []);
if ($this->users === null) {
$this->users = [];
}
$this->removeOldUsers();
if (empty($this->users[$this->uid])) {
$this->user = [
'uid' => $this->uid,
'id' => $this->id,
'unusedResultPages' => 0,
'whitelist' => false,
'locked' => false,
"expiration" => now()->addWeeks(2),
];
$this->users[$this->uid] = $this->user;
} else {
$this->user = $this->users[$this->uid];
}
# Lock out everyone in a Bot network
# Find out how many requests this IP has made
$sum = 0;