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

<?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;
}
}
?>