mirror of
https://github.com/shlinkio/shlink.git
synced 2025-02-20 11:48:27 -06:00
Add new geolocatio-country-code redirect condition type
This commit is contained in:
parent
a444ed0246
commit
781c083c9f
@ -49,7 +49,7 @@
|
|||||||
"shlinkio/shlink-event-dispatcher": "^4.1",
|
"shlinkio/shlink-event-dispatcher": "^4.1",
|
||||||
"shlinkio/shlink-importer": "^5.3.2",
|
"shlinkio/shlink-importer": "^5.3.2",
|
||||||
"shlinkio/shlink-installer": "^9.2",
|
"shlinkio/shlink-installer": "^9.2",
|
||||||
"shlinkio/shlink-ip-geolocation": "^4.1",
|
"shlinkio/shlink-ip-geolocation": "dev-main#fadae5d as 4.2",
|
||||||
"shlinkio/shlink-json": "^1.1",
|
"shlinkio/shlink-json": "^1.1",
|
||||||
"spiral/roadrunner": "^2024.1",
|
"spiral/roadrunner": "^2024.1",
|
||||||
"spiral/roadrunner-cli": "^2.6",
|
"spiral/roadrunner-cli": "^2.6",
|
||||||
|
@ -111,6 +111,9 @@ class RedirectRuleHandler implements RedirectRuleHandlerInterface
|
|||||||
RedirectConditionType::IP_ADDRESS => RedirectCondition::forIpAddress(
|
RedirectConditionType::IP_ADDRESS => RedirectCondition::forIpAddress(
|
||||||
$this->askMandatory('IP address, CIDR block or wildcard-pattern (1.2.*.*)', $io),
|
$this->askMandatory('IP address, CIDR block or wildcard-pattern (1.2.*.*)', $io),
|
||||||
),
|
),
|
||||||
|
RedirectConditionType::GEOLOCATION_COUNTRY_CODE => RedirectCondition::forGeolocationCountryCode(
|
||||||
|
$this->askMandatory('Country code to match?', $io),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
$continue = $io->confirm('Do you want to add another condition?');
|
$continue = $io->confirm('Do you want to add another condition?');
|
||||||
|
@ -9,6 +9,7 @@ use Shlinkio\Shlink\Core\Model\DeviceType;
|
|||||||
use Shlinkio\Shlink\Core\RedirectRule\Model\RedirectConditionType;
|
use Shlinkio\Shlink\Core\RedirectRule\Model\RedirectConditionType;
|
||||||
use Shlinkio\Shlink\Core\RedirectRule\Model\Validation\RedirectRulesInputFilter;
|
use Shlinkio\Shlink\Core\RedirectRule\Model\Validation\RedirectRulesInputFilter;
|
||||||
use Shlinkio\Shlink\Core\Util\IpAddressUtils;
|
use Shlinkio\Shlink\Core\Util\IpAddressUtils;
|
||||||
|
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||||
|
|
||||||
use function Shlinkio\Shlink\Core\acceptLanguageToLocales;
|
use function Shlinkio\Shlink\Core\acceptLanguageToLocales;
|
||||||
use function Shlinkio\Shlink\Core\ArrayUtils\some;
|
use function Shlinkio\Shlink\Core\ArrayUtils\some;
|
||||||
@ -16,7 +17,7 @@ use function Shlinkio\Shlink\Core\ipAddressFromRequest;
|
|||||||
use function Shlinkio\Shlink\Core\normalizeLocale;
|
use function Shlinkio\Shlink\Core\normalizeLocale;
|
||||||
use function Shlinkio\Shlink\Core\splitLocale;
|
use function Shlinkio\Shlink\Core\splitLocale;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
use function strtolower;
|
use function strcasecmp;
|
||||||
use function trim;
|
use function trim;
|
||||||
|
|
||||||
class RedirectCondition extends AbstractEntity implements JsonSerializable
|
class RedirectCondition extends AbstractEntity implements JsonSerializable
|
||||||
@ -52,6 +53,11 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
|
|||||||
return new self(RedirectConditionType::IP_ADDRESS, $ipAddressPattern);
|
return new self(RedirectConditionType::IP_ADDRESS, $ipAddressPattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function forGeolocationCountryCode(string $countryCode): self
|
||||||
|
{
|
||||||
|
return new self(RedirectConditionType::GEOLOCATION_COUNTRY_CODE, $countryCode);
|
||||||
|
}
|
||||||
|
|
||||||
public static function fromRawData(array $rawData): self
|
public static function fromRawData(array $rawData): self
|
||||||
{
|
{
|
||||||
$type = RedirectConditionType::from($rawData[RedirectRulesInputFilter::CONDITION_TYPE]);
|
$type = RedirectConditionType::from($rawData[RedirectRulesInputFilter::CONDITION_TYPE]);
|
||||||
@ -71,6 +77,7 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
|
|||||||
RedirectConditionType::LANGUAGE => $this->matchesLanguage($request),
|
RedirectConditionType::LANGUAGE => $this->matchesLanguage($request),
|
||||||
RedirectConditionType::DEVICE => $this->matchesDevice($request),
|
RedirectConditionType::DEVICE => $this->matchesDevice($request),
|
||||||
RedirectConditionType::IP_ADDRESS => $this->matchesRemoteIpAddress($request),
|
RedirectConditionType::IP_ADDRESS => $this->matchesRemoteIpAddress($request),
|
||||||
|
RedirectConditionType::GEOLOCATION_COUNTRY_CODE => $this->matchesGeolocationCountryCode($request),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +116,7 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
|
|||||||
private function matchesDevice(ServerRequestInterface $request): bool
|
private function matchesDevice(ServerRequestInterface $request): bool
|
||||||
{
|
{
|
||||||
$device = DeviceType::matchFromUserAgent($request->getHeaderLine('User-Agent'));
|
$device = DeviceType::matchFromUserAgent($request->getHeaderLine('User-Agent'));
|
||||||
return $device !== null && $device->value === strtolower($this->matchValue);
|
return $device !== null && $device->value === $this->matchValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function matchesRemoteIpAddress(ServerRequestInterface $request): bool
|
private function matchesRemoteIpAddress(ServerRequestInterface $request): bool
|
||||||
@ -118,6 +125,16 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
|
|||||||
return $remoteAddress !== null && IpAddressUtils::ipAddressMatchesGroups($remoteAddress, [$this->matchValue]);
|
return $remoteAddress !== null && IpAddressUtils::ipAddressMatchesGroups($remoteAddress, [$this->matchValue]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function matchesGeolocationCountryCode(ServerRequestInterface $request): bool
|
||||||
|
{
|
||||||
|
$geolocation = $request->getAttribute(Location::class);
|
||||||
|
if (!($geolocation instanceof Location)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strcasecmp($geolocation->countryCode, $this->matchValue) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
public function jsonSerialize(): array
|
public function jsonSerialize(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -138,6 +155,7 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
|
|||||||
$this->matchValue,
|
$this->matchValue,
|
||||||
),
|
),
|
||||||
RedirectConditionType::IP_ADDRESS => sprintf('IP address matches %s', $this->matchValue),
|
RedirectConditionType::IP_ADDRESS => sprintf('IP address matches %s', $this->matchValue),
|
||||||
|
RedirectConditionType::GEOLOCATION_COUNTRY_CODE => sprintf('country code is %s', $this->matchValue),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,4 +8,5 @@ enum RedirectConditionType: string
|
|||||||
case LANGUAGE = 'language';
|
case LANGUAGE = 'language';
|
||||||
case QUERY_PARAM = 'query-param';
|
case QUERY_PARAM = 'query-param';
|
||||||
case IP_ADDRESS = 'ip-address';
|
case IP_ADDRESS = 'ip-address';
|
||||||
|
case GEOLOCATION_COUNTRY_CODE = 'geolocation-country-code';
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,14 @@
|
|||||||
namespace ShlinkioTest\Shlink\Core\RedirectRule\Entity;
|
namespace ShlinkioTest\Shlink\Core\RedirectRule\Entity;
|
||||||
|
|
||||||
use Laminas\Diactoros\ServerRequestFactory;
|
use Laminas\Diactoros\ServerRequestFactory;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
use PHPUnit\Framework\Attributes\Test;
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
use PHPUnit\Framework\Attributes\TestWith;
|
use PHPUnit\Framework\Attributes\TestWith;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Shlinkio\Shlink\Common\Middleware\IpAddressMiddlewareFactory;
|
use Shlinkio\Shlink\Common\Middleware\IpAddressMiddlewareFactory;
|
||||||
use Shlinkio\Shlink\Core\Model\DeviceType;
|
use Shlinkio\Shlink\Core\Model\DeviceType;
|
||||||
use Shlinkio\Shlink\Core\RedirectRule\Entity\RedirectCondition;
|
use Shlinkio\Shlink\Core\RedirectRule\Entity\RedirectCondition;
|
||||||
|
use Shlinkio\Shlink\IpGeolocation\Model\Location;
|
||||||
|
|
||||||
use const ShlinkioTest\Shlink\ANDROID_USER_AGENT;
|
use const ShlinkioTest\Shlink\ANDROID_USER_AGENT;
|
||||||
use const ShlinkioTest\Shlink\DESKTOP_USER_AGENT;
|
use const ShlinkioTest\Shlink\DESKTOP_USER_AGENT;
|
||||||
@ -93,4 +95,20 @@ class RedirectConditionTest extends TestCase
|
|||||||
|
|
||||||
self::assertEquals($expected, $result);
|
self::assertEquals($expected, $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Test, DataProvider('provideVisits')]
|
||||||
|
public function matchesGeolocationCountryCode(Location|null $location, string $countryCodeToMatch, bool $expected): void
|
||||||
|
{
|
||||||
|
$request = ServerRequestFactory::fromGlobals()->withAttribute(Location::class, $location);
|
||||||
|
$result = RedirectCondition::forGeolocationCountryCode($countryCodeToMatch)->matchesRequest($request);
|
||||||
|
|
||||||
|
self::assertEquals($expected, $result);
|
||||||
|
}
|
||||||
|
public static function provideVisits(): iterable
|
||||||
|
{
|
||||||
|
yield 'no location' => [null, 'US', false];
|
||||||
|
yield 'non-matching location' => [new Location(countryCode: 'ES'), 'US', false];
|
||||||
|
yield 'matching location' => [new Location(countryCode: 'US'), 'US', true];
|
||||||
|
yield 'matching case-insensitive' => [new Location(countryCode: 'US'), 'us', true];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user