mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-06 23:33:13 +08:00
Added option to disable tracking based on IP address patterns
This commit is contained in:
@@ -6,6 +6,10 @@ namespace Shlinkio\Shlink\Core\Options;
|
||||
|
||||
use Laminas\Stdlib\AbstractOptions;
|
||||
|
||||
use function array_key_exists;
|
||||
use function explode;
|
||||
use function is_array;
|
||||
|
||||
class TrackingOptions extends AbstractOptions
|
||||
{
|
||||
private bool $anonymizeRemoteAddr = true;
|
||||
@@ -15,6 +19,7 @@ class TrackingOptions extends AbstractOptions
|
||||
private bool $disableIpTracking = false;
|
||||
private bool $disableReferrerTracking = false;
|
||||
private bool $disableUaTracking = false;
|
||||
private array $disableTrackingFrom = [];
|
||||
|
||||
public function anonymizeRemoteAddr(): bool
|
||||
{
|
||||
@@ -41,6 +46,11 @@ class TrackingOptions extends AbstractOptions
|
||||
return $this->disableTrackParam;
|
||||
}
|
||||
|
||||
public function queryHasDisableTrackParam(array $query): bool
|
||||
{
|
||||
return $this->disableTrackParam !== null && array_key_exists($this->disableTrackParam, $query);
|
||||
}
|
||||
|
||||
protected function setDisableTrackParam(?string $disableTrackParam): void
|
||||
{
|
||||
$this->disableTrackParam = $disableTrackParam;
|
||||
@@ -85,4 +95,23 @@ class TrackingOptions extends AbstractOptions
|
||||
{
|
||||
$this->disableUaTracking = $disableUaTracking;
|
||||
}
|
||||
|
||||
public function disableTrackingFrom(): array
|
||||
{
|
||||
return $this->disableTrackingFrom;
|
||||
}
|
||||
|
||||
public function hasDisableTrackingFrom(): bool
|
||||
{
|
||||
return ! empty($this->disableTrackingFrom);
|
||||
}
|
||||
|
||||
protected function setDisableTrackingFrom(string|array|null $disableTrackingFrom): void
|
||||
{
|
||||
if (is_array($disableTrackingFrom)) {
|
||||
$this->disableTrackingFrom = $disableTrackingFrom;
|
||||
} else {
|
||||
$this->disableTrackingFrom = $disableTrackingFrom === null ? [] : explode(',', $disableTrackingFrom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,21 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\Core\Visit;
|
||||
|
||||
use Fig\Http\Message\RequestMethodInterface;
|
||||
use InvalidArgumentException;
|
||||
use Mezzio\Router\Middleware\ImplicitHeadMiddleware;
|
||||
use PhpIP\IP;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Shlinkio\Shlink\Common\Middleware\IpAddressMiddlewareFactory;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Options\TrackingOptions;
|
||||
|
||||
use function array_key_exists;
|
||||
use function explode;
|
||||
use function Functional\map;
|
||||
use function Functional\some;
|
||||
use function implode;
|
||||
use function str_contains;
|
||||
|
||||
class RequestTracker implements RequestTrackerInterface, RequestMethodInterface
|
||||
{
|
||||
@@ -37,24 +44,63 @@ class RequestTracker implements RequestTrackerInterface, RequestMethodInterface
|
||||
$notFoundType = $request->getAttribute(NotFoundType::class);
|
||||
$visitor = Visitor::fromRequest($request);
|
||||
|
||||
if ($notFoundType?->isBaseUrl()) {
|
||||
$this->visitsTracker->trackBaseUrlVisit($visitor);
|
||||
} elseif ($notFoundType?->isRegularNotFound()) {
|
||||
$this->visitsTracker->trackRegularNotFoundVisit($visitor);
|
||||
} elseif ($notFoundType?->isInvalidShortUrl()) {
|
||||
$this->visitsTracker->trackInvalidShortUrlVisit($visitor);
|
||||
}
|
||||
match (true) { // @phpstan-ignore-line
|
||||
$notFoundType?->isBaseUrl() => $this->visitsTracker->trackBaseUrlVisit($visitor),
|
||||
$notFoundType?->isRegularNotFound() => $this->visitsTracker->trackRegularNotFoundVisit($visitor),
|
||||
$notFoundType?->isInvalidShortUrl() => $this->visitsTracker->trackInvalidShortUrlVisit($visitor),
|
||||
};
|
||||
}
|
||||
|
||||
private function shouldTrackRequest(ServerRequestInterface $request): bool
|
||||
{
|
||||
$query = $request->getQueryParams();
|
||||
$disableTrackParam = $this->trackingOptions->getDisableTrackParam();
|
||||
$forwardedMethod = $request->getAttribute(ImplicitHeadMiddleware::FORWARDED_HTTP_METHOD_ATTRIBUTE);
|
||||
if ($forwardedMethod === self::METHOD_HEAD) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $disableTrackParam === null || ! array_key_exists($disableTrackParam, $query);
|
||||
$remoteAddr = $request->getAttribute(IpAddressMiddlewareFactory::REQUEST_ATTR);
|
||||
if ($this->shouldDisableTrackingFromAddress($remoteAddr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = $request->getQueryParams();
|
||||
return ! $this->trackingOptions->queryHasDisableTrackParam($query);
|
||||
}
|
||||
|
||||
private function shouldDisableTrackingFromAddress(?string $remoteAddr): bool
|
||||
{
|
||||
if ($remoteAddr === null || ! $this->trackingOptions->hasDisableTrackingFrom()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$ip = IP::create($remoteAddr);
|
||||
} catch (InvalidArgumentException) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$remoteAddrParts = explode('.', $remoteAddr);
|
||||
$disableTrackingFrom = $this->trackingOptions->disableTrackingFrom();
|
||||
|
||||
return some($disableTrackingFrom, function (string $value) use ($ip, $remoteAddrParts): bool {
|
||||
try {
|
||||
return match (true) {
|
||||
str_contains($value, '*') => $ip->matches($this->parseValueWithWildcards($value, $remoteAddrParts)),
|
||||
str_contains($value, '/') => $ip->isIn($value),
|
||||
default => $ip->matches($value),
|
||||
};
|
||||
} catch (InvalidArgumentException) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function parseValueWithWildcards(string $value, array $remoteAddrParts): string
|
||||
{
|
||||
// Replace wildcard parts with the corresponding ones from the remote address
|
||||
return implode('.', map(
|
||||
explode('.', $value),
|
||||
fn (string $part, int $index) => $part === '*' ? $remoteAddrParts[$index] : $part,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user