You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
133 lines
4.2 KiB
133 lines
4.2 KiB
<?php declare(strict_types=1);
|
|
|
|
namespace BradyMcD\TAATP\URI;
|
|
|
|
use InvalidArgumentException;
|
|
|
|
class Otpauth
|
|
{
|
|
public function __construct(
|
|
public readonly string $userid,
|
|
public readonly string $issuer,
|
|
public readonly string $secret,
|
|
public readonly string $algorithm,
|
|
public readonly int $digits,
|
|
public readonly int $period,
|
|
)
|
|
{
|
|
$this->secret = \rtrim($secret, "=");
|
|
}
|
|
|
|
public static function fromString(string $uri): self
|
|
{
|
|
// This is definitely from writing Scheme.
|
|
$runCheck = function(array $checkArray, $target)
|
|
{
|
|
foreach($checkArray as $c)
|
|
{
|
|
($c[0]($target))? :throw new InvalidArgumentException($c[1]);
|
|
}
|
|
};
|
|
$uriChecks = [
|
|
[ function($arr)
|
|
{ return $arr !== false; },
|
|
$uri . " is not a valid URI."],
|
|
[ function($arr)
|
|
{ return \array_key_exists('scheme', $arr);},
|
|
$uri . " has no scheme."],
|
|
[ function($arr)
|
|
{ return $arr['scheme'] === 'otpauth';},
|
|
$uri . " is not an otpauth:// uri."],
|
|
[ function($arr)
|
|
{ return \array_key_exists('path', $arr);},
|
|
$uri . " isn't tagged for a user or issuer."],
|
|
[ function($arr)
|
|
{ return \array_key_exists('query', $arr);},
|
|
$uri . " lacks query information."]
|
|
];
|
|
|
|
$parsedUri = \parse_url($uri);
|
|
$runCheck($uriChecks, $parsedUri);
|
|
|
|
$queryChecks = [
|
|
[ function($que)
|
|
{ return \array_key_exists('issuer', $que);},
|
|
$parsedUri['query'] . " has no issuer information."],
|
|
[ function($que)
|
|
{ return \array_key_exists('secret', $que);},
|
|
$parsedUri['query'] . "has no secret key."],
|
|
];
|
|
$labelChecks = [
|
|
[ function($lab)
|
|
{ return \count($lab) === 2;},
|
|
$parsedUri['path'] . " doesn't have the correct number of components."]
|
|
];
|
|
|
|
$label = \explode(":", $parsedUri['path']);
|
|
$runCheck($labelChecks, $label);
|
|
|
|
$parsedQuery = [];
|
|
\parse_str($parsedUri['query'], $parsedQuery);
|
|
|
|
$queryDefaults = [
|
|
"algorithm" => "SHA1",
|
|
"period" => "30",
|
|
"digits" => "6",
|
|
"issuer" => \rawurldecode(\ltrim($label[0], "/")),
|
|
];
|
|
$parsedQuery = \array_merge($queryDefaults, $parsedQuery);
|
|
$runCheck($queryChecks, $parsedQuery);
|
|
|
|
$convertFields = function(array $conversion, array &$target) {
|
|
foreach ($conversion as $k => $v)
|
|
{
|
|
$target[$k] = $v($target[$k]);
|
|
}
|
|
};
|
|
$queryConversions = [
|
|
"period" => '\intval',
|
|
"digits" => '\intval',
|
|
"secret" => '\rawurldecode'
|
|
];
|
|
$labelConversions = [
|
|
0 => '\rawurldecode',
|
|
];
|
|
|
|
$convertFields($queryConversions, $parsedQuery);
|
|
$convertFields($labelConversions, $label);
|
|
|
|
// END SCHEMEING
|
|
|
|
\ltrim($label[0], "/") === $parsedQuery['issuer']
|
|
|| throw new InvalidArgumentException($uri . " has mismatching issuer information.\n" .
|
|
\ltrim($label[0], "/") . "\n" .
|
|
$parsedQuery['issuer']);
|
|
|
|
return new self(
|
|
$label[1],
|
|
$parsedQuery['issuer'],
|
|
$parsedQuery['secret'],
|
|
$parsedQuery['algorithm'],
|
|
$parsedQuery['digits'],
|
|
$parsedQuery['period']
|
|
);
|
|
}
|
|
|
|
public function emitStr(): string
|
|
{
|
|
$encodeIssuer = \rawurlencode($this->issuer);
|
|
$label = $encodeIssuer . ":" . $this->userid;
|
|
$issuer = "issuer=" . $encodeIssuer;
|
|
$algo = "algorithm=" . $this->algorithm;
|
|
$digits = "digits=" . $this->digits;
|
|
$period = "period=" . $this->period;
|
|
$secret = "secret=" . \rawurlencode($this->secret);
|
|
$query = \implode("&", [$secret, $issuer, $algo, $period, $digits]);
|
|
|
|
return "otpauth://totp/" . $label . "?" . $query;
|
|
}
|
|
|
|
}
|
|
|
|
?>
|