Skip to content

Commit ceeb2a2

Browse files
committed
pull_request_28
1 parent 401b2c7 commit ceeb2a2

File tree

6 files changed

+88
-75
lines changed

6 files changed

+88
-75
lines changed

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
project = "fortigate-api"
99
copyright = "2021, Vladimirs Prusakovs"
1010
author = "Vladimirs Prusakovs"
11-
release = "2.0.1"
11+
release = "2.0.2"
1212

1313
extensions = [
1414
"sphinx.ext.autodoc",

fortigate_api/fortigate.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,16 @@ def __init__( # pylint: disable=too-many-arguments
4444
:param int timeout: Session timeout (minutes). Default is 15.
4545
4646
:param bool verify: Transport Layer Security.
47-
`True` - A TLS certificate required,
47+
`True` - A trusted TLS certificate is required.
4848
`False` - Requests will accept any TLS certificate. Default is `False`.
4949
5050
:param str vdom: Name of the virtual domain. Default is `root`.
5151
5252
:param bool logging: Logging REST API response.
53-
`Ture` - Enable response logging, `False` - otherwise. Default is `False`.
53+
`True` - Enable response logging, `False` - otherwise. Default is `False`.
5454
5555
:param bool logging_error: Logging only the REST API response with error.
56-
`Ture` - Enable errors logging, `False` - otherwise. Default is `False`.
56+
`True` - Enable errors logging, `False` - otherwise. Default is `False`.
5757
"""
5858
kwargs = {
5959
"host": host,
@@ -74,8 +74,8 @@ def __init__( # pylint: disable=too-many-arguments
7474
def login(self) -> None: # pylint: disable=useless-parent-delegation
7575
"""Login to the Fortigate using REST API and creates a Session object.
7676
77-
- Validate 'token' if object has been initialized with `token` parameter.
78-
- Validate `password` if object has been initialized with `username` parameter.
77+
- Validate `token` if object has been initialized with `token` parameter.
78+
- Validate `password` if object has been initialized with `username` parameter.
7979
8080
:return: None. Creates Session object.
8181
"""
@@ -84,7 +84,7 @@ def login(self) -> None: # pylint: disable=useless-parent-delegation
8484
def logout(self) -> None: # pylint: disable=useless-parent-delegation
8585
"""Logout from the Fortigate using REST API, deletes Session object.
8686
87-
- No need to logo ut if object has been initialized with `token` parameter.
87+
- No need to log out if object has been initialized with `token` parameter.
8888
- Log out if object has been initialized with `username` parameter.
8989
9090
:return: None. Deletes Session object

fortigate_api/fortigate_api.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,17 @@ def __init__(
4444
:param int timeout: Session timeout (minutes). Default is 15.
4545
4646
:param bool verify: Transport Layer Security.
47-
`True` - A TLS certificate required,
47+
`True` - A trusted TLS certificate is required.
4848
`False` - Requests will accept any TLS certificate.
4949
Default is `False`.
5050
5151
:param str vdom: Name of the virtual domain. Default is `root`.
5252
5353
:param bool logging: Logging REST API response.
54-
`Ture` - Enable response logging, `False` - otherwise. Default is `False`.
54+
`True` - Enable response logging, `False` - otherwise. Default is `False`.
5555
5656
:param bool logging_error: Logging only the REST API response with error.
57-
`Ture` - Enable errors logging, `False` - otherwise. Default is `False`.
57+
`True` - Enable errors logging, `False` - otherwise. Default is `False`.
5858
"""
5959
api_params = {
6060
"host": host,
@@ -117,7 +117,7 @@ def login(self) -> None:
117117
"""Login to the Fortigate using REST API and creates a Session.
118118
119119
- Validate `token` if object has been initialized with `token` parameter.
120-
- Validate `password` if object has been initialized with `username` parameter.
120+
- Validate `password` if object has been initialized with `username` parameter.
121121
122122
:return: None. Creates Session.
123123
"""

fortigate_api/fortigate_base.py

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ def __init__(self, **kwargs):
5353
:param str vdom: Name of the virtual domain. Default is `root`.
5454
5555
:param bool logging: Logging REST API response.
56-
`Ture` - Enable response logging, `False` - otherwise. Default is `False`.
56+
`True` - Enable response logging, `False` - otherwise. Default is `False`.
5757
5858
:param bool logging_error: Logging only the REST API response with error.
59-
`Ture` - Enable errors logging, `False` - otherwise. Default is `False`.
59+
`True` - Enable errors logging, `False` - otherwise. Default is `False`.
6060
"""
6161
self.host = str(kwargs.get("host"))
6262
self.username = str(kwargs.get("username"))
@@ -133,8 +133,8 @@ def url(self) -> str:
133133
def login(self) -> None:
134134
"""Login to the Fortigate using REST API and creates a Session object.
135135
136-
- Validate 'token' if object has been initialized with `token` parameter.
137-
- Validate `password` if object has been initialized with `username` parameter.
136+
- Validate `token` if object has been initialized with `token` parameter.
137+
- Validate `password` if object has been initialized with `username` parameter.
138138
139139
:return: None. Creates Session object.
140140
"""
@@ -144,7 +144,7 @@ def login(self) -> None:
144144
if self.token:
145145
try:
146146
response: Response = session.get(
147-
url=f"{self.url}/api/v2/cmdb/system/status",
147+
url=f"{self.url}/api/v2/monitor/system/status",
148148
headers=self._bearer_token(),
149149
verify=self.verify,
150150
)
@@ -156,20 +156,17 @@ def login(self) -> None:
156156

157157
# password
158158
try:
159-
session.post(
159+
response = session.post(
160160
url=f"{self.url}/logincheck",
161161
data=urlencode([("username", self.username), ("secretkey", self.password)]),
162162
timeout=self.timeout,
163163
verify=self.verify,
164164
)
165165
except Exception as ex:
166166
raise self._hide_secret_ex(ex)
167-
167+
response.raise_for_status()
168168
token = self._get_token_from_cookies(session)
169169
session.headers.update({"X-CSRFTOKEN": token})
170-
171-
response = session.get(url=f"{self.url}/api/v2/cmdb/system/vdom")
172-
response.raise_for_status()
173170
self._session = session
174171

175172
def logout(self) -> None:
@@ -220,21 +217,12 @@ def _get_token_from_cookies(session: Session) -> str:
220217
221218
:raises ValueError: If the ccsrftoken cookie is absent.
222219
"""
223-
while True:
224-
# fortios < v7
225-
cookie_name = "ccsrftoken"
226-
if cookies := [o for o in session.cookies if o and o.name == cookie_name]:
227-
break
228-
229-
# fortios >= v7
230-
cookie_name += "_"
231-
if cookies := [o for o in session.cookies if o and o.name.startswith(cookie_name)]:
232-
break
233-
234-
raise ValueError("Invalid login credentials. Cookie 'ccsrftoken' is missing.")
235-
236-
token = str(cookies[0].value).strip('"')
237-
return token
220+
cookie_prefix = "ccsrftoken"
221+
if cookies := [o for o in session.cookies if o and o.name.startswith(cookie_prefix)]:
222+
token = str(cookies[0].value)
223+
token = token.strip('"')
224+
return token
225+
raise ValueError("Invalid login credentials. Cookie 'ccsrftoken' is missing.")
238226

239227
def _hide_secret(self, string: str) -> str:
240228
"""Hide password, secretkey in text (for safe logging)."""

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "fortigate_api"
3-
version = "2.0.1"
3+
version = "2.0.2"
44
description = "Python package to configure Fortigate (Fortios) devices using REST API and SSH"
55
authors = ["Vladimirs Prusakovs <[email protected]>"]
66
readme = "README.rst"
@@ -55,7 +55,7 @@ test = ["pytest"]
5555

5656
[tool.poetry.urls]
5757
"Bug Tracker" = "https://github.com/vladimirs-git/fortigate-api/issues"
58-
"Download URL" = "https://github.com/vladimirs-git/fortigate-api/archive/refs/tags/2.0.1.tar.gz"
58+
"Download URL" = "https://github.com/vladimirs-git/fortigate-api/archive/refs/tags/2.0.2.tar.gz"
5959

6060
[tool.pylint]
6161
max-line-length = 100

tests/test__fortigate_base.py

Lines changed: 62 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
"""Test fortigate_base.py"""
2+
from collections import OrderedDict
23
from unittest.mock import patch
34

45
import pytest
6+
import requests_mock
57
from pytest_mock import MockerFixture
68
from requests import Session
9+
from requests_mock import Mocker
710

811
from fortigate_api import fortigate_base
912
from fortigate_api.fortigate import FortiGate
@@ -96,12 +99,53 @@ def test__url(scheme, host, port, expected):
9699
assert actual == expected
97100

98101

102+
# ============================ login =============================
103+
104+
# noinspection PyUnresolvedReferences
105+
@pytest.mark.parametrize("token, expected, headers", [
106+
("", "TOKEN", ("X-CSRFTOKEN", "TOKEN")),
107+
("TOKEN", "", None),
108+
])
109+
def test__login(token, expected, headers):
110+
"""FortiGateBase.login() for username."""
111+
api = FortiGate(host="HOST", token=token)
112+
with requests_mock.Mocker() as mock:
113+
if token:
114+
mock.get("https://host/api/v2/monitor/system/status")
115+
else:
116+
mock.post("https://host/logincheck")
117+
118+
with patch("fortigate_api.FortiGate._get_token_from_cookies", return_value=expected):
119+
api.login()
120+
assert isinstance(api._session, Session)
121+
store: OrderedDict = getattr(api._session.headers, "_store")
122+
actual = store.get("x-csrftoken")
123+
assert actual == headers
124+
125+
126+
# =========================== helpers ============================
127+
128+
def test__get_session(api: FortiGate, mocker: MockerFixture):
129+
"""FortiGateBase._get_session()"""
130+
# session
131+
api._session = Session()
132+
session = api._get_session()
133+
assert isinstance(session, Session)
134+
135+
# login
136+
api._session = None
137+
mock_response = mocker.Mock()
138+
mocker.patch(target="requests.Session.post", return_value=mock_response)
139+
mocker.patch(target="requests.Session.get", return_value=tst.crate_response(200))
140+
with patch("fortigate_api.FortiGate._get_token_from_cookies", return_value="token"):
141+
session = api._get_session()
142+
assert isinstance(session, Session) is True
143+
144+
99145
@pytest.mark.parametrize("name, expected", [
100146
("ccsrftoken", "token"), # < v7
101147
("ccsrftoken_443", "token"), # >= v7
102148
("ccsrftoken_443_3334d10", "token"), # >= v7
103-
("ccsrftokenother-name", ValueError),
104-
("ccsrftoken-other-name", ValueError),
105149
("other-name", ValueError),
106150
])
107151
def test__get_token_from_cookies(api: FortiGate, name, expected):
@@ -115,6 +159,22 @@ def test__get_token_from_cookies(api: FortiGate, name, expected):
115159
api._get_token_from_cookies(session=session)
116160

117161

162+
@pytest.mark.parametrize("string, password, expected", [
163+
("", "", ""),
164+
("", "secret", ""),
165+
("text", "", "text"),
166+
("text", "secret", "text"),
167+
("_secret_", "secret", "_<hidden>_"),
168+
("_%5B_", "secret", "_%5B_"),
169+
("_secret%5B_", "secret[", "_<hidden>_"),
170+
])
171+
def test__hide_secret(string, password, expected):
172+
"""FortiGateBase._hide_secret()"""
173+
fgt = FortiGate(host="host", password=password)
174+
actual = fgt._hide_secret(string=string)
175+
assert actual == expected
176+
177+
118178
@pytest.mark.parametrize("kwargs, scheme, expected", [
119179
({}, "https", 443),
120180
({}, "http", 80),
@@ -160,41 +220,6 @@ def test__valid_url(kwargs, url, expected):
160220
assert actual == expected
161221

162222

163-
@pytest.mark.parametrize("string, password, expected", [
164-
("", "", ""),
165-
("", "secret", ""),
166-
("text", "", "text"),
167-
("text", "secret", "text"),
168-
("_secret_", "secret", "_<hidden>_"),
169-
("_%5B_", "secret", "_%5B_"),
170-
("_secret%5B_", "secret[", "_<hidden>_"),
171-
])
172-
def test__hide_secret(string, password, expected):
173-
"""FortiGateBase._hide_secret()"""
174-
fgt = FortiGate(host="host", password=password)
175-
actual = fgt._hide_secret(string=string)
176-
assert actual == expected
177-
178-
179-
# =========================== helpers ============================
180-
181-
def test__get_session(api: FortiGate, mocker: MockerFixture):
182-
"""FortiGateBase._get_session()"""
183-
# session
184-
api._session = Session()
185-
session = api._get_session()
186-
assert isinstance(session, Session)
187-
188-
# login
189-
api._session = None
190-
mock_response = mocker.Mock()
191-
mocker.patch(target="requests.Session.post", return_value=mock_response)
192-
mocker.patch(target="requests.Session.get", return_value=tst.crate_response(200))
193-
with patch("fortigate_api.FortiGate._get_token_from_cookies", return_value="token"):
194-
session = api._get_session()
195-
assert isinstance(session, Session) is True
196-
197-
198223
# =========================== helpers ============================
199224

200225
@pytest.mark.parametrize("kwargs, expected", [

0 commit comments

Comments
 (0)