Compare commits
3 commits
f8304aebf2
...
531f5cdf3c
| Author | SHA1 | Date | |
|---|---|---|---|
| 531f5cdf3c | |||
| 616a864c9d | |||
| 6dfd5993a2 |
11 changed files with 263 additions and 61 deletions
|
|
@ -6,8 +6,12 @@ import os
|
|||
@dataclass(frozen=True)
|
||||
class Constants:
|
||||
|
||||
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') or 'https://api.hydraveil.net/api/v1/health'
|
||||
SP_API_BASE_URL: Final[str] = os.environ.get('SP_API_BASE_URL', 'https://api.hydraveil.net/api/v1')
|
||||
PING_URL: Final[str] = os.environ.get('PING_URL', '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_VERSION_NUMBER: Final[str] = os.environ.get('HV_CLIENT_VERSION_NUMBER')
|
||||
|
|
@ -16,10 +20,10 @@ class Constants:
|
|||
|
||||
SYSTEM_CONFIG_PATH: Final[str] = '/etc'
|
||||
|
||||
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') or os.path.join(HOME, '.config')
|
||||
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') or os.path.join(HOME, '.local/state')
|
||||
CACHE_HOME: Final[str] = os.environ.get('XDG_CACHE_HOME', os.path.join(HOME, '.cache'))
|
||||
CONFIG_HOME: Final[str] = os.environ.get('XDG_CONFIG_HOME', os.path.join(HOME, '.config'))
|
||||
DATA_HOME: Final[str] = os.environ.get('XDG_DATA_HOME', os.path.join(HOME, '.local/share'))
|
||||
STATE_HOME: Final[str] = os.environ.get('XDG_STATE_HOME', os.path.join(HOME, '.local/state'))
|
||||
|
||||
HV_SYSTEM_CONFIG_PATH: Final[str] = f'{SYSTEM_CONFIG_PATH}/hydra-veil'
|
||||
|
||||
|
|
@ -43,3 +47,7 @@ class Constants:
|
|||
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_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'
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ class ConnectionTerminationError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class TorServiceInitializationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class PolicyAssignmentError(Exception):
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ class ApplicationController:
|
|||
return selected_items + available_preferred_items
|
||||
|
||||
@staticmethod
|
||||
def __run_process(initialization_file_path, profile, display, session_state):
|
||||
def __run_process(initialization_file_path, profile, display, session_state: SessionState):
|
||||
|
||||
if shutil.which('bwrap') is None:
|
||||
raise CommandNotFoundError('bwrap')
|
||||
|
|
@ -183,7 +183,7 @@ class ApplicationController:
|
|||
|
||||
process = subprocess.Popen(initialization_file_path, env=environment)
|
||||
|
||||
session_state = SessionState(session_state.id, session_state.network_port_numbers, [virtual_display_process.pid, process.pid])
|
||||
session_state.process_ids.extend([virtual_display_process.pid, process.pid])
|
||||
SessionStateController.update_or_create(session_state)
|
||||
|
||||
process.wait()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from collections.abc import Callable
|
||||
from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutureTimeoutError
|
||||
from core.Constants import Constants
|
||||
from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionUnprotectedError, ConnectionTerminationError, CommandNotFoundError
|
||||
from core.Errors import InvalidSubscriptionError, MissingSubscriptionError, ConnectionUnprotectedError, ConnectionTerminationError, CommandNotFoundError, TorServiceInitializationError
|
||||
from core.controllers.ConfigurationController import ConfigurationController
|
||||
from core.controllers.ProfileController import ProfileController
|
||||
from core.controllers.SessionStateController import SessionStateController
|
||||
|
|
@ -14,10 +15,14 @@ from pathlib import Path
|
|||
from subprocess import CalledProcessError
|
||||
from typing import Union, Optional, Any
|
||||
import os
|
||||
import psutil
|
||||
import random
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import stem
|
||||
import stem.control
|
||||
import stem.process
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
|
@ -122,7 +127,6 @@ class ConnectionController:
|
|||
session_directory = tempfile.mkdtemp(prefix='hv-')
|
||||
session_state = SessionStateController.get_or_new(profile.id)
|
||||
|
||||
maximum_number_of_attempts = None
|
||||
port_number = None
|
||||
proxy_port_number = None
|
||||
|
||||
|
|
@ -137,10 +141,9 @@ class ConnectionController:
|
|||
|
||||
if profile.connection.code == 'tor':
|
||||
|
||||
maximum_number_of_attempts = 5
|
||||
port_number = ConnectionController.get_random_available_port_number()
|
||||
ConnectionController.establish_tor_session_connection(session_directory, port_number)
|
||||
session_state.network_port_numbers.append(port_number)
|
||||
ConnectionController.establish_tor_session_connection(port_number, connection_observer=connection_observer)
|
||||
session_state.network_port_numbers.tor.append(port_number)
|
||||
|
||||
elif profile.connection.code == 'wireguard':
|
||||
|
||||
|
|
@ -149,20 +152,18 @@ class ConnectionController:
|
|||
|
||||
port_number = ConnectionController.get_random_available_port_number()
|
||||
ConnectionController.establish_wireguard_session_connection(profile, session_directory, port_number)
|
||||
session_state.network_port_numbers.append(port_number)
|
||||
session_state.network_port_numbers.wireguard.append(port_number)
|
||||
|
||||
if profile.connection.masked:
|
||||
|
||||
maximum_number_of_attempts = 5
|
||||
|
||||
while proxy_port_number is None or proxy_port_number == port_number:
|
||||
proxy_port_number = ConnectionController.get_random_available_port_number()
|
||||
|
||||
ConnectionController.establish_proxy_session_connection(profile, session_directory, port_number, proxy_port_number)
|
||||
session_state.network_port_numbers.append(proxy_port_number)
|
||||
session_state.network_port_numbers.proxy.append(proxy_port_number)
|
||||
|
||||
if not profile.connection.is_unprotected():
|
||||
ConnectionController.await_connection(proxy_port_number or port_number, maximum_number_of_attempts, connection_observer=connection_observer)
|
||||
ConnectionController.await_connection(proxy_port_number or port_number, connection_observer=connection_observer)
|
||||
|
||||
SessionStateController.update_or_create(session_state)
|
||||
|
||||
|
|
@ -208,16 +209,118 @@ class ConnectionController:
|
|||
time.sleep(1.0)
|
||||
|
||||
@staticmethod
|
||||
def establish_tor_session_connection(session_directory: str, port_number: int):
|
||||
def establish_tor_session_connection(port_number: int, connection_observer: Optional[ConnectionObserver] = None):
|
||||
|
||||
if shutil.which('tor') is None:
|
||||
raise CommandNotFoundError('tor')
|
||||
try:
|
||||
|
||||
tor_session_directory = f'{session_directory}/tor'
|
||||
Path(tor_session_directory).mkdir(exist_ok=True, mode=0o700)
|
||||
controller = stem.control.Controller.from_socket_file(Constants.HV_TOR_CONTROL_SOCKET_PATH)
|
||||
controller.authenticate()
|
||||
|
||||
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)
|
||||
except (FileNotFoundError, stem.SocketError, TypeError, IndexError):
|
||||
|
||||
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
|
||||
def establish_wireguard_session_connection(profile: SessionProfile, session_directory: str, port_number: int):
|
||||
|
|
@ -315,23 +418,18 @@ class ConnectionController:
|
|||
return port_number
|
||||
|
||||
@staticmethod
|
||||
def await_connection(port_number: Optional[int] = None, maximum_number_of_attempts: Optional[int] = None, connection_observer: Optional[ConnectionObserver] = None):
|
||||
def await_connection(port_number: Optional[int] = None, connection_observer: Optional[ConnectionObserver] = None):
|
||||
|
||||
if port_number is None:
|
||||
ConnectionController.await_network_interface()
|
||||
|
||||
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):
|
||||
for retry_count in range(Constants.MAX_CONNECTION_ATTEMPTS):
|
||||
|
||||
if connection_observer is not None:
|
||||
|
||||
connection_observer.notify('connecting', dict(
|
||||
retry_interval=retry_interval,
|
||||
maximum_number_of_attempts=maximum_number_of_attempts,
|
||||
retry_interval=Constants.CONNECTION_RETRY_INTERVAL,
|
||||
maximum_number_of_attempts=Constants.MAX_CONNECTION_ATTEMPTS,
|
||||
attempt_count=retry_count + 1
|
||||
))
|
||||
|
||||
|
|
@ -342,7 +440,7 @@ class ConnectionController:
|
|||
|
||||
except ConnectionError:
|
||||
|
||||
time.sleep(retry_interval)
|
||||
time.sleep(Constants.CONNECTION_RETRY_INTERVAL)
|
||||
retry_count += 1
|
||||
|
||||
raise ConnectionError('The connection could not be established.')
|
||||
|
|
@ -412,13 +510,13 @@ class ConnectionController:
|
|||
@staticmethod
|
||||
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()
|
||||
process = ConnectionController.establish_tor_session_connection(session_directory, port_number)
|
||||
ConnectionController.establish_tor_session_connection(port_number, connection_observer=connection_observer)
|
||||
|
||||
ConnectionController.await_connection(port_number, 5, connection_observer=connection_observer)
|
||||
ConnectionController.await_connection(port_number, connection_observer=connection_observer)
|
||||
task_output = task(*args, proxies=ConnectionController.get_proxies(port_number), **kwargs)
|
||||
process.terminate()
|
||||
|
||||
ConnectionController.terminate_tor_session_connection(port_number)
|
||||
|
||||
return task_output
|
||||
|
||||
|
|
@ -474,3 +572,16 @@ class ConnectionController:
|
|||
return True
|
||||
|
||||
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
|
||||
))
|
||||
|
|
|
|||
|
|
@ -97,6 +97,10 @@ class ProfileController:
|
|||
session_state = SessionStateController.get(profile.id)
|
||||
|
||||
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)
|
||||
|
||||
if profile.is_system_profile():
|
||||
|
|
@ -173,7 +177,7 @@ class ProfileController:
|
|||
if profile.is_session_profile():
|
||||
|
||||
session_state = SessionStateController.get_or_new(profile.id)
|
||||
return len(session_state.network_port_numbers) > 0 or len(session_state.process_ids) > 0
|
||||
return len(session_state.network_port_numbers.all) > 0 or len(session_state.process_ids) > 0
|
||||
|
||||
if profile.is_system_profile():
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ class SessionStateController:
|
|||
def exists(id: int):
|
||||
return SessionState.exists(id)
|
||||
|
||||
@staticmethod
|
||||
def all():
|
||||
return SessionState.all()
|
||||
|
||||
@staticmethod
|
||||
def update_or_create(session_state):
|
||||
session_state.save()
|
||||
|
|
|
|||
16
core/models/session/NetworkPortNumbers.py
Normal file
16
core/models/session/NetworkPortNumbers.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
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
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
from core.Constants import Constants
|
||||
from core.models.session.NetworkPortNumbers import NetworkPortNumbers
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses_json import config, Exclude, dataclass_json
|
||||
from json import JSONDecodeError
|
||||
|
|
@ -17,7 +18,7 @@ class SessionState:
|
|||
id: int = field(
|
||||
metadata=config(exclude=Exclude.ALWAYS)
|
||||
)
|
||||
network_port_numbers: list[int] = field(default_factory=list)
|
||||
network_port_numbers: NetworkPortNumbers = field(default_factory=NetworkPortNumbers)
|
||||
process_ids: list[int] = field(default_factory=list)
|
||||
|
||||
def get_state_path(self):
|
||||
|
|
@ -39,25 +40,53 @@ class SessionState:
|
|||
@staticmethod
|
||||
def find_by_id(id: int):
|
||||
|
||||
state_path = SessionState.__get_state_path(id)
|
||||
|
||||
try:
|
||||
session_state_file_contents = open(f'{SessionState.__get_state_path(id)}/state.json', 'r').read()
|
||||
session_state_file_contents = Path(f'{state_path}/state.json').read_text()
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
try:
|
||||
|
||||
session_state = json.loads(session_state_file_contents)
|
||||
except JSONDecodeError:
|
||||
session_state['id'] = id
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
return SessionState.from_dict(session_state)
|
||||
|
||||
except (JSONDecodeError, AttributeError):
|
||||
|
||||
shutil.rmtree(Path(state_path), ignore_errors=True)
|
||||
return None
|
||||
|
||||
session_state['id'] = id
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
return SessionState.from_dict(session_state)
|
||||
|
||||
@staticmethod
|
||||
def exists(id: int):
|
||||
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
|
||||
def dissolve(id: int):
|
||||
|
||||
|
|
@ -71,12 +100,12 @@ class SessionState:
|
|||
shutil.rmtree(session_state_path, ignore_errors=True)
|
||||
|
||||
@staticmethod
|
||||
def __kill_associated_processes(session_state):
|
||||
def __kill_associated_processes(session_state: 'SessionState'):
|
||||
|
||||
associated_process_ids = list(session_state.process_ids)
|
||||
network_connections = psutil.net_connections()
|
||||
|
||||
for network_port_number in session_state.network_port_numbers:
|
||||
for network_port_number in session_state.network_port_numbers.isolated:
|
||||
|
||||
for network_connection in network_connections:
|
||||
|
||||
|
|
|
|||
|
|
@ -5,3 +5,6 @@ class ConnectionObserver(BaseObserver):
|
|||
|
||||
def __init__(self):
|
||||
self.on_connecting = []
|
||||
self.on_tor_bootstrapping = []
|
||||
self.on_tor_bootstrap_progressing = []
|
||||
self.on_tor_bootstrapped = []
|
||||
|
|
|
|||
|
|
@ -18,10 +18,12 @@ class WebServiceApiService:
|
|||
@staticmethod
|
||||
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)
|
||||
applications = []
|
||||
|
||||
if response.status_code == 200:
|
||||
if response.status_code == status_codes.OK:
|
||||
for application in response.json()['data']:
|
||||
applications.append(Application(application['code'], application['name'], application['id']))
|
||||
|
||||
|
|
@ -30,10 +32,12 @@ class WebServiceApiService:
|
|||
@staticmethod
|
||||
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)
|
||||
application_versions = []
|
||||
|
||||
if response.status_code == 200:
|
||||
if response.status_code == status_codes.OK:
|
||||
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']))
|
||||
|
||||
|
|
@ -42,10 +46,12 @@ class WebServiceApiService:
|
|||
@staticmethod
|
||||
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)
|
||||
client_versions = []
|
||||
|
||||
if response.status_code == 200:
|
||||
if response.status_code == status_codes.OK:
|
||||
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']))
|
||||
|
||||
|
|
@ -54,10 +60,12 @@ class WebServiceApiService:
|
|||
@staticmethod
|
||||
def get_operators(proxies: Optional[dict] = None):
|
||||
|
||||
from requests.status_codes import codes as status_codes
|
||||
|
||||
response = WebServiceApiService.__get('/operators', None, proxies)
|
||||
operators = []
|
||||
|
||||
if response.status_code == 200:
|
||||
if response.status_code == status_codes.OK:
|
||||
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']))
|
||||
|
||||
|
|
@ -66,10 +74,12 @@ class WebServiceApiService:
|
|||
@staticmethod
|
||||
def get_locations(proxies: Optional[dict] = None):
|
||||
|
||||
from requests.status_codes import codes as status_codes
|
||||
|
||||
response = WebServiceApiService.__get('/locations', None, proxies)
|
||||
locations = []
|
||||
|
||||
if response.status_code == 200:
|
||||
if response.status_code == status_codes.OK:
|
||||
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']))
|
||||
|
||||
|
|
@ -78,10 +88,12 @@ class WebServiceApiService:
|
|||
@staticmethod
|
||||
def get_subscription_plans(proxies: Optional[dict] = None):
|
||||
|
||||
from requests.status_codes import codes as status_codes
|
||||
|
||||
response = WebServiceApiService.__get('/subscription-plans', None, proxies)
|
||||
subscription_plans = []
|
||||
|
||||
if response.status_code == 200:
|
||||
if response.status_code == status_codes.OK:
|
||||
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']))
|
||||
|
||||
|
|
@ -90,12 +102,14 @@ class WebServiceApiService:
|
|||
@staticmethod
|
||||
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, {
|
||||
'subscription_plan_id': subscription_plan_id,
|
||||
'location_id': location_id
|
||||
}, proxies)
|
||||
|
||||
if response.status_code == 201:
|
||||
if response.status_code == status_codes.CREATED:
|
||||
return Subscription(response.headers['X-Billing-Code'])
|
||||
|
||||
else:
|
||||
|
|
@ -104,13 +118,15 @@ class WebServiceApiService:
|
|||
@staticmethod
|
||||
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_fragments = re.findall('....?', billing_code)
|
||||
billing_code = '-'.join(billing_code_fragments)
|
||||
|
||||
response = WebServiceApiService.__get('/subscriptions/current', billing_code, proxies)
|
||||
|
||||
if response.status_code == 200:
|
||||
if response.status_code == status_codes.OK:
|
||||
|
||||
subscription = response.json()['data']
|
||||
return Subscription(billing_code, Subscription.from_iso_format(subscription['expires_at']))
|
||||
|
|
@ -121,9 +137,11 @@ class WebServiceApiService:
|
|||
@staticmethod
|
||||
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)
|
||||
|
||||
if response.status_code == 200:
|
||||
if response.status_code == status_codes.OK:
|
||||
|
||||
response_data = response.json()['data']
|
||||
|
||||
|
|
@ -145,9 +163,11 @@ class WebServiceApiService:
|
|||
@staticmethod
|
||||
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)
|
||||
|
||||
if response.status_code == 200:
|
||||
if response.status_code == status_codes.OK:
|
||||
|
||||
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'])
|
||||
|
|
@ -158,11 +178,13 @@ class WebServiceApiService:
|
|||
@staticmethod
|
||||
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, {
|
||||
'public_key': public_key,
|
||||
}, proxies)
|
||||
|
||||
if response.status_code == 201:
|
||||
if response.status_code == status_codes.CREATED:
|
||||
return response.text
|
||||
else:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ dependencies = [
|
|||
"pysocks ~= 1.7.1",
|
||||
"python-dateutil ~= 2.9.0.post0",
|
||||
"requests ~= 2.32.5",
|
||||
"stem ~= 1.8.2",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
|
|
|
|||
Loading…
Reference in a new issue