Skip to content

Commit e5af59c

Browse files
committed
Core: Added endpoint validation
1 parent 521a6f2 commit e5af59c

File tree

5 files changed

+70
-18
lines changed

5 files changed

+70
-18
lines changed

shellhub/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Increment versions here according to SemVer
2-
__version__ = "0.0.1"
2+
__version__ = "0.1.0"
33

44
from .models.device import ShellHubDevice, ShellHubDeviceInfo
55
from .models.base import ShellHub

shellhub/models/base.py

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import re
12
from typing import Any
23
from typing import Dict
34
from typing import List
45
from typing import Optional
6+
from typing import Tuple
7+
from urllib.parse import urlparse
58

69
import requests
710

@@ -16,26 +19,59 @@ class ShellHub:
1619
_username: str
1720
_password: str
1821
_endpoint: str
22+
_url: str
1923
_access_token: Optional[str]
24+
_use_ssl: bool
2025

21-
def __init__(self, username: str, password: str, endpoint: str) -> None:
22-
self._username: str = username
23-
self._password: str = password
24-
self._endpoint: str = endpoint
25-
self._access_token: Optional[str] = None
26+
def __init__(self, username: str, password: str, endpoint_or_url: str, use_ssl: bool = True) -> None:
27+
self._username = username
28+
self._password = password
29+
self._use_ssl = use_ssl
30+
self._url, self._endpoint = self._format_and_validate_url(endpoint_or_url)
31+
self._access_token = None
2632

2733
self._login()
2834

35+
def _format_and_validate_url(self, endpoint: str) -> Tuple[str, str]:
36+
# Adjust the endpoint based on the _use_ssl flag
37+
if not endpoint.startswith(("http://", "https://")):
38+
protocol = "https://" if self._use_ssl else "http://"
39+
endpoint = protocol + endpoint
40+
41+
# Validate the URL (basic check)
42+
if not self._is_valid_url(endpoint):
43+
raise ShellHubBaseException("Invalid URL provided.")
44+
45+
# Use urlparse to extract the base endpoint without the scheme
46+
parsed_url = urlparse(endpoint)
47+
base_endpoint = parsed_url.netloc
48+
49+
return endpoint, base_endpoint # Return both full URL and base endpoint
50+
51+
@staticmethod
52+
def _is_valid_url(url: str) -> bool:
53+
# Simple pattern to check if the URL is well-formed
54+
pattern = re.compile(
55+
r"^https?:\/\/" # http:// or https://
56+
r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|" # domain...
57+
r"localhost|" # localhost...
58+
r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" # ...or ip
59+
r"(?::\d+)?" # optional port
60+
r"(?:\/[^\s]*)?$",
61+
re.IGNORECASE,
62+
) # optional path
63+
return re.match(pattern, url) is not None
64+
2965
def __repr__(self) -> str:
30-
return f"<ShellHub username={self._username} endpoint={self._endpoint}>"
66+
return f"<ShellHub username={self._username} url={self._url}>"
3167

3268
def __str__(self) -> str:
33-
return self._endpoint
69+
return self._url
3470

3571
def _login(self) -> None:
3672
try:
3773
response = requests.post(
38-
f"{self._endpoint}/api/login",
74+
f"{self._url}/api/login",
3975
json={
4076
"username": self._username,
4177
"password": self._password,
@@ -69,7 +105,7 @@ def make_request(
69105
params = params[:-1]
70106

71107
response: requests.Response = getattr(requests, method.lower())(
72-
f"{self._endpoint}{endpoint}{params if params else ''}",
108+
f"{self._url}{endpoint}{params if params else ''}",
73109
headers={
74110
"Authorization": f"Bearer {self._access_token}",
75111
},
@@ -79,7 +115,7 @@ def make_request(
79115
if response.status_code == 401:
80116
self._login()
81117
response = getattr(requests, method.lower())(
82-
f"{self._endpoint}{endpoint}{params if params else ''}",
118+
f"{self._url}{endpoint}{params if params else ''}",
83119
headers={
84120
"Authorization": f"Bearer {self._access_token}",
85121
},

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def shellhub():
1616
m.post(login_url, json=mock_response)
1717

1818
# Create an instance of ShellHub with mocked login
19-
shellhub_instance = ShellHub(username="john.doe", password="dolphin", endpoint=MOCKED_DOMAIN_URL)
19+
shellhub_instance = ShellHub(username="john.doe", password="dolphin", endpoint_or_url=MOCKED_DOMAIN_URL)
2020

2121
yield shellhub_instance
2222

tests/test_base.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ def test_login(requests_mock):
1212
"token": "jwt_token",
1313
}
1414
requests_mock.post(login_url, json=mock_response)
15-
shellhub = ShellHub(username="john.doe", password="dolphin", endpoint=MOCKED_DOMAIN_URL)
15+
shellhub = ShellHub(username="john.doe", password="dolphin", endpoint_or_url=MOCKED_DOMAIN_URL)
1616
assert shellhub._access_token == mock_response["token"]
1717

1818

1919
def test_incorrect_endpoint():
2020
with pytest.raises(ShellHubBaseException):
21-
ShellHub(username="john.doe", password="dolphin", endpoint=MOCKED_DOMAIN_URL)
21+
ShellHub(username="john.doe", password="dolphin", endpoint_or_url=MOCKED_DOMAIN_URL)
2222

2323

2424
def test_incorrect_username_password(requests_mock):
@@ -28,12 +28,28 @@ def test_incorrect_username_password(requests_mock):
2828
}
2929
requests_mock.post(login_url, json=mock_response, status_code=401)
3030
with pytest.raises(ShellHubAuthenticationError):
31-
ShellHub(username="john.doe", password="dolphin", endpoint=MOCKED_DOMAIN_URL)
31+
ShellHub(username="john.doe", password="dolphin", endpoint_or_url=MOCKED_DOMAIN_URL)
3232

3333

3434
def test_repr(shellhub):
35-
assert repr(shellhub) == f"<ShellHub username=john.doe endpoint={MOCKED_DOMAIN_URL}>"
35+
assert repr(shellhub) == f"<ShellHub username=john.doe url={MOCKED_DOMAIN_URL}>"
3636

3737

3838
def test_str(shellhub):
39-
assert str(shellhub) == shellhub._endpoint
39+
assert str(shellhub) == shellhub._url
40+
41+
42+
def test_format_and_validate_url(shellhub):
43+
url, endpoint = shellhub._format_and_validate_url("www.example.com")
44+
assert url == "https://www.example.com"
45+
assert endpoint == "www.example.com"
46+
47+
with pytest.raises(ShellHubBaseException):
48+
shellhub._format_and_validate_url("invalid_url")
49+
50+
51+
def test_is_valid_url(shellhub):
52+
assert shellhub._is_valid_url("https://www.example.com")
53+
assert shellhub._is_valid_url("http://www.example.com")
54+
assert shellhub._is_valid_url("www.example.com") is False
55+
assert shellhub._is_valid_url("invalid_url") is False

tests/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
MOCKED_DOMAIN_URL = "http://shellhub.localhost"
1+
MOCKED_DOMAIN_URL = "http://shellhub.example.org"

0 commit comments

Comments
 (0)