From ae5031cf25dbe347c67e21d7b73bf9dd5745d888 Mon Sep 17 00:00:00 2001 From: Vladimir Prusakov Date: Tue, 2 May 2023 18:24:24 +0300 Subject: [PATCH] 1.2.4 (2023-05-02) ------------------ * [fix] Fortigate._valid_url() --- CHANGELOG.rst | 5 +++ README.rst | 7 ++-- examples/address.py | 10 +++--- examples/interface.py | 38 ++++++++++++-------- examples/monitor.py | 73 ++++++++++++++++++++++++++++++++++++++ fortigate_api/fortigate.py | 13 ++++--- pyproject.toml | 4 +-- tests/helper__tst.py | 10 +++--- tests/test__fortigate.py | 22 ++++++------ 9 files changed, 140 insertions(+), 42 deletions(-) create mode 100644 examples/monitor.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1564d148..6b899dab 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,11 @@ CHANGELOG ========= +1.2.4 (2023-05-02) +------------------ +* [fix] Fortigate._valid_url() + + 1.2.3 (2023-04-27) ------------------ * [feat] poetry diff --git a/README.rst b/README.rst index 5cbbbd22..6fc18986 100644 --- a/README.rst +++ b/README.rst @@ -49,7 +49,7 @@ or install the package from github.com release .. code:: bash - pip install https://github.com/vladimirs-git/fortigate-api/archive/refs/tags/1.2.3.tar.gz + pip install https://github.com/vladimirs-git/fortigate-api/archive/refs/tags/1.2.4.tar.gz or install the package from github.com repository @@ -866,6 +866,8 @@ Python examples `./examples/fortigate.py`_ Python examples `./examples/fortigate_token.py`_ +Python examples `./examples/monitor.py`_ + .. code:: python from fortigate_api import Fortigate @@ -1084,14 +1086,15 @@ Return .. _`./examples/yml/zone.yml`: ./examples/yml/zone.yml .. _`./examples/address.py`: ./examples/address.py -.. _`./examples/address_token.py`: ./examples/address_token.py .. _`./examples/address_group.py`: ./examples/address_group.py +.. _`./examples/address_token.py`: ./examples/address_token.py .. _`./examples/dhcp_server.py`: ./examples/dhcp_server.py .. _`./examples/external_resource.py`: ./examples/external_resource.py .. _`./examples/fortigate.py`: ./examples/fortigate.py .. _`./examples/fortigate_token.py`: ./examples/fortigate_token.py .. _`./examples/interface.py`: ./examples/interface.py .. _`./examples/ip_pool.py`: ./examples/ip_pool.py +.. _`./examples/monitor.py`: ./examples/monitor.py .. _`./examples/policy.py`: ./examples/policy.py .. _`./examples/policy_extended_filter.py`: ./examples/policy_extended_filter.py .. _`./examples/snmp_community.py`: ./examples/snmp_community.py diff --git a/examples/address.py b/examples/address.py index 24fb3e84..ddd3749d 100644 --- a/examples/address.py +++ b/examples/address.py @@ -29,10 +29,12 @@ fgt.login() # Create Address -data = {"name": "ADDRESS", - "obj-type": "ip", - "subnet": "127.0.0.100 255.255.255.252", - "type": "ipmask"} +data = { + "name": "ADDRESS", + "obj-type": "ip", + "subnet": "127.0.0.100 255.255.255.252", + "type": "ipmask", +} response = fgt.address.create(data=data) print("address.create", response) # address.create diff --git a/examples/interface.py b/examples/interface.py index 2502ff08..a58dd098 100644 --- a/examples/interface.py +++ b/examples/interface.py @@ -8,7 +8,9 @@ - Filter interface by multiple conditions - Update interface data in the Fortigate - Check for presence of interface in the Fortigate -- Get all interfaces in vdom "VDOM" +- Change virtual domain to VDOM and get all interfaces of this virtual domain +- Change virtual domain to root and get all interfaces of this virtual domain +- Get all interfaces in all virtual domains (root and VDOM) """ import logging @@ -25,11 +27,11 @@ fgt = FortigateAPI(host=HOST, username=USERNAME, password=PASSWORD) fgt.login() -print("\nGets all interfaces in vdom \"root\" from the Fortigate") +print("\nGet all interfaces in vdom \"root\" from the Fortigate") interfaces = fgt.interface.get() print(f"interfaces count={len(interfaces)}") # interfaces count=21 -print("\nGets filtered interface by name (unique identifier)") +print("\nGet filtered interface by name (unique identifier)") interfaces = fgt.interface.get(uid="dmz") pprint(interfaces) # [{"name": "dmz", @@ -37,41 +39,49 @@ # ... # }] -print("\nFilters interface by operator equals \"==\"") +print("\nFilter interface by operator equals \"==\"") interfaces = fgt.interface.get(filter="name==dmz") print(f"interfaces count={len(interfaces)}") # interfaces count=1 -print("\nFilters interface by operator contains \"=@\"") +print("\nFilter interface by operator contains \"=@\"") interfaces = fgt.interface.get(filter="name=@wan") print(f"interfaces count={len(interfaces)}") # interfaces count=2 -print("\nFilters interface by operator not equals \"!=\"") +print("\nFilter interface by operator not equals \"!=\"") interfaces = fgt.interface.get(filter="name!=dmz") print(f"interfaces count={len(interfaces)}") # interfaces count=20 -print("\nFilters interface by multiple conditions") +print("\nFilter interface by multiple conditions") interfaces = fgt.interface.get(filter=["allowaccess=@ping", "detectprotocol==ping"]) print(f"interfaces count={len(interfaces)}") # interfaces count=8 -print("\nUpdates interface data in the Fortigate") +print("\nUpdate interface data in the Fortigate") data = dict(name="dmz", description="dmz") response = fgt.interface.update(uid="dmz", data=data) print("interface.update", response) # interface.update -print("\nChecks for presence of interface in the Fortigate") +print("\nCheck for presence of interface in the Fortigate") response = fgt.interface.is_exist(uid="dmz") print("interface.is_exist", response) # interface.is_exist True -print("\nChanges virtual domain to \"VDOM\" and gets all interfaces inside this vdom") +# Interfaces in virtual domains + +print("\nChange virtual domain to VDOM and get all interfaces of this virtual domain") fgt.rest.vdom = "VDOM" -print(f"{fgt!r}") # Fortigate(host='host', username='username', password='********', vdom='VDOM') +print(f"{fgt!r}") # Fortigate(host='host', username='username', vdom='VDOM') print(fgt.vdom) # VDOM interfaces = fgt.interface.get() -print(f"interfaces count={len(interfaces)}") # interfaces count=0 +print(f"interfaces count={len(interfaces)}") # interfaces count=12 -print("\nChanges virtual domain to \"root\"") +print("\nChange virtual domain to root and get all interfaces of this virtual domain") fgt.vdom = "root" -print(f"{fgt!r}") # Fortigate(host='host', username='username', password='********') +print(f"{fgt!r}") # Fortigate(host='host', username='username') print(fgt.vdom) # root +interfaces = fgt.interface.get() +print(f"interfaces count={len(interfaces)}") # interfaces count=31 + +print("\nGet all interfaces in all virtual domains (root and VDOM)") +interfaces = fgt.interface.get(all=True) +print(f"interfaces count={len(interfaces)}") # interfaces count=43 fgt.logout() diff --git a/examples/monitor.py b/examples/monitor.py new file mode 100644 index 00000000..c7f919bb --- /dev/null +++ b/examples/monitor.py @@ -0,0 +1,73 @@ +"""Monitor examples. + +- Get directory of monitor options (schema) +- Get all ipv4 routes +- Get static ipv4 routes +- Get route to interested ip address +""" +import logging +from pprint import pprint + +from fortigate_api import Fortigate + +logging.getLogger().setLevel(logging.DEBUG) + +HOST = "host" +USERNAME = "username" +PASSWORD = "password" + +fgt = Fortigate(host=HOST, username=USERNAME, password=PASSWORD) + +# Get directory of monitor options (schema) +directory = fgt.directory("api/v2/monitor") +pprint(directory) +# [{"access_group": "sysgrp.cfg", +# "action": "select", +# "name": "health", +# "path": "firewall", +# "request": {"http_method": "GET"}, +# "response": {"type": "array"}, +# "summary": "List configured load balance server health monitors.", +# "supported": True}, +# ... + +# Get all ipv4 routes +routes = fgt.get(url="api/v2/monitor/router/ipv4") +pprint(routes) +# [{"distance": 20, +# "gateway": "10.0.0.1", +# "install_date": 1681965487, +# "interface": "tunnel1", +# "ip_mask": "10.0.0.0/8", +# "ip_version": 4, +# "is_tunnel_route": True, +# "metric": 100, +# "priority": 1, +# "tunnel_parent": "tunnel1", +# "type": "bgp", +# "vrf": 0}, +# ... +# ] + +# Get static ipv4 routes +routes = fgt.get(url="api/v2/monitor/router/ipv4?type=static") +pprint(routes) +# [{"distance": 10, +# "gateway": "10.0.1.1", +# "interface": "wan1", +# "ip_mask": "0.0.0.0/0", +# "ip_version": 4, +# "metric": 0, +# "priority": 1, +# "type": "static", +# "vrf": 0}, +# ... +# ] + +# Get route to interested ip address +routes = fgt.get(url="api/v2/monitor/router/lookup?destination=10.1.1.1") +pprint(routes) +# {"gateway": "10.0.0.1", +# "interface": "tunnel1", +# "network": "10.0.0.0/10", +# "success": True} diff --git a/fortigate_api/fortigate.py b/fortigate_api/fortigate.py index a426c0bc..aff4c78a 100644 --- a/fortigate_api/fortigate.py +++ b/fortigate_api/fortigate.py @@ -10,7 +10,7 @@ import logging import re from typing import Callable, Iterable, Optional -from urllib.parse import urlencode +from urllib.parse import urlencode, urljoin import requests from requests import Session, Response @@ -423,8 +423,11 @@ def _response(self, method: Method, url: str, data: ODAny = None) -> Response: return response def _valid_url(self, url: str) -> str: - """Add "https://" to `url` if absent.""" + """Return a valid URL string. + + Add "https://" to `url` if it is absent and remove any trailing "/" character. + """ if re.match("http(s)?://", url): - return url - url = url.strip("/") - return f"{self.url}/{url}/" + return url.rstrip("/") + path = url.strip("/") + return urljoin(self.url, path) diff --git a/pyproject.toml b/pyproject.toml index d0b95073..b60cc4ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "fortigate_api" -version = "1.2.3" +version = "1.2.4" description = "Python package to configure Fortigate (Fortios) devices using REST API and SSH" authors = ["Vladimir Prusakov "] license = "MIT" @@ -44,4 +44,4 @@ test = ["pytest"] [tool.poetry.urls] "Bug Tracker" = "https://github.com/vladimirs-git/fortigate-api/issues" -"Download URL" = "https://github.com/vladimirs-git/fortigate-api/archive/refs/tags/1.2.3.tar.gz" +"Download URL" = "https://github.com/vladimirs-git/fortigate-api/archive/refs/tags/1.2.4.tar.gz" diff --git a/tests/helper__tst.py b/tests/helper__tst.py index b9762f37..03a9b1a8 100644 --- a/tests/helper__tst.py +++ b/tests/helper__tst.py @@ -111,13 +111,13 @@ class MockResponse(Response): f"api/v2/cmdb/antivirus/profile/{NAME1}": [D_NAME1], f"api/v2/cmdb/application/list/{NAME1}": [D_NAME1], - "api/v2/cmdb/firewall/address/": [D_ADDR1, D_ADDR2, D_ADDR3, D_ADDR4, D_ADDR5], + "api/v2/cmdb/firewall/address": [D_ADDR1, D_ADDR2, D_ADDR3, D_ADDR4, D_ADDR5], f"api/v2/cmdb/firewall/address/?filter=name{EQ}{ADDR1}": [D_ADDR1], f"api/v2/cmdb/firewall/address/{ADDR1}": [D_ADDR1], f"api/v2/cmdb/firewall/address/{ADDR1}?filter=name{EQ}{ADDR1}": [D_ADDR1], f"api/v2/cmdb/firewall/address/{SLASH_}": [D_SLASH], - "api/v2/cmdb/firewall/addrgrp/": [D_ADDGR1], + "api/v2/cmdb/firewall/addrgrp": [D_ADDGR1], f"api/v2/cmdb/firewall/addrgrp/?filter=name{EQ}{ADDGR1}": [D_ADDGR1], f"api/v2/cmdb/firewall/addrgrp/{ADDGR1}": [D_ADDGR1], f"api/v2/cmdb/firewall/addrgrp/{ADDGR1}?filter=name{EQ}{ADDGR1}": [D_ADDGR1], @@ -126,7 +126,7 @@ class MockResponse(Response): f"api/v2/cmdb/firewall/ippool/{NAME1}": [D_NAME1], f"api/v2/cmdb/firewall/vip/{NAME1}": [D_NAME1], - "api/v2/cmdb/firewall/policy/": [D_POL1, D_POL3], + "api/v2/cmdb/firewall/policy": [D_POL1, D_POL3], "api/v2/cmdb/firewall/policy/1": [D_POL1], "api/v2/cmdb/firewall/policy/3": [D_POL3], f"api/v2/cmdb/firewall/policy/1?filter=name{EQ}{POL1}": [D_POL1], @@ -141,7 +141,7 @@ class MockResponse(Response): f"api/v2/cmdb/firewall.service/custom/{NAME1}": [D_NAME1], f"api/v2/cmdb/firewall.service/group/{NAME1}": [D_NAME1], - "api/v2/cmdb/system.snmp/community/": [D_SNMP1, D_SNMP3], + "api/v2/cmdb/system.snmp/community": [D_SNMP1, D_SNMP3], "api/v2/cmdb/system.snmp/community/1": [D_SNMP1], "api/v2/cmdb/system.snmp/community/3": [D_SNMP3], f"api/v2/cmdb/system.snmp/community/1?filter=name{EQ}{NAME1}": [D_SNMP1], @@ -154,7 +154,7 @@ class MockResponse(Response): f"api/v2/cmdb/system/external-resource/{NAME1}": [D_NAME1], f"api/v2/cmdb/system/external-resource/?filter=name{EQ}{NAME1}": [D_NAME1], - "api/v2/cmdb/system/interface/": [D_INTF1, D_INTF3], + "api/v2/cmdb/system/interface": [D_INTF1, D_INTF3], f"api/v2/cmdb/system/interface/?filter=name{EQ}{NAME1}": [D_INTF1], f"api/v2/cmdb/system/interface/?filter=name{EQ}{NAME3}": [D_INTF3], f"api/v2/cmdb/system/interface/{NAME1}": [D_INTF1], diff --git a/tests/test__fortigate.py b/tests/test__fortigate.py index 6d5c1d34..97e6fcaa 100644 --- a/tests/test__fortigate.py +++ b/tests/test__fortigate.py @@ -231,17 +231,19 @@ def test_valid__hide_secret(self): def test_valid__valid_url(self): """Fortigate._valid_url()""" default = dict(host="host", username="username", password="", port=443) - query = "api/v2/cmdb/firewall/address/" - url_ = f"https://host/{query}" + query = "api/v2/cmdb/firewall/address" + full_url = f"https://host/{query}" + https_80 = "https://host:80/" + http_80 = "http://host:80/" for kwargs, url, req in [ - ({}, query, url_), - ({}, f"/{query}", url_), - ({}, "api/v2/cmdb/firewall/address", url_), - ({}, url_, url_), - (dict(port=80), query, f"https://host:80/{query}"), - (dict(port=80), f"https://host:80/{query}", f"https://host:80/{query}"), - (dict(scheme="http", port=80), query, f"http://host:80/{query}"), - (dict(scheme="http", port=80), f"http://host:80/{query}", f"http://host:80/{query}"), + ({}, query, full_url), + ({}, f"/{query}/", full_url), + ({}, full_url, full_url), + ({}, f"{full_url}/", full_url), + (dict(port=80), query, f"{https_80}{query}"), + (dict(port=80), f"{https_80}{query}", f"{https_80}{query}"), + (dict(scheme="http", port=80), query, f"{http_80}{query}"), + (dict(scheme="http", port=80), f"{http_80}{query}", f"{http_80}{query}"), ]: kwargs_ = {**default, **kwargs} fgt = Fortigate(**kwargs_)