Commit 62fe73a5 authored by Ludwig Ruderstaller's avatar Ludwig Ruderstaller
Browse files

Adding Cryptokeys Endpoint

parent 4effa3c1
Pipeline #17867 passed with stage
in 2 minutes and 16 seconds
......@@ -73,7 +73,11 @@ class Client
*/
public function call($payload = null, $uri, $hydrationClass = null, $isList = false, $method = 'GET', array $queryParams = [])
{
$uri = sprintf('%s/%s?%s', $this->apiUri, $uri, http_build_query($queryParams));
if (count($queryParams) > 0) {
$uri = sprintf('%s/%s?%s', $this->apiUri, $uri, http_build_query($queryParams));
} else {
$uri = sprintf('%s/%s', $this->apiUri, $uri);
}
$uri = rtrim($uri, '/');
$request = new Request($method, $uri, [
......
......@@ -32,13 +32,12 @@ abstract class AbstractEndpoint
->getValidator();
}
public function validateEntity($entity, $groups = null)
public function validateEntity($entity, $groups = null): bool
{
$violations = $this->validator->validate($entity, null, $groups);
if (count($violations) > 0) {
if (\count($violations) > 0) {
throw new ValidationException(
sprintf('Entity %s does not validate to spezification', get_class($entity)),
sprintf('Entity %s does not validate to spezification', \get_class($entity)),
0,
null,
$violations
......
<?php
/*
* This file is part of the CwdPowerDNS Client
*
* (c) 2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\PowerDNSClient\Endpoints;
use Cwd\PowerDNSClient\Client;
use Cwd\PowerDNSClient\Model\Cryptokey;
class CryptokeysEndpoint extends AbstractEndpoint
{
use UriHelperTrait;
private const ENDPOINT_LIST = 'servers/%s/zones/%s/cryptokeys';
private const ENDPOINT_ELEMENT = 'servers/%s/zones/%s/cryptokeys/%s';
private $zoneId;
public function __construct(Client $client, $defaultServerId, $zoneId)
{
$this->zoneId = $zoneId;
parent::__construct($client, $defaultServerId);
}
public function all($hydrationClass = Cryptokey::class): array
{
$uri = sprintf(self::ENDPOINT_LIST, $this->defaultServerId, $this->zoneId);
return $this->getClient()->call(null, $uri, $hydrationClass, true, 'GET');
}
public function get($cryptokey, $hydrationClass = Cryptokey::class): ?Cryptokey
{
$cryptokeyId = $cryptokey;
if ($cryptokey instanceof Cryptokey) {
$cryptokeyId = $cryptokey->getId();
}
return $this->getClient()->call(null, $this->uriHelper($cryptokeyId), $hydrationClass, false, 'GET');
}
public function create(Cryptokey $cryptokey, $hydrationClass = Cryptokey::class): ?Cryptokey
{
$this->validateEntity($cryptokey, ['CREATE']);
$uri = sprintf(self::ENDPOINT_LIST, $this->defaultServerId, $this->zoneId);
$payload = $this->getClient()->getSerializer()->serialize($cryptokey, 'json');
return $this->getClient()->call($payload, $uri, $hydrationClass, false, 'POST');
}
public function activate($cryptokey, $lacyLoad = true, $hydrationClass = Cryptokey::class): ?Cryptokey
{
return $this->setStatus(true, $cryptokey, $lacyLoad, $hydrationClass);
}
public function deactivate($cryptokey, $lacyLoad = true, $hydrationClass = Cryptokey::class): ?Cryptokey
{
return $this->setStatus(false, $cryptokey, $lacyLoad, $hydrationClass);
}
private function setStatus(bool $active, $cryptokey, $lacyLoad = true, $hydrationClass = Cryptokey::class): ?Cryptokey
{
$cryptokeyId = $cryptokey;
if ($cryptokey instanceof Cryptokey) {
$cryptokeyId = $cryptokey->getId();
}
$payload = $this->getClient()->getSerializer()->serialize(['active' => $active], 'json');
$this->getClient()->call($payload, $this->uriHelper($cryptokeyId), null, false, 'PUT');
if ($lacyLoad) {
return $this->get($cryptokeyId, $hydrationClass);
}
return null;
}
public function delete($cryptokey): void
{
$cryptokeyId = $cryptokey;
if ($cryptokey instanceof Cryptokey) {
$cryptokeyId = $cryptokey->getId();
}
$this->getClient()->call(null, $this->uriHelper($cryptokeyId), null, false, 'DELETE');
}
}
......@@ -18,8 +18,10 @@ use Cwd\PowerDNSClient\Model\Metadata;
class MetadataEndpoint extends AbstractEndpoint
{
const ENDPOINT_LIST = 'servers/%s/zones/%s/metadata';
const ENDPOINT_ELEMENT = 'servers/%s/zones/%s/metadata/%s';
use UriHelperTrait;
private const ENDPOINT_LIST = 'servers/%s/zones/%s/metadata';
private const ENDPOINT_ELEMENT = 'servers/%s/zones/%s/metadata/%s';
private $zoneId;
......@@ -45,12 +47,13 @@ class MetadataEndpoint extends AbstractEndpoint
/**
* @param string $kind
* @param string $hydrationClass
*
* @return Metadata
*
* @throws \Http\Client\Exception
*/
public function get(string $kind, $hydrationClass = Metadata::class): ?Metadata
public function get(string $kind, string $hydrationClass = Metadata::class): ?Metadata
{
return $this->getClient()->call(null, $this->uriHelper($kind), $hydrationClass, false, 'GET');
}
......@@ -82,12 +85,13 @@ class MetadataEndpoint extends AbstractEndpoint
/**
* @param Metadata $metadata
* @param bool $lacyLoad
* @param string $hydrationClass
*
* @return Metadata|null
*
* @throws \Http\Client\Exception
*/
public function update(Metadata $metadata, $lacyLoad = true, $hydrationClass = Metadata::class): ?Metadata
public function update(Metadata $metadata, $lacyLoad = true, string $hydrationClass = Metadata::class): ?Metadata
{
$this->validateEntity($metadata, ['UPDATE']);
$payload = $this->getClient()->getSerializer()->serialize($metadata, 'json');
......@@ -114,9 +118,4 @@ class MetadataEndpoint extends AbstractEndpoint
}
$this->getClient()->call(null, $this->uriHelper($kind), null, false, 'DELETE');
}
private function uriHelper($kind): string
{
return sprintf(self::ENDPOINT_ELEMENT, $this->defaultServerId, $this->zoneId, $kind);
}
}
......@@ -17,8 +17,15 @@ use Cwd\PowerDNSClient\Model\Server;
class ServersEndpoint extends AbstractEndpoint
{
const ENDPOINT = 'servers/%s';
private const ENDPOINT = 'servers/%s';
/**
* @param null|string $serverId
*
* @return Server
*
* @throws \Http\Client\Exception
*/
public function get(?string $serverId = null): Server
{
if (null === $serverId) {
......@@ -29,7 +36,7 @@ class ServersEndpoint extends AbstractEndpoint
}
/**
* @return Servers[]
* @return Server[]
*
* @throws \Http\Client\Exception
*/
......
<?php
/*
* This file is part of the CwdPowerDNS Client
*
* (c) 2018 cwd.at GmbH <office@cwd.at>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Cwd\PowerDNSClient\Endpoints;
trait UriHelperTrait
{
protected function uriHelper($kind): string
{
return sprintf(self::ENDPOINT_ELEMENT, $this->defaultServerId, $this->zoneId, $kind);
}
}
......@@ -17,15 +17,21 @@ use Cwd\PowerDNSClient\Model\Zone;
class ZonesEndpoint extends AbstractEndpoint
{
const ENDPOINT_LIST = 'servers/%s/zones';
const ENDPOINT_ELEMENT = 'servers/%s/zones/%s';
protected const ENDPOINT_LIST = 'servers/%s/zones';
protected const ENDPOINT_ELEMENT = 'servers/%s/zones/%s';
public function update(Zone $zone, $lazyLoad = false, $hydrationClass = Zone::class): ?Zone
/**
* @param Zone $zone
* @param bool $lazyLoad
* @param string $hydrationClass
*
* @return Zone|null
*
* @throws \Http\Client\Exception
*/
public function update(Zone $zone, $lazyLoad = false, string $hydrationClass = Zone::class): ?Zone
{
$this->validateEntity($zone, ['UPDATE']);
//$zone->setRrsets([]);
$payload = $this->getClient()->getSerializer()->serialize($zone, 'json', ['groups' => ['REPLACE']]);
$this->getClient()->call($payload, sprintf(self::ENDPOINT_ELEMENT, $this->defaultServerId, $zone->getId()), null, false, 'PUT');
......@@ -37,7 +43,16 @@ class ZonesEndpoint extends AbstractEndpoint
return null;
}
public function updateRRSets(Zone $zone, $lazyLoad = false, $hydrationClass = Zone::class): ?Zone
/**
* @param Zone $zone
* @param bool $lazyLoad
* @param string $hydrationClass
*
* @return Zone|null
*
* @throws \Http\Client\Exception
*/
public function updateRRSets(Zone $zone, bool $lazyLoad = false, string $hydrationClass = Zone::class): ?Zone
{
$this->validateEntity($zone, ['UPDATE']);
......@@ -62,9 +77,11 @@ class ZonesEndpoint extends AbstractEndpoint
}
/**
* @param string|Zone $zoneId
* @param int|Zone $zoneId
*
* @throws \Http\Client\Exception
*/
public function delete($zoneId)
public function delete($zoneId): void
{
if ($zoneId instanceof Zone) {
$zoneId = $zoneId->getId();
......@@ -73,9 +90,18 @@ class ZonesEndpoint extends AbstractEndpoint
$this->getClient()->call(null, sprintf(self::ENDPOINT_ELEMENT, $this->defaultServerId, $zoneId), null, false, 'DELETE');
}
public function create(Zone $zone, $rrsets = true, $hydrationClass = Zone::class): Zone
/**
* @param Zone $zone
* @param bool $rrsets
* @param string $hydrationClass
*
* @return Zone
*
* @throws \Http\Client\Exception
*/
public function create(Zone $zone, $rrsets = true, string $hydrationClass = Zone::class): Zone
{
$rrsets = ($rrsets) ? 'true' : 'false';
$rrsets = $rrsets ? 'true' : 'false';
$this->validateEntity($zone, ['CREATE']);
......@@ -86,6 +112,7 @@ class ZonesEndpoint extends AbstractEndpoint
/**
* @param string|Zone $zoneId
* @param string $hydrationClass
*
* @return Zone
*
......@@ -101,11 +128,14 @@ class ZonesEndpoint extends AbstractEndpoint
}
/**
* @return Servers[]
* @param string $zoneName
* @param string $hydrationClass
*
* @return Zone[]
*
* @throws \Http\Client\Exception
*/
public function all($zoneName = null, $hydrationClass = Zone::class): array
public function all(?string $zoneName = null, string $hydrationClass = Zone::class): array
{
$queryParams = [];
if (null !== $zoneName) {
......@@ -115,7 +145,12 @@ class ZonesEndpoint extends AbstractEndpoint
return $this->getClient()->call(null, sprintf(self::ENDPOINT_LIST, $this->defaultServerId), $hydrationClass, true, 'GET', $queryParams);
}
public function axfrRetrieve($zoneId)
/**
* @param int|Zone $zoneId
*
* @throws \Http\Client\Exception
*/
public function axfrRetrieve($zoneId): void
{
if ($zoneId instanceof Zone) {
$zoneId = $zoneId->getId();
......@@ -124,7 +159,12 @@ class ZonesEndpoint extends AbstractEndpoint
$this->getClient()->call(null, sprintf(self::ENDPOINT_ELEMENT, $this->defaultServerId, $zoneId).'/axfr-retrieve', null, false, 'PUT');
}
public function notify($zoneId)
/**
* @param int|Zone $zoneId
*
* @throws \Http\Client\Exception
*/
public function notify($zoneId): void
{
if ($zoneId instanceof Zone) {
$zoneId = $zoneId->getId();
......@@ -133,7 +173,14 @@ class ZonesEndpoint extends AbstractEndpoint
$this->getClient()->call(null, sprintf(self::ENDPOINT_ELEMENT, $this->defaultServerId, $zoneId).'/notify', null, false, 'PUT');
}
public function export($zoneId)
/**
* @param int|Zone $zoneId
*
* @return string
*
* @throws \Http\Client\Exception
*/
public function export($zoneId): ?string
{
if ($zoneId instanceof Zone) {
$zoneId = $zoneId->getId();
......@@ -142,7 +189,14 @@ class ZonesEndpoint extends AbstractEndpoint
return $this->getClient()->call(null, sprintf(self::ENDPOINT_ELEMENT, $this->defaultServerId, $zoneId).'/export', null, false, 'GET');
}
public function check($zoneId)
/**
* @param int|Zone $zoneId
*
* @return string
*
* @throws \Http\Client\Exception
*/
public function check($zoneId): string
{
if ($zoneId instanceof Zone) {
$zoneId = $zoneId->getId();
......@@ -151,7 +205,14 @@ class ZonesEndpoint extends AbstractEndpoint
return $this->getClient()->call(null, sprintf(self::ENDPOINT_ELEMENT, $this->defaultServerId, $zoneId).'/check', null, false, 'GET');
}
public function rectify($zoneId)
/**
* @param int|Zone $zoneId
*
* @return string
*
* @throws \Http\Client\Exception
*/
public function rectify($zoneId): string
{
if ($zoneId instanceof Zone) {
$zoneId = $zoneId->getId();
......
......@@ -13,14 +13,52 @@ declare(strict_types=1);
namespace Cwd\PowerDNSClient\Model;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
class Cryptokey
{
protected const VALID_KEYTYPES = [
'ksk',
'zsk',
'csk',
];
// https://doc.powerdns.com/md/authoritative/dnssec/#supported-algorithms
// https://github.com/operasoftware/dns-ui/issues/57#issuecomment-377553394
public const VALID_ALGORITHMS = [
1 => 'RSAMD5',
2 => 'DH',
3 => 'DSA',
5 => 'RSASHA1',
6 => 'DSA-NSEC3-SHA1',
7 => 'RSASHA1-NSEC3-SHA1',
8 => 'RSASHA256',
10 => 'RSASHA512',
12 => 'ECC-GOST',
13 => 'ECDSAP256SHA256',
14 => 'ECDSAP384SHA384',
15 => 'ED25519',
16 => 'ED448',
];
/** @var string */
private $type;
/** @var string */
private $type = 'Cryptokey';
/** @var int */
private $id;
/** @var string */
/**
* @var string
* @Assert\Choice(
* choices = {"ksk", "zsk", "csk"},
* groups={"CREATE"}
* )
*/
private $keytype;
/** @var bool */
private $active = false;
/** @var string */
......@@ -57,17 +95,17 @@ class Cryptokey
/**
* @return string
*/
public function getId(): string
public function getId(): ?int
{
return $this->id;
}
/**
* @param string $id
* @param int $id
*
* @return Cryptokey
*/
public function setId(string $id): Cryptokey
public function setId(int $id): Cryptokey
{
$this->id = $id;
......@@ -77,7 +115,7 @@ class Cryptokey
/**
* @return string
*/
public function getKeytype(): string
public function getKeytype(): ?string
{
return $this->keytype;
}
......@@ -117,7 +155,7 @@ class Cryptokey
/**
* @return string
*/
public function getDnskey(): string
public function getDnskey(): ?string
{
return $this->dnskey;
}
......@@ -157,7 +195,7 @@ class Cryptokey
/**
* @return string
*/
public function getPrivatekey(): string
public function getPrivatekey(): ?string
{
return $this->privatekey;
}
......@@ -175,19 +213,19 @@ class Cryptokey
}
/**
* @return string
* @return string|int|null
*/
public function getAlgorithm(): string
public function getAlgorithm()
{
return $this->algorithm;
}
/**
* @param string $algorithm
* @param string|int $algorithm
*
* @return Cryptokey
*/
public function setAlgorithm(string $algorithm): Cryptokey
public function setAlgorithm($algorithm): Cryptokey
{
$this->algorithm = $algorithm;
......@@ -197,7 +235,7 @@ class Cryptokey
/**
* @return int
*/
public function getBits(): int
public function getBits(): ?int
{
return $this->bits;
}
......@@ -213,4 +251,57 @@ class Cryptokey
return $this;
}
/**
* Reconstruct the DNSKEY RDATA wire format (https://tools.ietf.org/html/rfc4034#section-2.1)
* by merging the flags, protocol, algorithm, and the base64-decoded key data
* https://github.com/operasoftware/dns-ui/commit/35821799f7c2a2e17e9178612e24147dfe7c0867#diff-0d3376b053b1313ed26a84a0c61ef0f2R344
* by Thomas Pike.
*
* @return string
*/
public function getTag(): ?int
{
if (null === $this->dnskey) {
return null;
}
list($flags, $protocol, $algorithm, $keydata) = preg_split('/\s+/', $this->dnskey);
$wire_format = pack('nCC', $flags, $protocol, $algorithm).base64_decode($keydata);
// Split data into (zero-indexed) array of bytes
$keyvalues = array_values(unpack('C*', $wire_format));
// Follow algorithm from RFC 4034 Appendix B (https://tools.ietf.org/html/rfc4034#appendix-B)
$ac = 0;
foreach ($keyvalues as $i => $keyvalue) {
$ac += ($i & 1) ? $keyvalue : $keyvalue << 8;
}
$ac += ($ac >> 16) & 0xFFFF;
return $ac & 0xFFFF;
}
/**
* @param ExecutionContextInterface $context
* @param $payload
* @Assert\Callback(groups={"CREATE"})
*/
public function validateAlgos(ExecutionContextInterface $context, $payload): void
{
if (null === $this->getAlgorithm()) {
return;
}
if (\is_integer($this->getAlgorithm()) && !\array_key_exists($this->getAlgorithm(), self::VALID_ALGORITHMS)) {
$context->buildViolation(sprintf('Algorithm "%s" is unknown', $this->getAlgorithm()))
->atPath('algorithm')
->addViolation();
}
if (\is_string($this->getAlgorithm()) && !\in_array($this->getAlgorithm(), self::VALID_ALGORITHMS, false)) {
$context->buildViolation(sprintf('Algorithm "%s" is unknown', $this->getAlgorithm()))
->atPath('algorithm')
->addViolation();
}
}
}
......@@ -19,7 +19,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
class Metadata
{
// https://doc.powerdns.com/md/httpapi/api_spec/#zone-metadata
const UPDATE_FORBIDDEN = [
protected const UPDATE_FORBIDDEN = [
'NSEC3PARAM',
'NSEC3NARROW',
'PRESIGNED',
......@@ -27,7 +27,7 @@ class Metadata
];
// https://doc.powerdns.com/authoritative/domainmetadata.html
const VALID_KINDs = [
protected const VALID_KINDs = [
'ALLOW-AXFR-FROM',
'API-RECTIFY',
'AXFR-SOURCE',
......@@ -107,13 +107,13 @@ class Metadata
* @param $payload
* @Assert\Callback(groups={"CREATE", "UPDATE"})
*/
public function validateKinds(ExecutionContextInterface $context, $payload)