Compare commits

..

No commits in common. "531f5cdf3c1569a2c47e51037c6a7ebef9a5aee1" and "f8304aebf2b454b98a87a73d6d4ac3243a46e3cb" have entirely different histories.

11 changed files with 61 additions and 263 deletions

View file

@ -6,12 +6,8 @@ import os
@dataclass(frozen=True) @dataclass(frozen=True)
class Constants: class Constants:
SP_API_BASE_URL: Final[str] = os.environ.get('SP_API_BASE_URL', 'https://api.hydraveil.net/api/v1') SP_API_BASE_URL: Final[str] = os.environ.get('SP_API_BASE_URL') or 'https://api.hydraveil.net/api/v1'
PING_URL: Final[str] = os.environ.get('PING_URL', 'https://api.hydraveil.net/api/v1/health') PING_URL: Final[str] = os.environ.get('PING_URL') or 'https://api.hydraveil.net/api/v1/health'
CONNECTION_RETRY_INTERVAL: Final[int] = int(os.environ.get('CONNECTION_RETRY_INTERVAL', '5'))
MAX_CONNECTION_ATTEMPTS: Final[int] = int(os.environ.get('MAX_CONNECTION_ATTEMPTS', '2'))
TOR_BOOTSTRAP_TIMEOUT: Final[int] = int(os.environ.get('TOR_BOOTSTRAP_TIMEOUT', '90'))
HV_CLIENT_PATH: Final[str] = os.environ.get('HV_CLIENT_PATH') HV_CLIENT_PATH: Final[str] = os.environ.get('HV_CLIENT_PATH')
HV_CLIENT_VERSION_NUMBER: Final[str] = os.environ.get('HV_CLIENT_VERSION_NUMBER') HV_CLIENT_VERSION_NUMBER: Final[str] = os.environ.get('HV_CLIENT_VERSION_NUMBER')
@ -20,10 +16,10 @@ class Constants:
SYSTEM_CONFIG_PATH: Final[str] = '/etc' SYSTEM_CONFIG_PATH: Final[str] = '/etc'
CACHE_HOME: Final[str] = os.environ.get('XDG_CACHE_HOME', os.path.join(HOME, '.cache')) CACHE_HOME: Final[str] = os.environ.get('XDG_CACHE_HOME') or os.path.join(HOME, '.cache')
CONFIG_HOME: Final[str] = os.environ.get('XDG_CONFIG_HOME', os.path.join(HOME, '.config')) CONFIG_HOME: Final[str] = os.environ.get('XDG_CONFIG_HOME') or os.path.join(HOME, '.config')
DATA_HOME: Final[str] = os.environ.get('XDG_DATA_HOME', os.path.join(HOME, '.local/share')) DATA_HOME: Final[str] = os.environ.get('XDG_DATA_HOME') or os.path.join(HOME, '.local/share')
STATE_HOME: Final[str] = os.environ.get('XDG_STATE_HOME', os.path.join(HOME, '.local/state')) STATE_HOME: Final[str] = os.environ.get('XDG_STATE_HOME') or os.path.join(HOME, '.local/state')
HV_SYSTEM_CONFIG_PATH: Final[str] = f'{SYSTEM_CONFIG_PATH}/hydra-veil' HV_SYSTEM_CONFIG_PATH: Final[str] = f'{SYSTEM_CONFIG_PATH}/hydra-veil'
@ -47,7 +43,3 @@ class Constants:
HV_PRIVILEGE_POLICY_PATH: Final[str] = f'{SYSTEM_CONFIG_PATH}/sudoers.d/hydra-veil' HV_PRIVILEGE_POLICY_PATH: Final[str] = f'{SYSTEM_CONFIG_PATH}/sudoers.d/hydra-veil'
HV_SESSION_STATE_HOME: Final[str] = f'{HV_STATE_HOME}/sessions' HV_SESSION_STATE_HOME: Final[str] = f'{HV_STATE_HOME}/sessions'
HV_TOR_SESSION_STATE_HOME: Final[str] = f'{HV_SESSION_STATE_HOME}/tor'
HV_TOR_CONTROL_SOCKET_PATH: Final[str] = f'{HV_TOR_SESSION_STATE_HOME}/tor.sock'
HV_TOR_PROCESS_IDENTIFIER_PATH: Final[str] = f'{HV_TOR_SESSION_STATE_HOME}/tor.pid'

