Compare commits

..

5 commits

14 changed files with 231 additions and 91 deletions

View file

@ -1 +0,0 @@
this is june 13 version

View file

@ -1,3 +1,6 @@
from core.errors.exceptions import *
from core.errors.logger import logger
from collections.abc import Callable
from core.Constants import Constants
from core.controllers.ConfigurationController import ConfigurationController
@ -58,6 +61,8 @@ class ConnectionController:
connection = profile.connection
# needs_proxy_configuration comes from the child connection models
# for the system it returns false blindly. for the session is checks if the connection type is masked.
if connection.needs_proxy_configuration() and not profile.has_proxy_configuration():
if profile.has_subscription():
@ -75,6 +80,8 @@ class ConnectionController:
else:
raise MissingSubscriptionError()
# The needs_wireguard_configuration comes from the connection model, and can be transitioned to get it directly from the object's data
# The has_wireguard_configuration comes from each polymorph object doing an os check on if the wg config exists
if connection.needs_wireguard_configuration() and not profile.has_wireguard_configuration():
if profile.has_subscription():
@ -161,6 +168,7 @@ class ConnectionController:
port_number = None
proxy_port_number = None
# this is a check from SessionConnection of if there's a systemwide with mask
if profile.connection.is_unprotected():
if not ConnectionController.system_uses_wireguard_interface():
@ -246,42 +254,11 @@ class ConnectionController:
pass
if profile.connection.needs_operator_proxy():
operator_proxy_session = profile.get_operator_proxy_session()
protocol = profile.connection.get_protocol()
if protocol == 'vless':
from core.controllers.encrypted_proxy.VlessController import VlessController
from core.services.encrypted_proxy.vless_service import parse_vless_link
import socket
server_ip = operator_proxy_session.server_ip
if server_ip is None:
vless = parse_vless_link(operator_proxy_session.links[0])
server_ip = socket.gethostbyname(vless['host'])
ok = VlessController(1080).enable(
operator_proxy_session.links[0],
operator_proxy_session.username,
server_ip,
connection_observer
ok = ConnectionController.establish_encrypted_proxy_connection(
profile, socks5_port=1080, observer=connection_observer
)
elif protocol == 'hysteria2':
from core.controllers.encrypted_proxy.HysteriaController import HysteriaController
import socket
server_ip = operator_proxy_session.server_ip
if server_ip is None:
server_ip = socket.gethostbyname(operator_proxy_session.operator_hysteria2_host)
ok = HysteriaController(1080).enable(
operator_proxy_session.username,
operator_proxy_session.password,
operator_proxy_session.operator_hysteria2_host,
server_ip,
connection_observer
)
else:
ok = False
if not ok:
raise ConnectionError('The connection could not be established.')
token = SystemStateController.create(profile.id)
if connection_observer is not None:
connection_observer.notify('connected_token', {'session_token': token})
@ -317,6 +294,40 @@ class ConnectionController:
ConnectionController.terminate_tor_connection()
time.sleep(1.0)
@staticmethod
def establish_encrypted_proxy_connection(profile, socks5_port: int = 1080, observer=None) -> bool:
"""Shared encrypted proxy connection logic for CLI and GUI."""
import socket
operator_proxy_session = profile.get_operator_proxy_session()
protocol = profile.connection.code
if protocol == 'vless':
from core.controllers.encrypted_proxy.VlessController import VlessController
from core.services.encrypted_proxy.vless_service import parse_vless_link
server_ip = operator_proxy_session.server_ip
if server_ip is None:
vless = parse_vless_link(operator_proxy_session.links[0])
server_ip = socket.gethostbyname(vless['host'])
return VlessController(socks5_port).enable(
operator_proxy_session.links[0],
operator_proxy_session.username,
server_ip,
observer
)
elif protocol == 'hysteria2':
from core.controllers.encrypted_proxy.HysteriaController import HysteriaController
server_ip = operator_proxy_session.server_ip
if server_ip is None:
server_ip = socket.gethostbyname(operator_proxy_session.operator_hysteria2_host)
return HysteriaController(socks5_port).enable(
operator_proxy_session.username,
operator_proxy_session.password,
operator_proxy_session.operator_hysteria2_host,
server_ip,
observer
)
return False
@staticmethod
def establish_tor_connection(connection_observer: Optional[ConnectionObserver] = None):
@ -336,9 +347,13 @@ class ConnectionController:
@staticmethod
def establish_tor_session_connection(port_number: int, connection_observer: Optional[ConnectionObserver] = None):
try:
tor_module = TorModule(Constants.HV_TOR_STATE_HOME)
tor_module.create_session(port_number, connection_observer)
except TorServiceInitializationError as e:
logger.error(f"TorServiceInitializationError. Tor Can't Start: {e}")
except Exception as e:
logger.error(f"Tor Can't Start: {e}")
@staticmethod
def terminate_tor_session_connection(port_number: int):

View file

@ -9,10 +9,23 @@ class SubscriptionPlanController:
@staticmethod
def get(connection: Union[SessionConnection, SystemConnection], duration: int):
"""
Called by:
GUI in worker.py
Purpose:
confirm the subscription's length is valid
"""
return SubscriptionPlan.find(connection, duration)
@staticmethod
def get_all(connection: Optional[Union[SessionConnection, SystemConnection]] = None):
"""
Not used. Good candidate to be cut.
GUI's create_interface_elements
inside duration selection page actually has hardcoded amounts
"""
return SubscriptionPlan.all(connection)
@staticmethod

View file

@ -1,3 +1,7 @@
class TorServiceInitializationError(Exception):
pass
class ApplicationError(Exception):
pass

View file

@ -1,18 +1,14 @@
from dataclasses import dataclass
from dataclasses_json import dataclass_json
@dataclass_json
@dataclass
class BaseConnection:
code: str
# called by: connection controller for basic type checks on wireguard code
def needs_wireguard_configuration(self):
return self.code == 'wireguard'
def is_session_connection(self):
return type(self).__name__ == 'SessionConnection'
def is_system_connection(self):
return type(self).__name__ == 'SystemConnection'
def needs_operator_proxy(self):

View file

@ -53,7 +53,7 @@ class SubscriptionPlan(Model):
if connection.code == 'wireguard':
features_wireguard = True
if connection.code == 'operator':
if connection.code in ('operator', 'vless', 'hysteria2'):
features_proxy = True
Model._create_table_if_not_exists(table_name=_table_name, table_definition=_table_definition)
@ -79,7 +79,7 @@ class SubscriptionPlan(Model):
if connection.code == 'wireguard':
features_wireguard = True
if connection.code == 'operator':
if connection.code in ('operator', 'vless', 'hysteria2'):
features_proxy = True
return Model._query_all('SELECT * FROM subscription_plans WHERE features_proxy = ? AND features_wireguard = ?', SubscriptionPlan.factory, [features_proxy, features_wireguard])

View file

@ -27,11 +27,10 @@ def extract_from_nested(obj, yaml_keys, default=None):
This function removes unnecessary nesting,
by comparing it to a pre-made YAML mapping
"""
def denormalize(data, which_yaml_file):
def denormalize(data, mapping_file):
final_results = []
# prep the mapping config:
mapping_file = f"assets/yaml_mappings/{which_yaml_file}"
with open(mapping_file) as f:
mapping = yaml.safe_load(f)

View file

@ -1,6 +1,6 @@
from core.errors.logger import logger
from core.models.orm_models.Base import BaseModel
from sqlalchemy import create_engine
from sqlalchemy import create_engine, inspect
from sqlalchemy.orm import sessionmaker
import os
from pathlib import Path
@ -18,9 +18,15 @@ def get_path():
"""Returns XDG Base Directory path or ~/.local/share/hydra-veil"""
xdg_data = os.getenv("XDG_DATA_HOME")
if xdg_data:
return Path(xdg_data) / "my-app"
return Path(xdg_data) / "hydra-veil"
return Path.home() / ".local" / "share" / "hydra-veil"
def does_it_exist(filepath):
if filepath.exists():
return True
else:
return False
# ============================================================================
# GLOBAL STATE
# ============================================================================
@ -80,33 +86,42 @@ def create_ONLY_db_version_table():
raise RuntimeError("Engine not initialized. Call _reinitialize_engine_and_session() first.")
from core.models.orm_models.DatabaseVersion import database_version
database_version.create(engine, checkfirst=True)
# DatabaseVersion.__table__.create(engine, checkfirst=True)
try:
database_version.create(engine)
except:
logger.info("[DB MANAGEMENT] database_version already exists, but was attempted to be made again by calling create_ONLY_db_version_table.")
# if using without checking or try except blocks:
# database_version.create(engine, checkfirst=True)
def does_db_version_table_exist():
if engine is None:
raise RuntimeError("Engine not initialized. Call _reinitialize_engine_and_session() first.")
inspector = inspect(engine)
table_exists = inspector.has_table("database_version")
return table_exists
def create_ALL_tables():
"""Create all tables from BaseModel.metadata using the global engine."""
print("starting create_ALL_tables")
if engine is None:
raise RuntimeError("Engine not initialized. Call _reinitialize_engine_and_session() first.")
print("passed engine test")
from core.models.orm_models.Location import Location
from core.models.orm_models.Operator import Operator
from core.models.orm_models.CachedSync import CachedSync
from core.models.orm_models.EncryptedProxy import EncryptedProxy
from core.models.SubscriptionPlan import SubscriptionPlan
from core.models.session.ApplicationVersion import ApplicationVersion
try:
print("trying this")
BaseModel.metadata.create_all(engine, checkfirst=True)
print("Returning True for Creating all Tables..")
logger.info("[DB MANAGEMENT] All Tables have been successfully created.")
return True
except:
print("couldn't make all tables")
logger.error("[DB MANAGEMENT] Fatal Error with creating all tables in the create_ALL_tables function of session management.")
return False

View file

@ -9,6 +9,8 @@ class SessionConnection(BaseConnection):
if self.code not in ('system', 'tor', 'wireguard', 'vless', 'hysteria2'):
raise ValueError('Invalid connection code.')
# called by connection controller
# doesn't even make sense, it's checking if the Session is code system. this will always be false.
def is_unprotected(self):
return self.code == 'system' and self.masked is False

View file

@ -8,7 +8,7 @@ class SystemConnection(BaseConnection):
protocol: Optional[str] = field(default=None)
def __post_init__(self):
if self.code not in ('wireguard', 'operator'):
if self.code not in ('wireguard', 'operator', 'vless', 'hysteria2'):
raise ValueError('Invalid connection code.')
@staticmethod
@ -16,7 +16,7 @@ class SystemConnection(BaseConnection):
return False
def needs_operator_proxy(self):
return self.code == 'operator'
return self.code in ('operator', 'vless', 'hysteria2')
def get_protocol(self):
return self.protocol
return self.protocol if self.protocol else self.code

View file

@ -16,54 +16,98 @@ from core.errors.logger import logger
import json, os
from typing import Any
from core.Constants import Constants
import time
def tor_get_request(custom_url: str, connection_observer: ConnectionObserver) -> dict:
import requests
# ============================================================================
# INITIALIZE TOR
# ============================================================================
port_number = ConnectionService.get_random_available_port_number()
logger.debug(f"Using Tor on port number {port_number}")
logger.debug(f"[USE TOR SERVICE] Using Tor on port number {port_number}")
try:
tor_module = TorModule(Constants.HV_TOR_STATE_HOME)
tor_module.create_session(port_number, connection_observer)
logger.debug(f"[USE TOR SERVICE] Tor Started a Session")
except TorServiceInitializationError as e:
error_msg = f"[USE TOR SERVICE] Tor failed to start: {e}"
tor_module.destroy_session(port_number)
logger.error(error_msg, exc_info=True)
problem_results = evaluate_tor_networking_problem(
custom_url, connection_observer
)
return problem_results
except Exception as e:
logger.error(f"[USE TOR SERVICE] Unexpected Tor error: {e}")
tor_module.destroy_session(port_number)
problem_results = evaluate_tor_networking_problem(
custom_url, connection_observer
)
return problem_results
proxies = {
"http": f"socks5h://127.0.0.1:{port_number}",
"https": f"socks5h://127.0.0.1:{port_number}",
}
logger.debug(f"Doing Request through Tor via port {port_number}")
# ============================================================================
# USE TOR AS PROXY
# ============================================================================
try:
logger.debug(f"[TOR GET REQUEST] Using Tor on port number {port_number}")
response = requests.get(custom_url, proxies=proxies, timeout=5)
logger.debug("Request through Tor successful.")
logger.debug("[TOR GET REQUEST] Request through Tor Successful!")
# if it crashes from the above error check, then it won't destroy the proxy,
# if it crashes from an error in trying to get a JSON out of it, then we'd skip destroying the Tor session,
# so we'll destroy it now first,
logger.debug("[TOR GET REQUEST] DESTROY SESSION!")
tor_module.destroy_session(port_number)
# return response
# Now that it's safe, get a JSON out of it, and return the response
dictonary_of_response = response.json()
logger.debug(f"[TOR GET REQUEST] Got Data out of the reply. {dictonary_of_response}")
final_reply = {"valid": True, "data": dictonary_of_response}
return final_reply
except requests.exceptions.ConnectionError:
tor_module.destroy_session(port_number)
problem_results = evaluate_tor_networking_problem(
custom_url, connection_observer, port_number, proxies
custom_url, connection_observer
)
return problem_results
except requests.exceptions.Timeout:
logger.debug("Connection timed out")
tor_module.destroy_session(port_number)
problem_results = evaluate_tor_networking_problem(
custom_url, connection_observer, port_number
custom_url, connection_observer
)
return problem_results
except requests.exceptions.HTTPError:
logger.debug(f"HTTP error: {response.status_code}")
tor_module.destroy_session(port_number)
problem_results = evaluate_tor_networking_problem(
custom_url, connection_observer, port_number
custom_url, connection_observer
)
return problem_results
except requests.exceptions.ProxyError:
error_msg = "[USE TOR SERVICE] Requests Proxy Error"
tor_module.destroy_session(port_number)
logger.error(error_msg, exc_info=True)
problem_results = evaluate_tor_networking_problem(
custom_url, connection_observer
)
return problem_results
except Exception as e:
logger.error(f"[USE TOR SERVICE] Unexpected Tor error: {e}")
tor_module.destroy_session(port_number)
problem_results = evaluate_tor_networking_problem(
custom_url, connection_observer
)
return problem_results
@ -80,41 +124,63 @@ def tor_post_request(
"status": "unable_to_get"
} # incase this goes to the except block, it's "defined"
# ============================================================================
# INITIALIZE TOR
# ============================================================================
port_number = ConnectionService.get_random_available_port_number()
logger.debug(f"Using Tor on port number {port_number}")
try:
tor_module = TorModule(Constants.HV_TOR_STATE_HOME)
tor_module.create_session(port_number, connection_observer)
except TorServiceInitializationError as e:
error_msg = f"[USE TOR SERVICE] Tor failed to start: {e}"
tor_module.destroy_session(port_number)
logger.error(error_msg, exc_info=True)
problem_results = evaluate_tor_networking_problem(
custom_url, connection_observer
)
return problem_results
except Exception as e:
logger.error(f"[USE TOR SERVICE] Unexpected Tor error: {e}")
tor_module.destroy_session(port_number)
problem_results = evaluate_tor_networking_problem(
custom_url, connection_observer
)
return problem_results
proxies = {
"http": f"socks5h://127.0.0.1:{port_number}",
"https": f"socks5h://127.0.0.1:{port_number}",
}
logger.debug(f"Sending data through Tor via port {port_number}")
# ============================================================================
# USE TOR AS PROXY
# ============================================================================
try:
response = requests.post(custom_url, json=payload, proxies=proxies)
logger.debug("Sending data through Tor successful.")
logger.debug("[USE TOR SERVICE] Sent data through Tor successfully. Now deleting the session..")
tor_module.destroy_session(port_number)
return response
except requests.exceptions.ConnectionError:
error_msg = "There was a Connection Error with the Tor Module."
error_msg = "[USE TOR SERVICE] There was a Connection Error with the Use Tor Module."
logger.error(error_msg, exc_info=True)
logger.debug(error_msg)
tor_module.destroy_session(port_number)
problem_results = evaluate_tor_networking_problem(
custom_url, connection_observer
)
return problem_results
except requests.exceptions.Timeout:
error_msg = "Connection timed out"
error_msg = "[USE TOR SERVICE] Connection timed out"
logger.error(error_msg, exc_info=True)
logger.debug(error_msg)
tor_module.destroy_session(port_number)
problem_results = evaluate_tor_networking_problem(
custom_url, connection_observer
)
@ -122,10 +188,28 @@ def tor_post_request(
except requests.exceptions.HTTPError:
if isinstance(response, requests.Response):
error_msg = f"HTTP error: {response.status_code}"
error_msg = f"[USE TOR SERVICE] HTTP error: {response.status_code}"
logger.error(error_msg, exc_info=True)
logger.debug(error_msg)
tor_module.destroy_session(port_number)
problem_results = evaluate_tor_networking_problem(
custom_url, connection_observer
)
return problem_results
except requests.exceptions.ProxyError:
error_msg = "[USE TOR SERVICE] Requests Proxy Error"
logger.error(error_msg, exc_info=True)
tor_module.destroy_session(port_number)
problem_results = evaluate_tor_networking_problem(
custom_url, connection_observer
)
return problem_results
except Exception as e:
logger.error(f"[USE TOR SERVICE] Unexpected Tor error: {e}")
tor_module.destroy_session(port_number)
problem_results = evaluate_tor_networking_problem(
custom_url, connection_observer
)

View file

@ -5,6 +5,7 @@ from core.models.orm_models.Base import Base
from core.observers.ClientObserver import ClientObserver
from core.observers.ConnectionObserver import ConnectionObserver
from core.errors.logger import logger
from core.utils.basic_operations.get_parent_directory import get_parent_directory
from typing import Optional
import json
@ -29,8 +30,11 @@ def sync_one_orm_model(
new_data = api_result.get("data")
# prep data (denormalize)
parent_directory = get_parent_directory()
yaml_filename = f"{which_endpoint}.yaml"
denormalized_data = denormalize(new_data, yaml_filename)
full_yaml_path = f"{parent_directory}/assets/yaml_mappings/{yaml_filename}"
denormalized_data = denormalize(new_data, str(full_yaml_path))
if denormalized_data:
# Debug Pretty print with indentation

View file

@ -0,0 +1,8 @@
from pathlib import Path
import core
def get_parent_directory():
# Get core's parent directory
parent_directory = Path(core.__file__).parent.parent
return str(parent_directory)

View file

@ -1,6 +1,6 @@
[project]
name = "sp-hydra-veil-core"
version = "2.3.6"
version = "2.3.8"
authors = [
{ name = "Simplified Privacy" },
]
@ -19,7 +19,7 @@ dependencies = [
"pysocks ~= 1.7.1",
"python-dateutil ~= 2.9.0.post0",
"requests ~= 2.32.5",
"sp-essentials ~= 1.1.0",
"sp-essentials ~= 1.2.0",
"annotated-types==0.7.0",
"certifi==2026.4.22",
"charset-normalizer==3.4.7",
@ -38,6 +38,7 @@ dependencies = [
"pydantic_core==2.46.3",
"pydeps==3.0.6",
"pytokens==0.4.1",
"PyYAML==6.0.3",
"stdlib-list==0.12.0",
"SQLAlchemy==2.0.51",
"toolz==1.1.0",