From add6cae9d4673f70355c1b94aea836055bc33423 Mon Sep 17 00:00:00 2001 From: Jason Morriss Date: Wed, 22 Sep 2021 10:31:49 -0400 Subject: [PATCH] initial commit --- LICENSE | 21 + README.md | 5 + composer.json | 20 + php-cisco-ise.iml | 9 + src/AbstractListObject.php | 100 +++ src/AbstractObject.php | 83 ++ src/AbstractResult.php | 50 ++ src/AncEndPoint.php | 144 ++++ src/AncPolicy.php | 139 ++++ src/AuthenticationSettings.php | 176 +++++ src/CiscoISEClient.php | 1291 ++++++++++++++++++++++++++++++++ src/CiscoISEIterator.php | 166 ++++ src/EndPoint.php | 239 ++++++ src/EndPointGroup.php | 99 +++ src/ExceptionsTrait.php | 99 +++ src/ISEError.php | 41 + src/NetworkDevice.php | 281 +++++++ src/NetworkDeviceGroup.php | 98 +++ src/NetworkDeviceGroupList.php | 9 + src/NetworkDeviceIPList.php | 80 ++ src/Node.php | 317 ++++++++ src/ObjectInterface.php | 41 + src/ObjectListInterface.php | 48 ++ src/SearchResult.php | 110 +++ src/SnmpSettings.php | 299 ++++++++ src/TacacsSettings.php | 90 +++ src/VersionInfo.php | 64 ++ 27 files changed, 4119 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 composer.json create mode 100644 php-cisco-ise.iml create mode 100644 src/AbstractListObject.php create mode 100644 src/AbstractObject.php create mode 100644 src/AbstractResult.php create mode 100644 src/AncEndPoint.php create mode 100644 src/AncPolicy.php create mode 100644 src/AuthenticationSettings.php create mode 100644 src/CiscoISEClient.php create mode 100644 src/CiscoISEIterator.php create mode 100644 src/EndPoint.php create mode 100644 src/EndPointGroup.php create mode 100644 src/ExceptionsTrait.php create mode 100644 src/ISEError.php create mode 100644 src/NetworkDevice.php create mode 100644 src/NetworkDeviceGroup.php create mode 100644 src/NetworkDeviceGroupList.php create mode 100644 src/NetworkDeviceIPList.php create mode 100644 src/Node.php create mode 100644 src/ObjectInterface.php create mode 100644 src/ObjectListInterface.php create mode 100644 src/SearchResult.php create mode 100644 src/SnmpSettings.php create mode 100644 src/TacacsSettings.php create mode 100644 src/VersionInfo.php diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ddfc185 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2013 Jason Morriss + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..6fee32a --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +## Cisco Identity Engine REST Client + +Interface with the Cisco Identity Engine REST API. + + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b89ff12 --- /dev/null +++ b/composer.json @@ -0,0 +1,20 @@ +{ + "name": "lifo/php-cisco-ise", + "description": "Cisco Identity Services Engine REST API Client", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Jason Morriss", + "email": "lifo101@gmail.com" + } + ], + "autoload": { + "psr-4": { "Lifo\\CiscoISE\\": "src" } + }, + "require": { + "php": ">=7.1", + "ext-simplexml": "*", + "symfony/property-access": "^3.0 || ^4.0 || ^5.0" + } +} diff --git a/php-cisco-ise.iml b/php-cisco-ise.iml new file mode 100644 index 0000000..8021953 --- /dev/null +++ b/php-cisco-ise.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/AbstractListObject.php b/src/AbstractListObject.php new file mode 100644 index 0000000..b6d03d1 --- /dev/null +++ b/src/AbstractListObject.php @@ -0,0 +1,100 @@ +add($item); + } + return $dest; + } + + public function mapPropToKey(string $prop): string + { + return $prop; + } + + public function toArray(): array + { + return $this->list; + } + + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Add an item to the list + * + * @param string $item + * + * @return $this + */ + public function add($item): self + { + if (!$this->exists($item)) { + $this->list[] = $item; + } + + return $this; + } + + /** + * Remove an item from the list + * + * @param string $item + * + * @return $this + */ + public function remove($item): self + { + $this->list = array_values(array_filter($this->list, fn($ele) => $ele !== $item)); + + return $this; + } + + /** + * Returns true if the item exists + * + * @param string $item + * + * @return bool + */ + public function exists($item): bool + { + return in_array($item, $this->list); + } + + public function count(): int + { + return count($this->list); + } + + public function first() + { + if (!$this->list) return null; + return $this->list[0]; + } + + public function getId(): ?string + { + return null; + } +} \ No newline at end of file diff --git a/src/AbstractObject.php b/src/AbstractObject.php new file mode 100644 index 0000000..871299b --- /dev/null +++ b/src/AbstractObject.php @@ -0,0 +1,83 @@ + $val) { + $key = $this->mapPropToKey($prop); + if ($val instanceof ObjectInterface) { + $ary[$key] = $val->toArray(); + } else { + $ary[$key] = $val; + } + } + return $ary; + } + + public function jsonSerialize(): array + { + return $this->toArray(); + } + + /** + * @param mixed $from + * @param mixed $dest + * + * @return self + */ + public static function createFrom($from, $dest = null): self + { + $dest ??= new static(); + if (is_array($from)) $from = (object)$from; + $props = get_object_vars($from); + $accessor = PropertyAccess::createPropertyAccessor(); + foreach ($props as $prop => $value) { + try { + if (is_object($value)) { + // some server objects are lowercase; must uppercase it for Class detection to work + $cls = __NAMESPACE__ . '\\' . str_replace('setting', 'Setting', ucfirst($prop)); + if (is_a($cls, ObjectInterface::class, true)) { + $value = new $cls($value); + } + } + if ($accessor->isWritable($dest, $prop)) { + $accessor->setValue($dest, $prop, $value); + } + } catch (Exception $e) { + continue; + } + } + return $dest; + } +} \ No newline at end of file diff --git a/src/AbstractResult.php b/src/AbstractResult.php new file mode 100644 index 0000000..a11279d --- /dev/null +++ b/src/AbstractResult.php @@ -0,0 +1,50 @@ +result = $result; + $this->ise = $ise; + } + + /** + * Return the RAW result response from the server. + * + * @return object + */ + protected function getResult() + { + return $this->result; + } + + /** + * @return CiscoISEClient + */ + protected function getISEClient() + { + return $this->ise; + } + + /** + * @param string $var + * @param object $obj + * @param mixed $default + * + * @return mixed + */ + protected function extractProperty(string $var, object $obj, $default = null) + { + if (property_exists($obj, $var)) { + return $obj->$var; + } + return $default; + } + +} \ No newline at end of file diff --git a/src/AncEndPoint.php b/src/AncEndPoint.php new file mode 100644 index 0000000..1ad6b4d --- /dev/null +++ b/src/AncEndPoint.php @@ -0,0 +1,144 @@ +id = null; + } + + /** + * @return string|null + */ + public function getId(): ?string + { + return $this->id; + } + + /** + * @param string|null $id + * + * @return self + */ + public function setId(?string $id): self + { + $this->id = $id; + return $this; + } + + /** + * @return string|null + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * @param string|null $name + * + * @return self + */ + public function setName(?string $name): self + { + $this->name = $name; + return $this; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @param string|null $description + * + * @return self + */ + public function setDescription(?string $description): self + { + $this->description = $description; + return $this; + } + + /** + * @return string|null + */ + public function getMacAddress(): ?string + { + return $this->macAddress; + } + + /** + * @param string|null $macAddress + * + * @return self + */ + public function setMacAddress(?string $macAddress): self + { + $this->macAddress = $macAddress; + return $this; + } + + /** + * Alias for {@link getMacAddress} + * + * @return string|null + * @see getMacAddress + */ + public function getMac(): ?string + { + return $this->macAddress; + } + + /** + * Alias for {@link setMacAddress} + * + * @param string|null $mac + * + * @return self + * @see setMacAddress + */ + public function setMac(?string $mac): self + { + return $this->setMacAddress($mac); + } + + /** + * @return string|null + */ + public function getPolicyName(): ?string + { + return $this->policyName; + } + + /** + * @param string|null $policyName + * + * @return self + */ + public function setPolicyName(?string $policyName): self + { + $this->policyName = $policyName; + return $this; + } + +} \ No newline at end of file diff --git a/src/AncPolicy.php b/src/AncPolicy.php new file mode 100644 index 0000000..29710b7 --- /dev/null +++ b/src/AncPolicy.php @@ -0,0 +1,139 @@ +id = null; + } + + /** + * @return string|null + */ + public function getId(): ?string + { + return $this->id; + } + + /** + * @param string|null $id + * + * @return self + */ + public function setId(?string $id): self + { + $this->id = $id; + return $this; + } + + /** + * @return string|null + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * @param string|null $name + * + * @return self + */ + public function setName(?string $name): self + { + $this->name = $name; + return $this; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @param string|null $description + * + * @return self + */ + public function setDescription(?string $description): self + { + $this->description = $description; + return $this; + } + + /** + * get actions + * + * @return string[] + */ + public function getActions(): array + { + return $this->actions; + } + + /** + * Set actions + * + * @param string|string[] $actions + * + * @return AncPolicy + */ + public function setActions($actions): self + { + if (!is_iterable($actions)) $actions = [$actions]; + foreach ($actions as $act) { + $this->addAction($act); + } + return $this; + } + + /** + * Add an action + * + * @param string $action + * + * @return $this + */ + public function addAction(string $action): self + { + if (!in_array($action, $this->actions) && in_array($action, self::VALID_ACTIONS)) { + $this->actions[] = $action; + } + return $this; + } + + /** + * Remove an action + * + * @param string $action + * + * @return $this + */ + public function removeAction(string $action): self + { + if (false !== $pos = array_search($action, $this->actions)) { + array_splice($this->actions, $pos, 1); + } + return $this; + } + +} \ No newline at end of file diff --git a/src/AuthenticationSettings.php b/src/AuthenticationSettings.php new file mode 100644 index 0000000..d5c414e --- /dev/null +++ b/src/AuthenticationSettings.php @@ -0,0 +1,176 @@ +networkProtocol; + } + + /** + * @param string|null $networkProtocol + * + * @return AuthenticationSettings + */ + public function setNetworkProtocol(?string $networkProtocol): AuthenticationSettings + { + $this->networkProtocol = $networkProtocol; + return $this; + } + + /** + * @return string|null + */ + public function getRadiusSharedSecret(): ?string + { + return $this->radiusSharedSecret; + } + + /** + * @param string|null $radiusSharedSecret + * + * @return AuthenticationSettings + */ + public function setRadiusSharedSecret(?string $radiusSharedSecret): AuthenticationSettings + { + $this->radiusSharedSecret = $radiusSharedSecret; + return $this; + } + + /** + * @return string|null + */ + public function getKeyEncryptionKey(): ?string + { + return $this->keyEncryptionKey; + } + + /** + * @param string|null $keyEncryptionKey + * + * @return AuthenticationSettings + */ + public function setKeyEncryptionKey(?string $keyEncryptionKey): AuthenticationSettings + { + $this->keyEncryptionKey = $keyEncryptionKey; + return $this; + } + + /** + * @return string|null + */ + public function getMessageAuthenticatorCodeKey(): ?string + { + return $this->messageAuthenticatorCodeKey; + } + + /** + * @param string|null $messageAuthenticatorCodeKey + * + * @return AuthenticationSettings + */ + public function setMessageAuthenticatorCodeKey(?string $messageAuthenticatorCodeKey): AuthenticationSettings + { + $this->messageAuthenticatorCodeKey = $messageAuthenticatorCodeKey; + return $this; + } + + /** + * @return string|null + */ + public function getKeyInputFormat(): ?string + { + return $this->keyInputFormat; + } + + /** + * @param string|null $keyInputFormat + * + * @return AuthenticationSettings + */ + public function setKeyInputFormat(?string $keyInputFormat): AuthenticationSettings + { + $this->keyInputFormat = $keyInputFormat; + return $this; + } + + /** + * @return bool + */ + public function isEnableKeyWrap(): bool + { + return $this->enableKeyWrap; + } + + /** + * @param bool $enableKeyWrap + * + * @return AuthenticationSettings + */ + public function setEnableKeyWrap(bool $enableKeyWrap): AuthenticationSettings + { + $this->enableKeyWrap = $enableKeyWrap; + return $this; + } + + /** + * @return bool + */ + public function isDtlsRequired(): bool + { + return $this->dtlsRequired; + } + + /** + * @param bool $dtlsRequired + * + * @return AuthenticationSettings + */ + public function setDtlsRequired(bool $dtlsRequired): AuthenticationSettings + { + $this->dtlsRequired = $dtlsRequired; + return $this; + } + + /** + * @return bool + */ + public function isEnableMultiSecret(): bool + { + return $this->enableMultiSecret; + } + + /** + * ISE API uses a stringified 'false'|'true' for this one + * + * @param bool|string $enableMultiSecret + * + * @return AuthenticationSettings + */ + public function setEnableMultiSecret($enableMultiSecret): AuthenticationSettings + { + if (is_string($enableMultiSecret)) { + $enableMultiSecret = strtolower($enableMultiSecret) === 'true'; + } + $this->enableMultiSecret = $enableMultiSecret; + return $this; + } + + +} \ No newline at end of file diff --git a/src/CiscoISEClient.php b/src/CiscoISEClient.php new file mode 100644 index 0000000..e6c5750 --- /dev/null +++ b/src/CiscoISEClient.php @@ -0,0 +1,1291 @@ +setHosts($host); + $this->setPort(9060); + $this->setUsername($username); + $this->setPassword($password); + $this->setDebug(false); + $this->setTimeout(5); + $this->lastUrl = null; + $this->json = null; + $this->version = $version; + $this->errno = null; + $this->error = null; + $this->headers = []; + $this->httpStatus = null; + $this->headerIndex = null; + } + + protected function curlInit($url, $method = null, $contentType = 'json') + { + $ch = curl_init(); + $this->lastUrl = $url; + + curl_setopt($ch, CURLOPT_URL, $url); + // must send user/pass with each request (ISE doesn't seem to like using the cookie that it sends back) + curl_setopt($ch, CURLOPT_USERPWD, $this->username . ":" . $this->password); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // ISE API uses 'Location:' for other purposes +// curl_setopt($ch, CURLOPT_SSLVERSION, 1); // don't set SSL version; use default + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout); + curl_setopt($ch, CURLOPT_HEADERFUNCTION, [$this, 'processHeader']); + curl_setopt($ch, CURLOPT_VERBOSE, $this->isDebug()); + + if (!empty($method)) { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + } + + $this->headerIndex = null; + $this->sendHeaders = []; + $this->sendHeaders[] = 'Accept: application/' . $contentType; + + return $ch; + } + + /** + * @return bool + */ + public function isDebug(): bool + { + return $this->debug; + } + + /** + * @param bool $debug + * + * @return self + */ + public function setDebug(bool $debug): self + { + $this->debug = $debug; + return $this; + } + + protected function buildUrl($op, array $params = null): string + { + // Is the $op a fully qualified URL? + if (substr($op, 0, 4) == 'http') { + $url = $op; + } else { + $url = 'https://' . $this->host . ':' . $this->port . '/ers/' . ltrim($op ?: '', '/'); + } + + if ($params === null) { + $params = []; + } + + // add defaults + if (!empty($this->defaults)) { + $params = array_replace($params, $this->defaults); + } + + if (!empty($params)) { + // If the URL has a query string directly in it then make sure $params does not have any conflicting vars + $q = parse_url($url, PHP_URL_QUERY); + if ($q) { + $overrides = []; + parse_str($q, $overrides); + $params = array_diff_key($params, $overrides); + } + + if (!empty($params)) { + $q = http_build_query($params); + // replace 'filter[x]=' with 'filter=' + $q = preg_replace('/%5B\d+%5D/simU', '', $q); + $url .= '?' . $q; + } + } + + return $url; + } + + /** + * @param mixed $ch + * @param string|array $post + * @param string $contentType + * + * @return mixed + * @throws Exception on failure + */ + protected function curl($ch, $post = null, string $contentType = 'json'): ?object + { + if (!is_resource($ch)) { + $ch = $this->curlInit($ch); + } + + $this->post = null; + if (!empty($post)) { + if (!is_string($post)) { + $post = json_encode($post); + } + $this->post = $post; + curl_setopt($ch, CURLOPT_POSTFIELDS, $post); + $this->sendHeaders[] = 'Content-Type: application/' . $contentType; + } + + if ($this->sendHeaders) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $this->sendHeaders); + } + + $this->error = null; + $str = curl_exec($ch); + $this->httpStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $this->processError($ch); + $this->response = $this->processResponse($str); + curl_close($ch); + return $this->response; + } + + /** + * @param resource $ch Curl Handle + * + * @throws Exception on failure + */ + protected function processError($ch) + { + $this->errno = curl_errno($ch); + $this->error = curl_error($ch) ?: null; + if (substr($this->httpStatus, 0, 1) === '2') return; + $this->errno = $this->httpStatus; + switch (true) { + case $this->httpStatus >= 500: + $this->error = 'Internal Server Error'; + break; + case $this->httpStatus === 401: + $this->error = 'Invalid Credentials'; + break; + case $this->httpStatus === 403: + $this->error = 'Unauthorized'; + break; + case $this->httpStatus === 404: + $this->error = 'Not Found'; + break; + case $this->httpStatus === 415: + $this->error = 'Unsupported Media Type'; + break; + case $this->httpStatus >= 400: + $this->error = 'Bad Request'; + break; + } + + if ($this->error) { + throw new ISEError(null, $this->error); + } + } + + /** + * @param mixed $str + * + * @return object|null + */ + protected function processResponse($str): ?object + { + if (!$str) return null; + $json = json_decode($str); + $this->json = $json; + // API always returns an JSON object with a single property. + if ($json) { + $props = array_keys(get_object_vars($json)); + $type = $props[0]; + $cls = $this->mapTypeToClass($type); + try { + return $this->hydrateLocalObject($json->$type, $cls); + } catch (Exception $e) { + return $json; + } + } + return $json; + } + + /** + * Convert the object given into the corresponding Class, if possible + * + * @param string $type + * @param object $obj + * + * @return object + */ + protected function hydrateLocalObject(object $obj, string $type): object + { + $class = __NAMESPACE__ . '\\' . $type; + if (class_exists($class)) { + switch (true) { + case $class === SearchResult::class: + return new $class($obj, $this); + default: + return new $class($obj); + } + } else { + return $obj; + } + } + + /** + * Return the last response. + * + * @return object + */ + public function getResponse(): ?object + { + return $this->response; + } + + /** + * @return object + */ + public function getJson(): ?object + { + return $this->json; + } + + /** + * Return the last POST data + * + * @return string + */ + public function getPost(): ?string + { + return $this->post; + } + + /** + * @return string + */ + public function getHost(): string + { + return $this->host; + } + + /** + * @param string $host + * + * @return self + */ + public function setHost(string $host): self + { + $this->host = $host; + return $this; + } + + /** + * @param string[]|string $hosts + * + * @return self + */ + public function setHosts($hosts): self + { + $this->hosts = is_iterable($hosts) ? (array)$hosts : [$hosts]; + $this->setHost(reset($this->hosts)); + return $this; + } + + /** + * @return array + */ + public function getHosts(): array + { + return $this->hosts; + } + + /** + * Determine the primary node in the cluster and set it as the current host for API calls. + * {@link $hosts} must be set. + * + * @return Node|null + */ + public function determinePrimaryNode(): ?Node + { + $origHost = $this->getHost(); + $hosts = $this->getHosts(); + foreach ($hosts as $host) { + $this->setHost($host); + $s = $this->getNodes(); + if (!$s || !$s->getTotal()) continue; + $s->setHydrate(true); + /** @var Node $node */ + foreach ($s as $node) { + if ($node->getPrimaryPapNode()) { + $this->setHost($node->getIpAddress()); + return $node; + } + } + } + $this->setHost($origHost); + return null; + } + + /** + * Get current and supported versions. + * + * @return VersionInfo|null + */ + public function getVersions(): ?VersionInfo + { + $url = $this->buildUrl('config/node/versioninfo'); + return $this->curl($url); + } + + /** + * @param int $port + * + * @return CiscoISEClient + */ + public function setPort(int $port): self + { + $this->port = $port; + return $this; + } + + /** + * @return int + */ + public function getPort(): int + { + return $this->port; + } + + /** + * @param int $timeout + * + * @return self + */ + public function setTimeout(int $timeout): self + { + $this->timeout = $timeout; + return $this; + } + + /** + * @return int + */ + public function getTimeout(): int + { + return $this->timeout; + } + + /** + * @return string + */ + public function getUsername(): string + { + return $this->username; + } + + /** + * @param string $username + * + * @return self + */ + public function setUsername(string $username): self + { + $this->username = $username; + return $this; + } + + /** + * @return string + */ + public function getPassword(): string + { + return $this->password; + } + + /** + * @param string $password + * + * @return self + */ + public function setPassword(string $password): self + { + $this->password = $password; + return $this; + } + + /** + * @return string + */ + public function getError(): ?string + { + return $this->error; + } + + /** + * Return the last URL requested + * + * @return string + */ + public function getLastUrl(): ?string + { + return $this->lastUrl; + } + + /** + * Return the matching header from the last response received. Note, if you make several requests you'll only see + * the last response. + * + * @param string $name + * + * @return string|string[]|null + */ + public function getHeader(string $name) + { + if (!$this->headers) { + return null; + } + $name = strtolower($name); + $headers = end($this->headers); + $headers = array_combine(array_map('strtolower', array_keys($headers)), array_values($headers)); + if (array_key_exists($name, $headers)) { + return $headers[$name]; + } + return null; + } + + /** + * Return all headers + * + * @return string[] + */ + public function getHeaders(): array + { + return $this->headers; + } + + /** + * Low level command to GET anything. Mainly used from the CiscoISEIterator but can be used for other ad-hoc + * queries as well. + * + * @param string $op + * @param array $params + * + * @return mixed + */ + public function get(string $op, $params = []): ?object + { + return $this->curl($this->buildUrl($op, $params)); + } + + /** + * Fetch a list of nodes. + * + * @param array $params + * + * @return SearchResult + */ + public function getNodes($params = []) + { + $url = $this->buildUrl('config/node', $params); + return $this->curl($url); + } + + /** + * Fetch a list of endpoint identity groups. + * + * @param array $params + * + * @return SearchResult + */ + public function getEndpointGroups($params = null) + { + $url = $this->buildUrl('config/endpointgroup', $params); + return $this->curl($url); + } + + /** + * Fetch an endpoint identity group by ID or name. + * + * @param string $id + * @param bool $byName + * + * @return EndPointGroup|null + */ + public function getEndpointGroup(string $id, bool $byName = null): ?object + { + $url = $this->buildUrl(($byName ? EndPointGroup::CONFIG_NAME_URI : EndPointGroup::CONFIG_URI) . '/' . rawurlencode($id)); + return $this->curl($url); + } + + /** + * Fetch an endpoint identity group by name. + * + * @param string $name + * + * @return EndPointGroup|null + */ + public function getEndpointGroupByName(string $name): ?object + { + $url = $this->buildUrl(EndPointGroup::CONFIG_NAME_URI . '/' . rawurlencode($name)); + return $this->curl($url); + } + + /** + * Fetch a list of endpoints. Not safe to paginate through this result set due to the large result set (>100k). + * + * @param array $params + * + * @return SearchResult|object + */ + public function getEndPoints($params = null) + { + $url = $this->buildUrl(EndPoint::CONFIG_URI, $params); + return $this->curl($url); + } + + /** + * Fetch a list of ANC endpoints. + * + * @param array $params + * + * @return SearchResult|object + */ + public function getAncEndPoints($params = null) + { + $url = $this->buildUrl(AncEndPoint::CONFIG_URI, $params); + return $this->curl($url); + } + + /** + * Fetch an ANC EndPoint. + * + * @param string|EndPoint $id + * @param array $params + * + * @return SearchResult + */ + public function getAncEndpoint($id, $params = null) + { + if ($id instanceof EndPoint) $id = $id->getId(); + $url = $this->buildUrl(sprintf(AncEndPoint::CONFIG_URI . '/%s', rawurlencode($id)), $params); + return $this->curl($url); + } + + + /** + * Fetch a list of network devices. + * + * @param array $params + * + * @return SearchResult + */ + public function getNetworkDeviceGroups($params = null) + { + $url = $this->buildUrl('config/networkdevicegroup', $params); + return $this->curl($url); + } + + /** + * Fetch a single network device group by it's ID. + * + * @param string $id + * @param array $params + * + * @return NetworkDeviceGroup|null + */ + public function getNetworkDeviceGroup(string $id, $params = null): ?NetworkDeviceGroup + { + $url = $this->buildUrl(sprintf('config/networkdevicegroup/%s', $id), $params); + return $this->curl($url); + } + + /** + * Fetch a single network device group by it's ID. + * + * @param string $name + * @param null $params + * + * @return NetworkDeviceGroup|null + */ + public function getNetworkDeviceGroupByName(string $name, $params = null): ?NetworkDeviceGroup + { + try { + $url = $this->buildUrl(sprintf('config/networkdevicegroup/name/%s', rawurlencode(str_replace('#', ':', $name))), $params); + return $this->curl($url); + } catch (ISEError $e) { + return null; + } + } + + /** + * Fetch a single network device group by it's Building Name. + * This is a shortcut for fetching NetworkDeviceGroup('Location#All Locations#{BUILDING}') + * + * @param string $name + * @param null $params + * + * @return NetworkDeviceGroup|null + */ + public function getNetworkDeviceGroupByBuilding(string $name, $params = null): ?NetworkDeviceGroup + { + try { + return $this->getNetworkDeviceGroupByName('Location#All Locations#' . $name, $params); + } catch (Exception $e) { + return null; + } + } + + /** + * Fetch a list of network devices. + * + * @param array $params + * + * @return SearchResult|object + */ + public function getNetworkDevices($params = null) + { + $url = $this->buildUrl('config/networkdevice', $params); + return $this->curl($url); + } + + /** + * Fetch a single network device by it's ID. + * + * @param string $id + * @param null $params + * + * @return object|null + */ + public function getNetworkDevice(string $id, $params = null): ?object + { + $url = $this->buildUrl(sprintf('config/networkdevice/%s', $id), $params); + return $this->curl($url); + } + + /** + * Find a Network Device based on its name or IP address. Must match exactly, or null is returned + * + * @param string $match + * + * @return NetworkDevice|null + */ + public function findNetworkDevice(string $match): ?NetworkDevice + { +// $m = rawurlencode($match); + $res = $this->getNetworkDevices([ + 'size' => 2, + 'filtertype' => 'or', + 'filter' => [ + 'name.EQ.' . $match, + 'ipaddress.EQ.' . $match, + ] + ]); + if ($res instanceof SearchResult) { + if ($res->getTotal() === 1) { + $res = $res->getResources()[0]; + return $this->get($res->link->href); + } + } + return null; + } + + /** + * Fetch a list of ANC Policies. + * + * @param array $params + * + * @return SearchResult|object + */ + public function getAncPolicies($params = null) + { + $url = $this->buildUrl(AncPolicy::CONFIG_URI, $params); + return $this->curl($url); + } + + /** + * Fetch an ANC Policy by ID or name. + * + * @param string $id + * @param bool $byName + * + * @return AncPolicy|null + */ + public function getAncPolicy(string $id, bool $byName = null): ?AncPolicy + { + $url = $this->buildUrl(($byName ? AncPolicy::CONFIG_NAME_URI : AncPolicy::CONFIG_URI) . '/' . rawurlencode($id)); + return $this->curl($url); + } + + public function getAncPolicyByName(string $name): ?AncPolicy + { + return $this->getAncPolicy($name, true); + } + + /** + * Apply or clear ANC Policy on the mac(s) (or {@link EndPoint}s) provided. + * If Policy is null its cleared from the EndPoint + * + * @param string|string[]|EndPoint|EndPoint[] $macs MAC Address string, or EndPoint + * @param string|AncPolicy|null $policy Null to clear the policy from the EndPoint + * + * @return bool + */ + public function ancApply($macs, $policy): bool + { + $ary = []; + if (!is_iterable($macs)) $macs = [$macs]; + if ($policy instanceof AncPolicy) $policy = $policy->getId(); + foreach ($macs as $mac) { + $ary[] = ['name' => 'macAddress', 'value' => $mac instanceof EndPoint ? $mac->getMac() : $mac]; + if ($policy) { + $ary[] = ['name' => 'policyName', 'value' => $policy]; + } + } + + $url = $this->buildUrl($policy ? AncEndPoint::CONFIG_APPLY_URI : AncEndPoint::CONFIG_CLEAR_URI); + $ch = $this->curlInit($url, 'PUT'); + $res = $this->curl($ch, ['OperationAdditionalData' => ['additionalData' => $ary]]); + if ($this->httpStatus === 204) return true; + $err = new ISEError($res, sprintf('Error %s policy%s on %s', + $policy ? 'applying' : 'clearing', + $policy ? ' ' . $policy : '', + implode(',', $macs))); + // account for a couple of simple use-cases + if (!$policy && stripos($err->getMessage(), 'not associated') !== false) return false; + if ($policy && stripos($err->getMessage(), 'already associated') !== false) return false; + throw $err; + } + + /** + * Clear an ANC Policy for a Mac|EndPoint. + * + * @param $macs + * + * @return bool + */ + public function ancClear($macs): bool + { + return $this->ancApply($macs, null); + } + + /** + * Perform an Create request on the object given + * + * @param ObjectInterface|object|array $obj + * @param string $cls Class name of object being updated + * @param string $uri The URI of the operation to perform (eg: config/networkdevice) + * @param string $rootKey The root key for the JSON requestion (eg: 'NetworkDevice') + * @param bool $hydrate Hydrate the response into a real class + * + * @return bool|string + * @throws ISEError on failure + */ + protected function doCreate($obj, string $cls, string $uri, string $rootKey, bool $hydrate = false) + { + $ary = $obj instanceof ObjectInterface ? $obj->toArray() : (array)$obj; + $url = $this->buildUrl($uri); + $ch = $this->curlInit($url, 'POST'); + $res = $this->curl($ch, [$rootKey => $ary]); + if ($this->httpStatus === 201) { + $url = $this->getHeader('Location'); + $parts = explode('/', $url); + $id = end($parts); + return $hydrate ? $this->get($url) : $id; + } + try { + $name = (new ReflectionClass($cls))->getShortName(); + } catch (Exception $e) { + $name = $cls; + } + throw new ISEError($res, sprintf("Error creating %s", $name)); + } + + /** + * Perform an Update request on the object given + * + * @param ObjectInterface|object|array $obj + * @param string $cls Class name of object being updated + * @param string $uri The URI of the operation to perform (eg: config/networkdevice) + * @param string $rootKey The root key for the JSON requestion (eg: 'NetworkDevice') + * + * @return bool + * @throws ISEError on failure + */ + protected function doUpdate($obj, string $cls, string $uri, string $rootKey): bool + { + $ary = $obj instanceof ObjectInterface ? $obj->toArray() : (array)$obj; + if (!$ary) { +// throw self::createInvalidArgumentException(func_get_args(), sprintf('Invalid Invalid argument provided (must be %s or array)', $cls)); + return false; + } + $id = $ary['id'] ?? null; + if (!$id) { + throw self::createInvalidArgumentException(func_get_args(), 'ID required when updating a ' . $cls); + } + $url = $this->buildUrl($uri . '/' . rawurlencode($id)); + $ch = $this->curlInit($url, 'PUT'); + $res = $this->curl($ch, [$rootKey => $ary]); + if ($this->httpStatus === 200) return true; + + try { + $name = (new ReflectionClass($cls))->getShortName(); + } catch (Exception $e) { + $name = $cls; + } + throw new ISEError($res, sprintf("Error updating %s#%s", $name, $id)); + } + + /** + * @param mixed $obj + * @param string $uri + * + * @return bool + */ + protected function doDelete($obj, string $uri): bool + { + $id = $this->extractId($obj); + if (!$id) { + throw self::createInvalidArgumentException(func_get_args(), 'Invalid argument provided: Could not determine ID from $obj'); + } + $url = $this->buildUrl($uri . '/' . rawurlencode($id)); + $ch = $this->curlInit($url, 'DELETE'); + try { + $this->curl($ch); + return $this->httpStatus === 204; + } catch (Exception $e) { + return false; + } + } + + /** + * @param NetworkDevice|object|array $dev + * @param bool $hydrate If true, fetch the newly created object and return it instead of the ID string + * + * @return NetworkDevice|string|null if $hydrate is true then NetworkDevice else ID string + * @throws ISEError on failure + */ + public function createNetworkDevice($dev, $hydrate = false) + { + return $this->doCreate($dev, NetworkDevice::class, NetworkDevice::CONFIG_URI, NetworkDevice::JSON_ROOT_KEY, $hydrate); + } + + /** + * @param NetworkDevice|object|array $dev + * + * @return bool + * @throws ISEError on failure + */ + public function updateNetworkDevice($dev): bool + { + return $this->doUpdate($dev, NetworkDevice::class, NetworkDevice::CONFIG_URI, NetworkDevice::JSON_ROOT_KEY); + } + + /** + * @param NetworkDevice|object|string $dev + * + * @return bool True on success + */ + public function deleteNetworkDevice($dev): bool + { + return $this->doDelete($dev, NetworkDevice::CONFIG_URI); + } + + + /** + * @param NetworkDeviceGroup|object|array $dev + * @param bool $hydrate If true, fetch the newly created object and return it instead of the ID string + * + * @return NetworkDeviceGroup|string|null if $hydrate is true then NetworkDeviceGroup else ID string + * @throws ISEError on failure + */ + public function createNetworkDeviceGroup($dev, $hydrate = false) + { + return $this->doCreate($dev, NetworkDeviceGroup::class, NetworkDeviceGroup::CONFIG_URI, NetworkDeviceGroup::JSON_ROOT_KEY, $hydrate); + } + + /** + * @param NetworkDeviceGroup|object|string $dev + * + * @return bool True on success + */ + public function deleteNetworkDeviceGroup($dev): bool + { + return $this->doDelete($dev, NetworkDeviceGroup::CONFIG_URI); + } + + /** + * @param EndPoint|object|array $ep + * @param bool $hydrate If true, fetch the newly created object and return it instead of the ID string + * + * @return EndPoint|string|null if $hydrate is true then EndPoint else ID string + * @throws ISEError on failure + */ + public function createEndPoint($ep, $hydrate = false) + { + return $this->doCreate($ep, EndPoint::class, EndPoint::CONFIG_URI, EndPoint::JSON_ROOT_KEY, $hydrate); + } + + /** + * @param EndPoint|object|array $ep + * + * @return bool + * @throws ISEError on failure + */ + public function updateEndPoint($ep): bool + { + return $this->doUpdate($ep, EndPoint::class, EndPoint::CONFIG_URI, EndPoint::JSON_ROOT_KEY); + } + + /** + * @param EndPoint|object|string $ep + * + * @return bool True on success + */ + public function deleteEndPoint($ep): bool + { + return $this->doDelete($ep, EndPoint::CONFIG_URI); + } + + /** + * Create 1 or more EndPoints using the Bulk API + * + * @param object[] $list EndPoints to create + * + * @return string|bool Bulk ID if successful + */ + public function createEndPoints(array $list) + { + $this->doEndPointBulkRequest('create', $list); + if ($this->httpStatus === 202) { + $url = $this->getHeader('Location'); + $parts = explode('/', $url); + return end($parts); + } + return false; + } + + /** + * Delete 1 or more EndPoints using the Bulk API + * + * @param object[]|string[] $list EndPoints to create + * + * @return string|bool Bulk ID if successful + */ + public function deleteEndPoints(array $list) + { + $this->doEndPointBulkRequest('delete', $list); + if ($this->httpStatus === 202) { + $url = $this->getHeader('Location'); + $parts = explode('/', $url); + return end($parts); + } + return false; + } + + /** + * + * @param string $id ID or URL of bulk status + * + * @return object|bool + */ + public function getBulkStatus(string $id) + { + if (false !== strpos($id, '/')) { + $parts = explode('/', $id); + $id = end($parts); + } + $url = $this->buildUrl('config/endpoint/bulk/' . $id); + $res = $this->curl($url); + // todo: $res could contain useful information if it fails + return $this->httpStatus === 200 ? $res : false; + } + + /** + * Send a Bulk EndPoint request + * + * @param string $op + * @param object[] $endPoints + * + * @return mixed + */ + public function doEndPointBulkRequest(string $op, array $endPoints): ?object + { + $url = $this->buildUrl('config/endpoint/bulk'); + $ch = $this->curlInit($url, 'PUT'); + + // bulk requests only work with XML in API v2.4 + $xml = new SimpleXMLElement(''); + $xml->addAttribute('operationType', $op); + $xml->addAttribute('resourceMediaType', 'vnd.com.cisco.ise.identity.endpoint.1.0+xml'); + switch ($op) { + case 'create': + $data = $this->buildCreateEndPointData($xml, $endPoints); + break; + case 'delete': + $data = $this->buildDeleteEndPointData($xml, $endPoints); + break; + default: + throw new InvalidArgumentException("Invalid operation specified: \"$op\""); + } + try { + return $this->curl($ch, $data, 'xml'); + } catch (Exception $e) { + return null; + } + } + + protected function buildCreateEndPointData(SimpleXMLElement $xml, $resources) + { + $children = $xml->addChild('ns4:resourcesList'); + foreach ($resources as $r) { + $r = $r instanceof ObjectInterface ? $r->toArray() : (array)$r; + $desc = $r['description']; + unset($r['description']); + $c = $children->addChild('ns4:endpoint'); + $c->addAttribute('description', $desc); + + // Only these properties can be modified (and in this order) or the bulk API complains + static $myKeys = ['link', 'customAttributes', 'groupId', 'identityStore', 'identityStoreId', 'mac', 'mdmAttributes', 'portalUser', 'profileId', 'staticGroupAssignment', 'staticProfileAssignment']; + foreach ($myKeys as $k) { + if (!array_key_exists($k, $r)) { + continue; + } + $v = $r[$k]; + switch (true) { + case is_bool($v): + $v = $v ? 'true' : 'false'; + break; + } + $c->addChild($k, $v, ''); + } + } + // cleanup some XML so the API won't complain + $data = str_replace(' xmlns=""', '', $xml->asXML()); + $data = preg_replace('~<(\w[\w\d_]+)/>~', '<\\1>', $data); + return $data; + } + + protected function buildDeleteEndPointData(SimpleXMLElement $xml, $resources) + { + $children = $xml->addChild('idList', null, ''); + foreach ($resources as $r) { + $id = $r instanceof ObjectInterface ? $r->getId() : $r; + if (!$id) { + continue; + } + $children->addChild('id', $id, ''); + } + // cleanup some XML so the API won't complain + return str_replace(' xmlns=""', '', $xml->asXML()); + } + + /** + * Find an End Point based on it's MAC address + * + * @param string $mac + * + * @return EndPoint|null + */ + public function findEndPoint(string $mac): ?EndPoint + { + // if we can find it via "name" then we can save a call and return faster + $url = $this->buildUrl(EndPoint::CONFIG_NAME_URI . '/' . rawurlencode($mac)); + $res = $this->curl($url); + // errno=28 === Timed Out + if ($res && $this->httpStatus === 200 || $this->errno === 28) return $res; + + // convert mac from any format into "XX:XX:XX:XX:XX:XX" + $mac = implode(':', str_split(preg_replace('/[^a-zA-Z0-9]/', '', $mac), 2)); + $res = $this->getEndPoints(['filter' => 'mac.EQ.' . rawurlencode($mac)]); + if ($res instanceof SearchResult && $res->getTotal() === 1) { + $res = $res->getResources()[0]; + return $this->get($res->link->href); + } + return null; + } + + /** + * Fetch a list of endpoint identity groups. + * + * @param array $params + * + * @return SearchResult + */ + public function getIdentityGroups($params = null) + { + $url = $this->buildUrl('config/identitygroup', $params); + return $this->curl($url); + } + + /** + * Return the last HTTP code received + * + * @return int + */ + public function getHttpStatus(): ?int + { + return $this->httpStatus; + } + + /** + * Attempt to hydrate the given object from the server. + * Object must be one that was returned from the server. + * Returns original object if it cannot be hydrated. + * + * @param object|null $obj + * + * @return object|null + */ + public function hydrate(?object $obj): ?object + { + if (is_object($obj) && isset($obj->link->href)) { + $res = $this->get($obj->link->href); + return $res ?: $obj; + } + return $obj; + } + + /** + * Set a default parameter to be sent on all requests + * + * @param string $name Name of default to set. If NULL all defaults are cleared + * @param mixed $value Value of default. + * + * @return $this + */ + public function setDefault(string $name, $value = null): self + { + if ($name === null || $name === false) { + $this->defaults = []; + } else { + $this->defaults[$name] = $value; + } + return $this; + } + + /** + * Process each header line. Since CURL may make multiple requests, the resulting headers array may have multiple + * responses in it. + * + * @param $ch + * @param $header + * + * @return int + * @internal Callback function only. + * @noinspection PhpUnusedParameterInspection + */ + protected function processHeader($ch, $header): int + { + $len = strlen($header); + + // increase index each time we get a new HTTP response + if (preg_match('|^HTTP/\d+.\d+\s+\d+|', $header)) { + $this->headerIndex = $this->headerIndex === null ? 0 : $this->headerIndex + 1; + return $len; + } + + // ignore invalid headers + $header = explode(':', $header, 2); + if (count($header) < 2) { + return $len; + } + + $i = $this->headerIndex; + if (!isset($this->headers[$i])) { + $this->headers[$i] = []; + } + + $key = trim($header[0]); + if (isset($this->headers[$i][$key])) { + $this->headers[$i][$key] = [$this->headers[$i][$key]]; + $this->headers[$i][$key][] = trim($header[1]); + } else { + $this->headers[$i][$key] = trim($header[1]); + } + + // must return the total bytes processed (which is the length of the header) or CURL will error + return $len; + } + + /** + * Attempt to find the "id" in the parameter. could be a class, object, array... + * + * @param mixed $obj + * + * @return string|null + */ + protected function extractId($obj): ?string + { + switch (true) { + case $obj instanceof ObjectInterface: + return $obj->getId(); + case is_array($obj): + return isset($obj['id']) ? $obj['id'] : null; + case is_string($obj) || is_int($obj): + // assume the object is just a string ID + return (string)$obj; + } + return null; + } + + /** + * Map the response type received from the server into a real local Class, if possible + * + * @param string $type + * + * @return string + */ + private function mapTypeToClass(string $type): string + { + switch ($type) { + case 'ERSEndPoint': + return 'EndPoint'; + case 'ErsAncPolicy': + return 'AncPolicy'; + } + return $type; + } +} diff --git a/src/CiscoISEIterator.php b/src/CiscoISEIterator.php new file mode 100644 index 0000000..52a0ede --- /dev/null +++ b/src/CiscoISEIterator.php @@ -0,0 +1,166 @@ +page = 0; + $this->pos = 0; + $this->index = 0; + $this->client = clone $client; + $this->total = $total; + $this->limit = 0; + $this->results = $results ?: []; + $this->next = $next; + $this->hydrate = $hydrate; + if ($results === null) { + $this->load(); + } + $this->loaded = true; + } + + /** + * Return the current element + * + * @link http://php.net/manual/en/iterator.current.php + * @return mixed Can return any type. + * @since 5.0.0 + */ + public function current() + { + $o = $this->results[$this->pos]; + if ($this->hydrate && isset($o->link->href) && $o->link->rel === 'self') { + $o = $this->client->get($o->link->href); + } + return $o; + } + + /** + * Move forward to next element + * + * @link http://php.net/manual/en/iterator.next.php + * @return void Any returned value is ignored. + * @since 5.0.0 + */ + public function next() + { + $this->pos++; + $this->index++; + if (!isset($this->results[$this->pos])) { + $this->page++; + $this->load(); + } + } + + /** + * Return the key of the current element + * + * @link http://php.net/manual/en/iterator.key.php + * @return mixed scalar on success, or null on failure. + * @since 5.0.0 + */ + public function key() + { + return $this->index; + } + + /** + * Checks if current position is valid + * + * @link http://php.net/manual/en/iterator.valid.php + * @return boolean The return value will be casted to boolean and then evaluated. + * Returns true on success or false on failure. + * @since 5.0.0 + */ + public function valid() + { + return isset($this->results[$this->pos]) && (!$this->limit || $this->index < $this->limit); + } + + /** + * Rewind the Iterator to the first element + * + * @link http://php.net/manual/en/iterator.rewind.php + * @return void Any returned value is ignored. + * @since 5.0.0 + */ + public function rewind() + { + if (!$this->loaded) { + $this->page = 0; + $this->index = 0; + $this->pos = 0; + $this->results = []; + $this->load(); + } + $this->loaded = false; + } + + /** + * Load the next batch of results + */ + private function load() + { + $this->pos = 0; + if ($this->next && $this->total > 0 && $this->index < $this->total && (!$this->limit || $this->index < $this->limit)) { + $res = $this->client->get($this->next); + if ($res instanceof SearchResult) { + $this->results = $res->getResources(); + $this->next = $res->getNextPage(true); + } + + } else { + $this->results = []; + } + } + + /** + * @return int + */ + public function getLimit() + { + return $this->limit; + } + + /** + * @param int $limit + */ + public function setLimit($limit) + { + $this->limit = $limit; + } +} \ No newline at end of file diff --git a/src/EndPoint.php b/src/EndPoint.php new file mode 100644 index 0000000..e30f03b --- /dev/null +++ b/src/EndPoint.php @@ -0,0 +1,239 @@ +id = null; + } + + /** + * @return string|null + */ + public function getId(): ?string + { + return $this->id; + } + + /** + * @param string|null $id + * + * @return self + */ + public function setId(?string $id): self + { + $this->id = $id; + return $this; + } + + /** + * @return string|null + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * @param string|null $name + * + * @return self + */ + public function setName(?string $name): self + { + $this->name = $name; + return $this; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @param string|null $description + * + * @return self + */ + public function setDescription(?string $description): self + { + $this->description = $description; + return $this; + } + + /** + * @return string|null + */ + public function getMac(): ?string + { + return $this->mac; + } + + /** + * @param string|null $mac + * + * @return self + */ + public function setMac(?string $mac): self + { + $this->mac = $mac; + return $this; + } + + /** + * @return string|null + */ + public function getProfileId(): ?string + { + return $this->profileId; + } + + /** + * @param string|null $profileId + * + * @return self + */ + public function setProfileId(?string $profileId): self + { + $this->profileId = $profileId; + return $this; + } + + /** + * @return string|null + */ + public function getGroupId(): ?string + { + return $this->groupId; + } + + /** + * @param string|EndPointGroup|null $groupId + * + * @return self + */ + public function setGroupId($groupId): self + { + if ($groupId instanceof EndPointGroup) $groupId = $groupId->getId(); + $this->groupId = $groupId; + return $this; + } + + /** + * @return string|null + */ + public function getPortalUser(): ?string + { + return $this->portalUser; + } + + /** + * @param string|null $portalUser + * + * @return self + */ + public function setPortalUser(?string $portalUser): self + { + $this->portalUser = $portalUser; + return $this; + } + + /** + * @return string|null + */ + public function getIdentityStore(): ?string + { + return $this->identityStore; + } + + /** + * @param string|null $identityStore + * + * @return self + */ + public function setIdentityStore(?string $identityStore): self + { + $this->identityStore = $identityStore; + return $this; + } + + /** + * @return string|null + */ + public function getIdentityStoreId(): ?string + { + return $this->identityStoreId; + } + + /** + * @param string|null $id + * + * @return self + */ + public function setIdentityStoreId(?string $id): self + { + $this->identityStoreId = $id; + return $this; + } + + /** + * @return bool + */ + public function isStaticProfileAssignment(): bool + { + return $this->staticProfileAssignment; + } + + /** + * @param bool $flag + * + * @return self + */ + public function setStaticProfileAssignment(bool $flag): self + { + $this->staticProfileAssignment = $flag; + return $this; + } + + /** + * @return bool + */ + public function isStaticGroupAssignment(): bool + { + return $this->staticGroupAssignment; + } + + /** + * @param bool $flag + * + * @return self + */ + public function setStaticGroupAssignment(bool $flag): self + { + $this->staticGroupAssignment = $flag; + return $this; + } + +} \ No newline at end of file diff --git a/src/EndPointGroup.php b/src/EndPointGroup.php new file mode 100644 index 0000000..c9784a3 --- /dev/null +++ b/src/EndPointGroup.php @@ -0,0 +1,99 @@ +id = null; + } + + /** + * @return string|null + */ + public function getId(): ?string + { + return $this->id; + } + + /** + * @param string|null $id + * + * @return self + */ + public function setId(?string $id): self + { + $this->id = $id; + return $this; + } + + /** + * @return string|null + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * @param string|null $name + * + * @return self + */ + public function setName(?string $name): self + { + $this->name = $name; + return $this; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @param string|null $description + * + * @return self + */ + public function setDescription(?string $description): self + { + $this->description = $description; + return $this; + } + + /** + * @return bool + */ + public function isSystemDefined(): bool + { + return $this->systemDefined; + } + + /** + * @param bool $systemDefined + * + * @return self + */ + public function setSystemDefined(bool $systemDefined): self + { + $this->systemDefined = $systemDefined; + return $this; + } + + +} \ No newline at end of file diff --git a/src/ExceptionsTrait.php b/src/ExceptionsTrait.php new file mode 100644 index 0000000..8e75efe --- /dev/null +++ b/src/ExceptionsTrait.php @@ -0,0 +1,99 @@ + $limit ? substr($a, 0, $limit) . '...' : $a) . '"'; + break; + case $a instanceof \DateTime: + $list[] = sprintf('new DateTime("%s")', $a->format(DATE_RFC3339)); + break; + case is_callable($a): + $list[] = '{callable}'; + break; + case $a !== null && is_resource($a): + $list[] = '{' . get_resource_type($a) . '}'; + break; + default: + $list[] = "{arg$i}"; + } + } + } + return $list ? implode(', ', $list) : ''; + } + +} diff --git a/src/ISEError.php b/src/ISEError.php new file mode 100644 index 0000000..501b435 --- /dev/null +++ b/src/ISEError.php @@ -0,0 +1,41 @@ +error = $error; + $errors = $this->getErrorMessage(); + if ($errors) { + if ($msg) $msg .= ': '; + $msg .= $errors; + } + } + parent::__construct($msg); + } + + public function getError(): ?object + { + return $this->error; + } + + private function getErrorMessage(): string + { + $msg = []; + foreach ($this->error->messages as $err) { + $msg[] = $err->title; + } + return implode('. ', $msg); + } + +} \ No newline at end of file diff --git a/src/NetworkDevice.php b/src/NetworkDevice.php new file mode 100644 index 0000000..51b0efb --- /dev/null +++ b/src/NetworkDevice.php @@ -0,0 +1,281 @@ +id = null; + } + + /** + * @return string|null + */ + public function getId(): ?string + { + return $this->id; + } + + /** + * @param string|null $id + * + * @return NetworkDevice + */ + public function setId(string $id): self + { + $this->id = $id; + return $this; + } + + /** + * @return string|null + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * @param string|null $name + * + * @return NetworkDevice + */ + public function setName(?string $name): self + { + $this->name = $name; + return $this; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @param string|null $description + * + * @return NetworkDevice + */ + public function setDescription(?string $description): self + { + $this->description = $description; + return $this; + } + + /** + * @return string|null + */ + public function getDtlsDnsName(): ?string + { + return $this->dtlsDnsName; + } + + /** + * @param string|null $dtlsDnsName + * + * @return NetworkDevice + */ + public function setDtlsDnsName(?string $dtlsDnsName): self + { + $this->dtlsDnsName = $dtlsDnsName; + return $this; + } + + /** + * @return string|null + */ + public function getProfileName(): ?string + { + return $this->profileName; + } + + /** + * @param string|null $profileName + * + * @return NetworkDevice + */ + public function setProfileName(?string $profileName): self + { + $this->profileName = $profileName; + return $this; + } + + /** + * @return int|null + */ + public function getCoaPort(): ?int + { + return $this->coaPort; + } + + /** + * @param int|null $coaPort + * + * @return NetworkDevice + */ + public function setCoaPort(?int $coaPort): self + { + $this->coaPort = $coaPort; + return $this; + } + + /** + * @param AuthenticationSettings|object|null $settings + * + * @return NetworkDevice + */ + public function setAuthenticationSettings($settings): self + { + if ($settings && !$settings instanceof AuthenticationSettings) { + $settings = new AuthenticationSettings($settings); + } + $this->authenticationSettings = $settings; + return $this; + } + + /** + * @return AuthenticationSettings + */ + public function getAuthenticationSettings(): AuthenticationSettings + { + if (!$this->authenticationSettings) { + $this->authenticationSettings = new AuthenticationSettings(); + } + return $this->authenticationSettings; + } + + /** + * @param TacacsSettings|object|null $settings + * + * @return NetworkDevice + */ + public function setTacacsSettings($settings): self + { + if ($settings && !$settings instanceof TacacsSettings) { + $settings = new TacacsSettings($settings); + } + $this->tacacsSettings = $settings; + return $this; + } + + /** + * @return TacacsSettings + */ + public function getTacacsSettings(): TacacsSettings + { + if (!$this->tacacsSettings) { + $this->tacacsSettings = new TacacsSettings(); + } + return $this->tacacsSettings; + } + + /** + * @param NetworkDeviceIPList|array|null $ips + * + * @return NetworkDevice + */ + public function setNetworkDeviceIPList($ips): self + { + if ($ips !== null && !$ips instanceof NetworkDeviceIPList) { + $ips = new NetworkDeviceIPList($ips); + } + $this->networkDeviceIPList = $ips; + return $this; + } + + /** + * @return NetworkDeviceIPList + */ + public function getNetworkDeviceIPList(): NetworkDeviceIPList + { + if (!$this->networkDeviceIPList) { + $this->networkDeviceIPList = new NetworkDeviceIPList(); + } + return $this->networkDeviceIPList; + } + + /** + * @param NetworkDeviceGroupList|array|null $groups + * + * @return NetworkDevice + */ + public function setNetworkDeviceGroupList($groups): self + { + if ($groups !== null && !$groups instanceof NetworkDeviceGroupList) { + $groups = new NetworkDeviceGroupList($groups); + } + $this->networkDeviceGroupList = $groups; + return $this; + } + + /** + * @return NetworkDeviceGroupList + */ + public function getNetworkDeviceGroupList(): NetworkDeviceGroupList + { + if (!$this->networkDeviceGroupList) { + $this->networkDeviceGroupList = new NetworkDeviceGroupList(); + } + return $this->networkDeviceGroupList; + } + + /** + * @param SnmpSettings|array|null $settings + * + * @return NetworkDevice + */ + public function setSnmpSettings($settings): self + { + if ($settings !== null && !$settings instanceof SnmpSettings) { + $settings = new SnmpSettings($settings); + } + $this->snmpSettings = $settings; + return $this; + } + + /** + * @return SnmpSettings + */ + public function getSnmpSettings(): SnmpSettings + { + if (!$this->snmpSettings) { + $this->snmpSettings = new SnmpSettings(); + } + return $this->snmpSettings; + } + + +} \ No newline at end of file diff --git a/src/NetworkDeviceGroup.php b/src/NetworkDeviceGroup.php new file mode 100644 index 0000000..88e2488 --- /dev/null +++ b/src/NetworkDeviceGroup.php @@ -0,0 +1,98 @@ +id = null; + } + + /** + * @return string|null + */ + public function getId(): ?string + { + return $this->id; + } + + /** + * @param string|null $id + * + * @return self + */ + public function setId(?string $id): self + { + $this->id = $id; + return $this; + } + + /** + * @return string|null + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * @param string|null $name + * + * @return self + */ + public function setName(?string $name): self + { + $this->name = $name; + return $this; + } + + /** + * @return string|null + */ + public function getDescription(): ?string + { + return $this->description; + } + + /** + * @param string|null $description + * + * @return self + */ + public function setDescription(?string $description): self + { + $this->description = $description; + return $this; + } + + /** + * @return string|null + */ + public function getOthername(): ?string + { + return $this->othername; + } + + /** + * @param string|null $othername + * + * @return NetworkDeviceGroup + */ + public function setOthername(?string $othername): self + { + $this->othername = $othername; + return $this; + } + +} \ No newline at end of file diff --git a/src/NetworkDeviceGroupList.php b/src/NetworkDeviceGroupList.php new file mode 100644 index 0000000..f5abfce --- /dev/null +++ b/src/NetworkDeviceGroupList.php @@ -0,0 +1,9 @@ +extractIP($ip); + return parent::add($ip); + } + + /** + * Remove an IP from the list + * + * @param object|string $ip + * + * @return self + */ + public function remove($ip): self + { + $ip = $this->extractIP($ip); + $this->list = array_values(array_filter($this->list, + fn($item) => $item->ipaddress !== $ip->ipaddress && $item->mask !== $ip->mask + )); + + return $this; + } + + /** + * Returns true if the IP exists + * + * @param $ip + * + * @return bool + */ + public function exists($ip): bool + { + $ip = $this->extractIP($ip); + foreach ($this->list as $item) { + if ($item->ipaddress === $ip->ipaddress && $item->mask === $ip->mask) { + return true; + } + } + return false; + } + + public function getFirstIP(): ?string + { + $ip = $this->first(); + if (!$ip) return null; + if ($ip->mask === 32 || $ip->mask === 128) { + return $ip->ipaddress; + } + return $ip->ipaddress . '/' . $ip->mask; + } + + private function extractIP($ip): ?object + { + if (!$ip) return null; + if (is_array($ip)) $ip = (object)$ip; + if (!is_object($ip)) { + list($prefix, $mask) = array_pad(explode('/', $ip), 2, 32); + $ip = (object)[ + 'ipaddress' => $prefix, + 'mask' => $mask, + ]; + } + return $ip; + } +} \ No newline at end of file diff --git a/src/Node.php b/src/Node.php new file mode 100644 index 0000000..47c2fec --- /dev/null +++ b/src/Node.php @@ -0,0 +1,317 @@ +id = null; + } + + /** + * @return string|null + */ + public function getId(): ?string + { + return $this->id; + } + + /** + * @param string|null $id + * + * @return self + */ + public function setId(string $id): self + { + $this->id = $id; + return $this; + } + + /** + * @return string|null + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * @param string|null $name + * + * @return self + */ + public function setName(?string $name): self + { + $this->name = $name; + return $this; + } + + /** + * @return string|null + */ + public function getGateway(): ?string + { + return $this->gateway; + } + + /** + * @param string|null $gateway + * + * @return self + */ + public function setGateway(?string $gateway): self + { + $this->gateway = $gateway; + return $this; + } + + /** + * @return string|null + */ + public function getUsername(): ?string + { + return $this->username; + } + + /** + * @param string|null $username + * + * @return self + */ + public function setUsername(?string $username): self + { + $this->username = $username; + return $this; + } + + /** + * @return string|null + */ + public function getPassword(): ?string + { + return $this->password; + } + + /** + * @param string|null $password + * + * @return self + */ + public function setPassword(?string $password): self + { + $this->password = $password; + return $this; + } + + /** + * @return string|null + */ + public function getDisplayName(): ?string + { + return $this->displayName; + } + + /** + * @param string|null $displayName + * + * @return self + */ + public function setDisplayName(?string $displayName): self + { + $this->displayName = $displayName; + return $this; + } + + /** + * @return bool|null + */ + public function getInDeployment(): ?bool + { + return $this->inDeployment; + } + + /** + * @param bool|null $inDeployment + * + * @return self + */ + public function setInDeployment(?bool $inDeployment): self + { + $this->inDeployment = $inDeployment; + return $this; + } + + /** + * @return string|null + */ + public function getOtherPapFqdn(): ?string + { + return $this->otherPapFqdn; + } + + /** + * @param string|null $otherPapFqdn + * + * @return self + */ + public function setOtherPapFqdn(?string $otherPapFqdn): self + { + $this->otherPapFqdn = $otherPapFqdn; + return $this; + } + + /** + * @return string|null + */ + public function getIpAddress(): ?string + { + return $this->ipAddress; + } + + /** + * @param string|null $ipAddress + * + * @return self + */ + public function setIpAddress(?string $ipAddress): self + { + $this->ipAddress = $ipAddress; + return $this; + } + + /** + * @return array + */ + public function getIpAddresses(): array + { + return $this->ipAddresses; + } + + /** + * @param array $ipAddresses + * + * @return self + */ + public function setIpAddresses(array $ipAddresses): self + { + $this->ipAddresses = $ipAddresses; + return $this; + } + + /** + * @return string|null + */ + public function getNodeServiceTypes(): ?string + { + return $this->nodeServiceTypes; + } + + /** + * @param string|null $nodeServiceTypes + * + * @return self + */ + public function setNodeServiceTypes(?string $nodeServiceTypes): self + { + $this->nodeServiceTypes = $nodeServiceTypes; + return $this; + } + + /** + * @return bool|null + */ + public function getPrimaryPapNode(): ?bool + { + return $this->primaryPapNode; + } + + /** + * @param bool|null $primaryPapNode + * + * @return self + */ + public function setPrimaryPapNode(?bool $primaryPapNode): self + { + $this->primaryPapNode = $primaryPapNode; + return $this; + } + + /** + * @return bool|null + */ + public function getPapNode(): ?bool + { + return $this->papNode; + } + + /** + * @param bool|null $papNode + * + * @return self + */ + public function setPapNode(?bool $papNode): self + { + $this->papNode = $papNode; + return $this; + } + + /** + * @return bool|null + */ + public function getPxGridNode(): ?bool + { + return $this->pxGridNode; + } + + /** + * @param bool|null $pxGridNode + * + * @return self + */ + public function setPxGridNode(?bool $pxGridNode): self + { + $this->pxGridNode = $pxGridNode; + return $this; + } + + +} \ No newline at end of file diff --git a/src/ObjectInterface.php b/src/ObjectInterface.php new file mode 100644 index 0000000..61eaadd --- /dev/null +++ b/src/ObjectInterface.php @@ -0,0 +1,41 @@ +total = $this->extractProperty('total', $this->result, 0); + $this->limit = 0; + $this->hydrate = false; + } + + public function getIterator(): CiscoISEIterator + { + $list = $this->getResources(); + $next = $this->getNextPage(); + $it = new CiscoISEIterator($this->ise, $this->total, $list, $next ? $next->href : null, $this->hydrate); + if ($this->limit) { + $it->setLimit($this->limit); + } + return $it; + } + + /** + * Get total items in result + * + * @return int + */ + public function getTotal(): int + { + return $this->total; + } + + /** + * Return the current resource set. + * + * @return object[]|null + */ + public function getResources(): ?array + { + return $this->extractProperty('resources', $this->result); + } + + /** + * Return the 'next page' object + * + * @param bool $hrefOnly Only return the HREF property + * + * @return mixed + */ + public function getNextPage($hrefOnly = false) + { + $next = $this->extractProperty('nextPage', $this->result); + if ($hrefOnly && $next) { + $next = $next->href; + } + return $next; + } + + /** + * @return int + */ + public function getLimit(): int + { + return $this->limit; + } + + /** + * @param int|null $limit + * + * @return SearchResult + */ + public function setLimit(?int $limit): SearchResult + { + $this->limit = $limit ?? 0; + + return $this; + } + + /** + * @return bool + */ + public function isHydrate(): bool + { + return $this->hydrate; + } + + /** + * @param bool $hydrate + * + * @return SearchResult + */ + public function setHydrate(bool $hydrate): SearchResult + { + $this->hydrate = $hydrate; + + return $this; + } +} \ No newline at end of file diff --git a/src/SnmpSettings.php b/src/SnmpSettings.php new file mode 100644 index 0000000..d395d92 --- /dev/null +++ b/src/SnmpSettings.php @@ -0,0 +1,299 @@ +version; + } + + /** + * @param string|null $version + * + * @return SnmpSettings + */ + public function setVersion(?string $version): SnmpSettings + { + switch (true) { + case $version === null: + break; + case in_array(strtolower($version), ['1', 'one']): + $version = 'ONE'; + break; + case in_array(strtolower($version), ['2', '2c', 'two', 'two_c']): + $version = 'TWO_C'; + break; + case in_array(strtolower($version), ['3', 'three']): + $version = 'THREE'; + break; + } + $this->version = $version; + return $this; + } + + /** + * @return string|null + */ + public function getUsername(): ?string + { + return $this->username; + } + + /** + * @param string|null $username + * + * @return SnmpSettings + */ + public function setUsername(?string $username): SnmpSettings + { + $this->username = $username; + return $this; + } + + /** + * @return string|null + */ + public function getSecurityLevel(): ?string + { + return $this->securityLevel; + } + + /** + * @param string|null $securityLevel + * + * @return SnmpSettings + */ + public function setSecurityLevel(?string $securityLevel): SnmpSettings + { + $this->securityLevel = $securityLevel; + return $this; + } + + /** + * @return string|null + */ + public function getAuthProtocol(): ?string + { + return $this->authProtocol; + } + + /** + * @param string|null $authProtocol + * + * @return SnmpSettings + */ + public function setAuthProtocol(?string $authProtocol): SnmpSettings + { + $this->authProtocol = $authProtocol; + return $this; + } + + /** + * @return string|null + */ + public function getAuthPassword(): ?string + { + return $this->authPassword; + } + + /** + * @param string|null $authPassword + * + * @return SnmpSettings + */ + public function setAuthPassword(?string $authPassword): SnmpSettings + { + $this->authPassword = $authPassword; + return $this; + } + + /** + * @param string|null $pw + * + * @return SnmpSettings + * @deprecated Due to typo in ISE API (note the 'Passowrd' instead of 'Password'). Use {@link setAuthPassword} instead. + * @internal This is here only so the PropertyAccessor will properly set the password + */ + public function setAuthPassowrd(?string $pw): SnmpSettings + { + return $this->setAuthPassword($pw); + } + + /** + * @return string|null + */ + public function getPrivacyProtocol(): ?string + { + return $this->privacyProtocol; + } + + /** + * @param string|null $privacyProtocol + * + * @return SnmpSettings + */ + public function setPrivacyProtocol(?string $privacyProtocol): SnmpSettings + { + $this->privacyProtocol = $privacyProtocol; + return $this; + } + + /** + * @return string|null + */ + public function getPrivacyPassword(): ?string + { + return $this->privacyPassword; + } + + /** + * @param string|null $privacyPassword + * + * @return SnmpSettings + */ + public function setPrivacyPassword(?string $privacyPassword): SnmpSettings + { + $this->privacyPassword = $privacyPassword; + return $this; + } + + /** + * + * @param string|null $pw + * + * @return SnmpSettings + * @internal This is here only so the PropertyAccessor will properly set the password + * @deprecated Due to typo in ISE API (note the 'Passowrd' instead of 'Password'). Use {@link setPrivacyPassword} instead. + */ + public function setPrivacyPassowrd(?string $pw): SnmpSettings + { + return $this->setPrivacyPassword($pw); + } + + /** + * @return string|null + */ + public function getRoCommunity(): ?string + { + return $this->roCommunity; + } + + /** + * @param string|null $roCommunity + * + * @return SnmpSettings + */ + public function setRoCommunity(?string $roCommunity): SnmpSettings + { + $this->roCommunity = $roCommunity; + return $this; + } + + /** + * @return string|null + */ + public function getOriginatingPolicyServicesNode(): ?string + { + return $this->originatingPolicyServicesNode; + } + + /** + * @param string|null $originatingPolicyServicesNode + * + * @return SnmpSettings + */ + public function setOriginatingPolicyServicesNode(?string $originatingPolicyServicesNode): SnmpSettings + { + $this->originatingPolicyServicesNode = $originatingPolicyServicesNode; + return $this; + } + + /** + * @return int|null + */ + public function getPollingInterval(): ?int + { + return $this->pollingInterval; + } + + /** + * @param int|null $pollingInterval + * + * @return SnmpSettings + */ + public function setPollingInterval(?int $pollingInterval): SnmpSettings + { + $this->pollingInterval = $pollingInterval; + return $this; + } + + /** + * @return bool + */ + public function isLinkTrapQuery(): bool + { + return $this->linkTrapQuery; + } + + /** + * @param bool $linkTrapQuery + * + * @return SnmpSettings + */ + public function setLinkTrapQuery(bool $linkTrapQuery): SnmpSettings + { + $this->linkTrapQuery = $linkTrapQuery; + return $this; + } + + /** + * @return bool + */ + public function isMacTrapQuery(): bool + { + return $this->macTrapQuery; + } + + /** + * @param bool $macTrapQuery + * + * @return SnmpSettings + */ + public function setMacTrapQuery(bool $macTrapQuery): SnmpSettings + { + $this->macTrapQuery = $macTrapQuery; + return $this; + } + +} \ No newline at end of file diff --git a/src/TacacsSettings.php b/src/TacacsSettings.php new file mode 100644 index 0000000..ad38452 --- /dev/null +++ b/src/TacacsSettings.php @@ -0,0 +1,90 @@ +sharedSecret; + } + + /** + * @param string|null $sharedSecret + * + * @return TacacsSettings + */ + public function setSharedSecret(?string $sharedSecret): TacacsSettings + { + $this->sharedSecret = $sharedSecret; + return $this; + } + + /** + * @return string|null + */ + public function getConnectModeOptions(): ?string + { + return $this->connectModeOptions; + } + + /** + * @param string|null $connectModeOptions + * + * @return TacacsSettings + */ + public function setConnectModeOptions(?string $connectModeOptions): TacacsSettings + { + $this->connectModeOptions = $connectModeOptions; + return $this; + } + + /** + * @return string|null + */ + public function getPreviousSharedSecret(): ?string + { + return $this->previousSharedSecret; + } + + /** + * @param string|null $previousSharedSecret + * + * @return TacacsSettings + */ + public function setPreviousSharedSecret(?string $previousSharedSecret): TacacsSettings + { + $this->previousSharedSecret = $previousSharedSecret; + return $this; + } + + /** + * @return int|null + */ + public function getPreviousSharedSecretExpiry(): ?int + { + return $this->previousSharedSecretExpiry; + } + + /** + * @param int|null $previousSharedSecretExpiry + * + * @return TacacsSettings + */ + public function setPreviousSharedSecretExpiry(?int $previousSharedSecretExpiry): TacacsSettings + { + $this->previousSharedSecretExpiry = $previousSharedSecretExpiry; + return $this; + } + +} \ No newline at end of file diff --git a/src/VersionInfo.php b/src/VersionInfo.php new file mode 100644 index 0000000..1e8e630 --- /dev/null +++ b/src/VersionInfo.php @@ -0,0 +1,64 @@ +currentServerVersion; + } + + /** + * @param string|null $currentServerVersion + * + * @return self + */ + public function setCurrentServerVersion(?string $currentServerVersion): self + { + $this->currentServerVersion = $currentServerVersion; + return $this; + } + + /** + * @return array + */ + public function getSupportedVersions(): array + { + return $this->supportedVersions; + } + + /** + * @param string|string[] $versions + * + * @return self + */ + public function setSupportedVersions($versions): self + { + $this->supportedVersions = array_map(fn($v) => trim($v), (is_string($versions) ? explode(',', $versions) : $versions) ?? []); + return $this; + } + + /** + * Returns true if the version provided is supported (<= currentServerVersion) + * + * @param string|null $ver + * + * @return bool + */ + public function supports(?string $ver): bool + { + if (!$ver || !$this->currentServerVersion) return false; + return version_compare($this->currentServerVersion, $ver, '>='); + } +} \ No newline at end of file