View file

@ -26,10 +26,6 @@ class ConnectionTerminationError(Exception):
pass pass
class TorServiceInitializationError(Exception):
pass
class PolicyAssignmentError(Exception): class PolicyAssignmentError(Exception):
pass pass

View file

@ -152,7 +152,7 @@ class ApplicationController:
return selected_items + available_preferred_items return selected_items + available_preferred_items
@staticmethod @staticmethod
def __run_process(initialization_file_path, profile, display, session_state: SessionState): def __run_process(initialization_file_path, profile, display, session_state):
if shutil.which('bwrap') is None: if shutil.which('bwrap') is None:
raise CommandNotFoundError('bwrap') raise CommandNotFoundError('bwrap')
@ -183,7 +183,7 @@ class ApplicationController:
process = subprocess.Popen(initialization_file_path, env=environment) process = subprocess.Popen(initialization_file_path, env=environment)
session_state.process_ids.extend([virtual_display_process.pid, process.pid]) session_state = SessionState(session_state.id, session_state.network_port_numbers, [virtual_display_process.pid, process.pid])
SessionStateController.update_or_create(session_state) SessionStateController.update_or_create(session_state)
process.wait() process.wait()

View file

@ -1,7 +1,6 @@
from collections.abc import Callable from collections.abc import Callable
from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutureTimeoutError
from core.Constants import Constants from core.Constants import Constants
from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionUnprotectedError, ConnectionTerminationError, CommandNotFoundError, TorServiceInitializationError from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionUnprotectedError, ConnectionTerminationError, CommandNotFoundError
from core.controllers.ConfigurationController import ConfigurationController from core.controllers.ConfigurationController import ConfigurationController
from core.controllers.ProfileController import ProfileController from core.controllers.ProfileController import ProfileController
from core.controllers.SessionStateController import SessionStateController from core.controllers.SessionStateController import SessionStateController
@ -15,14 +14,10 @@ from pathlib import Path
from subprocess import CalledProcessError from subprocess import CalledProcessError
from typing import Union, Optional, Any from typing import Union, Optional, Any
import os import os
import psutil
import random import random
import re import re
import shutil import shutil
import socket import socket
import stem
import stem.control
import stem.process
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
@ -127,6 +122,7 @@ class ConnectionController:
session_directory = tempfile.mkdtemp(prefix='hv-') session_directory = tempfile.mkdtemp(prefix='hv-')
session_state = SessionStateController.get_or_new(profile.id) session_state = SessionStateController.get_or_new(profile.id)
maximum_number_of_attempts = None
port_number = None port_number = None
proxy_port_number = None proxy_port_number = None
@ -141,9 +137,10 @@ class ConnectionController:
if profile.connection.code == 'tor': if profile.connection.code == 'tor':
maximum_number_of_attempts = 5
port_number = ConnectionController.get_random_available_port_number() port_number = ConnectionController.get_random_available_port_number()
ConnectionController.establish_tor_session_connection(port_number, connection_observer=connection_observer) ConnectionController.establish_tor_session_connection(session_directory, port_number)
session_state.network_port_numbers.tor.append(port_number) session_state.network_port_numbers.append(port_number)
elif profile.connection.code == 'wireguard': elif profile.connection.code == 'wireguard':
@ -152,18 +149,20 @@ class ConnectionController:
port_number = ConnectionController.get_random_available_port_number() port_number = ConnectionController.get_random_available_port_number()
ConnectionController.establish_wireguard_session_connection(profile, session_directory, port_number) ConnectionController.establish_wireguard_session_connection(profile, session_directory, port_number)
session_state.network_port_numbers.wireguard.append(port_number) session_state.network_port_numbers.append(port_number)
if profile.connection.masked: if profile.connection.masked:
maximum_number_of_attempts = 5
while proxy_port_number is None or proxy_port_number == port_number: while proxy_port_number is None or proxy_port_number == port_number:
proxy_port_number = ConnectionController.get_random_available_port_number() proxy_port_number = ConnectionController.get_random_available_port_number()
ConnectionController.establish_proxy_session_connection(profile, session_directory, port_number, proxy_port_number) ConnectionController.establish_proxy_session_connection(profile, session_directory, port_number, proxy_port_number)
session_state.network_port_numbers.proxy.append(proxy_port_number) session_state.network_port_numbers.append(proxy_port_number)
if not profile.connection.is_unprotected(): if not profile.connection.is_unprotected():
ConnectionController.await_connection(proxy_port_number or port_number, connection_observer=connection_observer) ConnectionController.await_connection(proxy_port_number or port_number, maximum_number_of_attempts, connection_observer=connection_observer)
SessionStateController.update_or_create(session_state) SessionStateController.update_or_create(session_state)
@ -209,118 +208,16 @@ class ConnectionController:
time.sleep(1.0) time.sleep(1.0)
@staticmethod @staticmethod
def establish_tor_session_connection(port_number: int, connection_observer: Optional[ConnectionObserver] = None): def establish_tor_session_connection(session_directory: str, port_number: int):
try: if shutil.which('tor') is None:
raise CommandNotFoundError('tor')
controller = stem.control.Controller.from_socket_file(Constants.HV_TOR_CONTROL_SOCKET_PATH) tor_session_directory = f'{session_directory}/tor'
controller.authenticate() Path(tor_session_directory).mkdir(exist_ok=True, mode=0o700)
except (FileNotFoundError, stem.SocketError, TypeError, IndexError): process = subprocess.Popen(('echo', f'DataDirectory {tor_session_directory}/tor\nSocksPort {port_number}'), stdout=subprocess.PIPE)
return subprocess.Popen(('tor', '-f', '-'), stdin=process.stdout, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
ConnectionController.establish_tor_connection(connection_observer=connection_observer)
controller = stem.control.Controller.from_socket_file(Constants.HV_TOR_CONTROL_SOCKET_PATH)
controller.authenticate()
socks_port_numbers = [str(port_number) for port_number in controller.get_ports('socks')]
socks_port_numbers.append(str(port_number))
controller.set_conf('SocksPort', socks_port_numbers)
@staticmethod
def terminate_tor_session_connection(port_number: int):
try:
controller = stem.control.Controller.from_socket_file(Constants.HV_TOR_CONTROL_SOCKET_PATH)
controller.authenticate()
socks_port_numbers = [str(port_number) for port_number in controller.get_ports('socks')]
if len(socks_port_numbers) > 1:
socks_port_numbers = [socks_port_number for socks_port_number in socks_port_numbers if socks_port_number != port_number]
controller.set_conf('SocksPort', socks_port_numbers)
else:
controller.set_conf('SocksPort', '0')
except (FileNotFoundError, stem.SocketError, TypeError, IndexError):
pass
@staticmethod
def establish_tor_connection(connection_observer: Optional[ConnectionObserver] = None):
Path(Constants.HV_TOR_SESSION_STATE_HOME).mkdir(exist_ok=True, mode=0o700)
ConnectionController.terminate_tor_connection()
if connection_observer is not None:
connection_observer.notify('tor_bootstrapping')
with ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(
stem.process.launch_tor_with_config,
config={
'DataDirectory': Constants.HV_TOR_SESSION_STATE_HOME,
'ControlSocket': Constants.HV_TOR_CONTROL_SOCKET_PATH,
'PIDFile': Constants.HV_TOR_PROCESS_IDENTIFIER_PATH,
'SocksPort': '0'
},
init_msg_handler=lambda contents: ConnectionController.__on_tor_initialization_message(contents, connection_observer)
)
try:
future.result(timeout=Constants.TOR_BOOTSTRAP_TIMEOUT)
except FutureTimeoutError:
ConnectionController.terminate_tor_connection()
raise TorServiceInitializationError('The dedicated Tor service could not be initialized.')
if connection_observer is not None:
connection_observer.notify('tor_bootstrapped')
try:
controller = stem.control.Controller.from_socket_file(Constants.HV_TOR_CONTROL_SOCKET_PATH)
controller.authenticate()
except (FileNotFoundError, stem.SocketError, TypeError, IndexError):
raise TorServiceInitializationError('The dedicated Tor service could not be initialized.')
for session_state in SessionStateController.all():
for port_number in session_state.network_port_numbers.tor:
ConnectionController.establish_tor_session_connection(port_number)
@staticmethod
def terminate_tor_connection():
process_identifier_file = Path(Constants.HV_TOR_PROCESS_IDENTIFIER_PATH)
control_socket_file = Path(Constants.HV_TOR_CONTROL_SOCKET_PATH)
try:
process_identifier = int(process_identifier_file.read_text().strip())
except (OSError, ValueError):
process_identifier = None
if process_identifier is not None:
try:
process = psutil.Process(process_identifier)
if process.is_running():
process.terminate()
except psutil.NoSuchProcess:
pass
process_identifier_file.unlink(missing_ok=True)
control_socket_file.unlink(missing_ok=True)
@staticmethod @staticmethod
def establish_wireguard_session_connection(profile: SessionProfile, session_directory: str, port_number: int): def establish_wireguard_session_connection(profile: SessionProfile, session_directory: str, port_number: int):
@ -418,18 +315,23 @@ class ConnectionController:
return port_number return port_number
@staticmethod @staticmethod
def await_connection(port_number: Optional[int] = None, connection_observer: Optional[ConnectionObserver] = None): def await_connection(port_number: Optional[int] = None, maximum_number_of_attempts: Optional[int] = None, connection_observer: Optional[ConnectionObserver] = None):
if port_number is None: if port_number is None:
ConnectionController.await_network_interface() ConnectionController.await_network_interface()
for retry_count in range(Constants.MAX_CONNECTION_ATTEMPTS): if maximum_number_of_attempts is None:
maximum_number_of_attempts = 2
retry_interval = 5.0
for retry_count in range(maximum_number_of_attempts):
if connection_observer is not None: if connection_observer is not None:
connection_observer.notify('connecting', dict( connection_observer.notify('connecting', dict(
retry_interval=Constants.CONNECTION_RETRY_INTERVAL, retry_interval=retry_interval,
maximum_number_of_attempts=Constants.MAX_CONNECTION_ATTEMPTS, maximum_number_of_attempts=maximum_number_of_attempts,
attempt_count=retry_count + 1 attempt_count=retry_count + 1
)) ))
@ -440,7 +342,7 @@ class ConnectionController:
except ConnectionError: except ConnectionError:
time.sleep(Constants.CONNECTION_RETRY_INTERVAL) time.sleep(retry_interval)
retry_count += 1 retry_count += 1
raise ConnectionError('The connection could not be established.') raise ConnectionError('The connection could not be established.')
@ -510,13 +412,13 @@ class ConnectionController:
@staticmethod @staticmethod
def __with_tor_connection(*args, task: Callable[..., Any], connection_observer: Optional[ConnectionObserver] = None, **kwargs): def __with_tor_connection(*args, task: Callable[..., Any], connection_observer: Optional[ConnectionObserver] = None, **kwargs):
session_directory = tempfile.mkdtemp(prefix='hv-')
port_number = ConnectionController.get_random_available_port_number() port_number = ConnectionController.get_random_available_port_number()
ConnectionController.establish_tor_session_connection(port_number, connection_observer=connection_observer) process = ConnectionController.establish_tor_session_connection(session_directory, port_number)
ConnectionController.await_connection(port_number, connection_observer=connection_observer) ConnectionController.await_connection(port_number, 5, connection_observer=connection_observer)
task_output = task(*args, proxies=ConnectionController.get_proxies(port_number), **kwargs) task_output = task(*args, proxies=ConnectionController.get_proxies(port_number), **kwargs)
process.terminate()
ConnectionController.terminate_tor_session_connection(port_number)
return task_output return task_output
@ -572,16 +474,3 @@ class ConnectionController:
return True return True
return False return False
@staticmethod
def __on_tor_initialization_message(contents, connection_observer: Optional[ConnectionObserver] = None):
if connection_observer is not None:
if 'Bootstrapped ' in contents:
progress = (m := re.search(r' (\d{1,3})% ', contents)) and int(m.group(1))
connection_observer.notify('tor_bootstrap_progressing', None, dict(
progress=progress
))

View file

@ -97,10 +97,6 @@ class ProfileController:
session_state = SessionStateController.get(profile.id) session_state = SessionStateController.get(profile.id)
if session_state is not None: if session_state is not None:
for port_number in session_state.network_port_numbers.tor:
ConnectionController.terminate_tor_session_connection(port_number)
session_state.dissolve(session_state.id) session_state.dissolve(session_state.id)
if profile.is_system_profile(): if profile.is_system_profile():
@ -177,7 +173,7 @@ class ProfileController:
if profile.is_session_profile(): if profile.is_session_profile():
session_state = SessionStateController.get_or_new(profile.id) session_state = SessionStateController.get_or_new(profile.id)
return len(session_state.network_port_numbers.all) > 0 or len(session_state.process_ids) > 0 return len(session_state.network_port_numbers) > 0 or len(session_state.process_ids) > 0
if profile.is_system_profile(): if profile.is_system_profile():

View file

@ -21,10 +21,6 @@ class SessionStateController:
def exists(id: int): def exists(id: int):
return SessionState.exists(id) return SessionState.exists(id)
@staticmethod
def all():
return SessionState.all()
@staticmethod @staticmethod
def update_or_create(session_state): def update_or_create(session_state):
session_state.save() session_state.save()

View file

@ -1,16 +0,0 @@
from dataclasses import dataclass, field
@dataclass
class NetworkPortNumbers:
proxy: list[int] = field(default_factory=list)
wireguard: list[int] = field(default_factory=list)
tor: list[int] = field(default_factory=list)
@property
def all(self):
return self.proxy + self.wireguard + self.tor
@property
def isolated(self):
return self.proxy + self.wireguard

View file

@ -1,5 +1,4 @@
from core.Constants import Constants from core.Constants import Constants
from core.models.session.NetworkPortNumbers import NetworkPortNumbers
from dataclasses import dataclass, field from dataclasses import dataclass, field
from dataclasses_json import config, Exclude, dataclass_json from dataclasses_json import config, Exclude, dataclass_json
from json import JSONDecodeError from json import JSONDecodeError
@ -18,7 +17,7 @@ class SessionState:
id: int = field( id: int = field(
metadata=config(exclude=Exclude.ALWAYS) metadata=config(exclude=Exclude.ALWAYS)
) )
network_port_numbers: NetworkPortNumbers = field(default_factory=NetworkPortNumbers) network_port_numbers: list[int] = field(default_factory=list)
process_ids: list[int] = field(default_factory=list) process_ids: list[int] = field(default_factory=list)
def get_state_path(self): def get_state_path(self):
@ -40,53 +39,25 @@ class SessionState:
@staticmethod @staticmethod
def find_by_id(id: int): def find_by_id(id: int):
state_path = SessionState.__get_state_path(id)
try: try:
session_state_file_contents = Path(f'{state_path}/state.json').read_text() session_state_file_contents = open(f'{SessionState.__get_state_path(id)}/state.json', 'r').read()
except FileNotFoundError: except FileNotFoundError:
return None return None
try: try:
session_state = json.loads(session_state_file_contents) session_state = json.loads(session_state_file_contents)
session_state['id'] = id except JSONDecodeError:
# noinspection PyUnresolvedReferences
return SessionState.from_dict(session_state)
except (JSONDecodeError, AttributeError):
shutil.rmtree(Path(state_path), ignore_errors=True)
return None return None
session_state['id'] = id
# noinspection PyUnresolvedReferences
return SessionState.from_dict(session_state)
@staticmethod @staticmethod
def exists(id: int): def exists(id: int):
return os.path.isdir(SessionState.__get_state_path(id)) and re.match(r'^\d+$', str(id)) return os.path.isdir(SessionState.__get_state_path(id)) and re.match(r'^\d+$', str(id))
@staticmethod
def all():
session_states = []
for directory_entry in os.listdir(Constants.HV_SESSION_STATE_HOME):
try:
id = int(directory_entry)
except ValueError:
continue
if SessionState.exists(id):
session_state = SessionState.find_by_id(id)
if session_state is not None:
session_states.append(session_state)
session_states.sort(key=lambda key: key.id)
return session_states
@staticmethod @staticmethod
def dissolve(id: int): def dissolve(id: int):
@ -100,12 +71,12 @@ class SessionState:
shutil.rmtree(session_state_path, ignore_errors=True) shutil.rmtree(session_state_path, ignore_errors=True)
@staticmethod @staticmethod
def __kill_associated_processes(session_state: 'SessionState'): def __kill_associated_processes(session_state):
associated_process_ids = list(session_state.process_ids) associated_process_ids = list(session_state.process_ids)
network_connections = psutil.net_connections() network_connections = psutil.net_connections()
for network_port_number in session_state.network_port_numbers.isolated: for network_port_number in session_state.network_port_numbers:
for network_connection in network_connections: for network_connection in network_connections:

View file

@ -5,6 +5,3 @@ class ConnectionObserver(BaseObserver):
def __init__(self): def __init__(self):
self.on_connecting = [] self.on_connecting = []
self.on_tor_bootstrapping = []
self.on_tor_bootstrap_progressing = []
self.on_tor_bootstrapped = []

View file

@ -18,12 +18,10 @@ class WebServiceApiService:
@staticmethod @staticmethod
def get_applications(proxies: Optional[dict] = None): def get_applications(proxies: Optional[dict] = None):
from requests.status_codes import codes as status_codes
response = WebServiceApiService.__get('/platforms/linux-x86_64/applications', None, proxies) response = WebServiceApiService.__get('/platforms/linux-x86_64/applications', None, proxies)
applications = [] applications = []
if response.status_code == status_codes.OK: if response.status_code == 200:
for application in response.json()['data']: for application in response.json()['data']:
applications.append(Application(application['code'], application['name'], application['id'])) applications.append(Application(application['code'], application['name'], application['id']))
@ -32,12 +30,10 @@ class WebServiceApiService:
@staticmethod @staticmethod
def get_application_versions(code: str, proxies: Optional[dict] = None): def get_application_versions(code: str, proxies: Optional[dict] = None):
from requests.status_codes import codes as status_codes
response = WebServiceApiService.__get(f'/platforms/linux-x86_64/applications/{code}/application-versions', None, proxies) response = WebServiceApiService.__get(f'/platforms/linux-x86_64/applications/{code}/application-versions', None, proxies)
application_versions = [] application_versions = []
if response.status_code == status_codes.OK: if response.status_code == 200:
for application_version in response.json()['data']: for application_version in response.json()['data']:
application_versions.append(ApplicationVersion(code, application_version['version_number'], application_version['format_revision'], application_version['id'], application_version['download_path'], application_version['released_at'], application_version['file_hash'])) application_versions.append(ApplicationVersion(code, application_version['version_number'], application_version['format_revision'], application_version['id'], application_version['download_path'], application_version['released_at'], application_version['file_hash']))
@ -46,12 +42,10 @@ class WebServiceApiService:
@staticmethod @staticmethod
def get_client_versions(proxies: Optional[dict] = None): def get_client_versions(proxies: Optional[dict] = None):
from requests.status_codes import codes as status_codes
response = WebServiceApiService.__get('/platforms/linux-x86_64/appimage/client-versions', None, proxies) response = WebServiceApiService.__get('/platforms/linux-x86_64/appimage/client-versions', None, proxies)
client_versions = [] client_versions = []
if response.status_code == status_codes.OK: if response.status_code == 200:
for client_version in response.json()['data']: for client_version in response.json()['data']:
client_versions.append(ClientVersion(client_version['version_number'], client_version['released_at'], client_version['id'], client_version['download_path'])) client_versions.append(ClientVersion(client_version['version_number'], client_version['released_at'], client_version['id'], client_version['download_path']))
@ -60,12 +54,10 @@ class WebServiceApiService:
@staticmethod @staticmethod
def get_operators(proxies: Optional[dict] = None): def get_operators(proxies: Optional[dict] = None):
from requests.status_codes import codes as status_codes
response = WebServiceApiService.__get('/operators', None, proxies) response = WebServiceApiService.__get('/operators', None, proxies)
operators = [] operators = []
if response.status_code == status_codes.OK: if response.status_code == 200:
for operator in response.json()['data']: for operator in response.json()['data']:
operators.append(Operator(operator['id'], operator['name'], operator['public_key'], operator['nostr_public_key'], operator['nostr_profile_reference'], operator['nostr_attestation']['event_reference'])) operators.append(Operator(operator['id'], operator['name'], operator['public_key'], operator['nostr_public_key'], operator['nostr_profile_reference'], operator['nostr_attestation']['event_reference']))
@ -74,12 +66,10 @@ class WebServiceApiService:
@staticmethod @staticmethod
def get_locations(proxies: Optional[dict] = None): def get_locations(proxies: Optional[dict] = None):
from requests.status_codes import codes as status_codes
response = WebServiceApiService.__get('/locations', None, proxies) response = WebServiceApiService.__get('/locations', None, proxies)
locations = [] locations = []
if response.status_code == status_codes.OK: if response.status_code == 200:
for location in response.json()['data']: for location in response.json()['data']:
locations.append(Location(location['country']['code'], location['code'], location['id'], location['country']['name'], location['name'], location['time_zone']['code'], location['operator_id'], location['provider']['name'], location['is_proxy_capable'], location['is_wireguard_capable'])) locations.append(Location(location['country']['code'], location['code'], location['id'], location['country']['name'], location['name'], location['time_zone']['code'], location['operator_id'], location['provider']['name'], location['is_proxy_capable'], location['is_wireguard_capable']))
@ -88,12 +78,10 @@ class WebServiceApiService:
@staticmethod @staticmethod
def get_subscription_plans(proxies: Optional[dict] = None): def get_subscription_plans(proxies: Optional[dict] = None):
from requests.status_codes import codes as status_codes
response = WebServiceApiService.__get('/subscription-plans', None, proxies) response = WebServiceApiService.__get('/subscription-plans', None, proxies)
subscription_plans = [] subscription_plans = []
if response.status_code == status_codes.OK: if response.status_code == 200:
for subscription_plan in response.json()['data']: for subscription_plan in response.json()['data']:
subscription_plans.append(SubscriptionPlan(subscription_plan['id'], subscription_plan['code'], subscription_plan['wireguard_session_limit'], subscription_plan['duration'], subscription_plan['price'], subscription_plan['features_proxy'], subscription_plan['features_wireguard'])) subscription_plans.append(SubscriptionPlan(subscription_plan['id'], subscription_plan['code'], subscription_plan['wireguard_session_limit'], subscription_plan['duration'], subscription_plan['price'], subscription_plan['features_proxy'], subscription_plan['features_wireguard']))
@ -102,14 +90,12 @@ class WebServiceApiService:
@staticmethod @staticmethod
def post_subscription(subscription_plan_id, location_id, proxies: Optional[dict] = None): def post_subscription(subscription_plan_id, location_id, proxies: Optional[dict] = None):
from requests.status_codes import codes as status_codes
response = WebServiceApiService.__post('/subscriptions', None, { response = WebServiceApiService.__post('/subscriptions', None, {
'subscription_plan_id': subscription_plan_id, 'subscription_plan_id': subscription_plan_id,
'location_id': location_id 'location_id': location_id
}, proxies) }, proxies)
if response.status_code == status_codes.CREATED: if response.status_code == 201:
return Subscription(response.headers['X-Billing-Code']) return Subscription(response.headers['X-Billing-Code'])
else: else:
@ -118,15 +104,13 @@ class WebServiceApiService:
@staticmethod @staticmethod
def get_subscription(billing_code: str, proxies: Optional[dict] = None): def get_subscription(billing_code: str, proxies: Optional[dict] = None):
from requests.status_codes import codes as status_codes
billing_code = billing_code.replace('-', '').upper() billing_code = billing_code.replace('-', '').upper()
billing_code_fragments = re.findall('....?', billing_code) billing_code_fragments = re.findall('....?', billing_code)
billing_code = '-'.join(billing_code_fragments) billing_code = '-'.join(billing_code_fragments)
response = WebServiceApiService.__get('/subscriptions/current', billing_code, proxies) response = WebServiceApiService.__get('/subscriptions/current', billing_code, proxies)
if response.status_code == status_codes.OK: if response.status_code == 200:
subscription = response.json()['data'] subscription = response.json()['data']
return Subscription(billing_code, Subscription.from_iso_format(subscription['expires_at'])) return Subscription(billing_code, Subscription.from_iso_format(subscription['expires_at']))
@ -137,11 +121,9 @@ class WebServiceApiService:
@staticmethod @staticmethod
def get_invoice(billing_code: str, proxies: Optional[dict] = None): def get_invoice(billing_code: str, proxies: Optional[dict] = None):
from requests.status_codes import codes as status_codes
response = WebServiceApiService.__get('/invoices/current', billing_code, proxies) response = WebServiceApiService.__get('/invoices/current', billing_code, proxies)
if response.status_code == status_codes.OK: if response.status_code == 200:
response_data = response.json()['data'] response_data = response.json()['data']
@ -163,11 +145,9 @@ class WebServiceApiService:
@staticmethod @staticmethod
def get_proxy_configuration(billing_code: str, proxies: Optional[dict] = None): def get_proxy_configuration(billing_code: str, proxies: Optional[dict] = None):
from requests.status_codes import codes as status_codes
response = WebServiceApiService.__get('/proxy-configurations/current', billing_code, proxies) response = WebServiceApiService.__get('/proxy-configurations/current', billing_code, proxies)
if response.status_code == status_codes.OK: if response.status_code == 200:
proxy_configuration = response.json()['data'] proxy_configuration = response.json()['data']
return ProxyConfiguration(proxy_configuration['ip_address'], proxy_configuration['port'], proxy_configuration['username'], proxy_configuration['password'], proxy_configuration['location']['time_zone']['code']) return ProxyConfiguration(proxy_configuration['ip_address'], proxy_configuration['port'], proxy_configuration['username'], proxy_configuration['password'], proxy_configuration['location']['time_zone']['code'])
@ -178,13 +158,11 @@ class WebServiceApiService:
@staticmethod @staticmethod
def post_wireguard_session(country_code: str, location_code: str, billing_code: str, public_key: str, proxies: Optional[dict] = None): def post_wireguard_session(country_code: str, location_code: str, billing_code: str, public_key: str, proxies: Optional[dict] = None):
from requests.status_codes import codes as status_codes
response = WebServiceApiService.__post(f'/countries/{country_code}/locations/{location_code}/wireguard-sessions', billing_code, { response = WebServiceApiService.__post(f'/countries/{country_code}/locations/{location_code}/wireguard-sessions', billing_code, {
'public_key': public_key, 'public_key': public_key,
}, proxies) }, proxies)
if response.status_code == status_codes.CREATED: if response.status_code == 201:
return response.text return response.text
else: else:
return None return None

View file

@ -19,7 +19,6 @@ dependencies = [
"pysocks ~= 1.7.1", "pysocks ~= 1.7.1",
"python-dateutil ~= 2.9.0.post0", "python-dateutil ~= 2.9.0.post0",
"requests ~= 2.32.5", "requests ~= 2.32.5",
"stem ~= 1.8.2",
] ]
[project.urls] [project.urls]