diff --git a/eduvpn/app.py b/eduvpn/app.py
index 0d5b722e..f5085937 100644
--- a/eduvpn/app.py
+++ b/eduvpn/app.py
@@ -8,7 +8,7 @@
from eduvpn_common.main import EduVPN, ServerType, WrappedError
from eduvpn_common.state import State, StateType
-from eduvpn_common.types import ProxyReady, ProxySetup, ReadRxBytes, RefreshList # type: ignore[attr-defined]
+from eduvpn_common.types import ProxySetup, ReadRxBytes, RefreshList # type: ignore[attr-defined]
from eduvpn import nm
from eduvpn.config import Configuration
@@ -21,6 +21,7 @@
parse_tokens,
)
from eduvpn.keyring import DBusKeyring, InsecureFileKeyring, TokenKeyring
+from eduvpn.proxy import Proxy
from eduvpn.server import ServerDatabase, parse_profiles, parse_required_transition
from eduvpn.utils import (
handle_exception,
@@ -144,8 +145,8 @@ def __init__(
self.nm_manager = nm_manager
self._was_tcp = False
self._should_failover = False
- self._peer_ips_proxy = None
self._refresh_list_handler = RefreshList(self.refresh_list)
+ self._proxy_setup_handler = ProxySetup(self.on_proxy_setup)
@property
def keyring(self):
@@ -327,22 +328,8 @@ def save_tokens(self, server_id: str, server_type: int, tokens: str):
logger.error("Failed saving tokens with exception:")
logger.error(e, exc_info=True)
- def on_proxy_setup(self, fd, peer_ips):
- logger.debug(f"got proxy fd: {fd}, peer_ips: {peer_ips}")
- self._peer_ips_proxy = json.loads(peer_ips)
-
- @run_in_background_thread("start-proxy")
- def start_proxy(self, proxy, callback):
- try:
- self.common.start_proxyguard(
- proxy.listen,
- proxy.source_port,
- proxy.peer,
- ProxySetup(self.on_proxy_setup),
- ProxyReady(lambda: callback()),
- )
- except Exception as e:
- handle_exception(self.common, e)
+ def on_proxy_setup(self, fd):
+ logger.debug(f"got proxy fd: {fd}")
def connect(
self,
@@ -424,21 +411,22 @@ def connect(config):
if not self.common.in_state(State.CONNECTING):
self.common.set_state(State.CONNECTING)
connection = Connection.parse(config)
+ proxy = None
+ if config.proxy is not None:
+ wrapper_proxy = self.common.new_proxyguard(
+ config.proxy.listen_port, config.proxy.source_port, config.proxy.peer, self._proxy_setup_handler
+ )
+ proxy = Proxy(self.common, config.proxy, wrapper_proxy)
connection.connect(
self.nm_manager,
config.default_gateway,
self.config.allow_wg_lan,
config.dns_search_domains,
- config.proxy,
- self._peer_ips_proxy,
+ proxy,
on_connect,
)
- self._peer_ips_proxy = None
- if config.proxy:
- self.start_proxy(config.proxy, lambda: connect(config))
- else:
- connect(config)
+ connect(config)
def reconnect(self, callback: Optional[Callable] = None, prefer_tcp: bool = False):
def on_disconnected(success: bool):
@@ -588,7 +576,7 @@ def search_custom(self, query: str) -> Iterator[Any]:
class Application:
def __init__(self, variant: ApplicationVariant, common: EduVPN) -> None:
self.variant = variant
- self.nm_manager = nm.NMManager(variant)
+ self.nm_manager = nm.NMManager(variant, common)
self.common = common
directory = variant.config_prefix
self.config = Configuration.load(directory)
diff --git a/eduvpn/cli.py b/eduvpn/cli.py
index 5899ded8..5ab243fe 100644
--- a/eduvpn/cli.py
+++ b/eduvpn/cli.py
@@ -67,7 +67,7 @@ def ask_profiles(setter, profiles, current: Optional[Profile] = None) -> bool:
return False
# Multiple profiles, print the index
- sorted_profiles = sorted(profiles.profiles.items(), key=lambda pair: str(pair[1]))
+ sorted_profiles = sorted(profiles.profiles.items(), key=lambda pair: pair[1])
# Multiple profiles, print the index
index = 0
choices = []
diff --git a/eduvpn/config.py b/eduvpn/config.py
index 60532358..eb59bb5a 100644
--- a/eduvpn/config.py
+++ b/eduvpn/config.py
@@ -19,10 +19,10 @@
class SettingDescriptor(Generic[T]):
- def __set_name__(self, owner: Type["Configuration"], name: str) -> None:
+ def __set_name__(self, _owner: Type["Configuration"], name: str) -> None:
self.name = name
- def __get__(self, instance: Type["Configuration"], owner: Type["Configuration"]) -> bool:
+ def __get__(self, instance: Type["Configuration"], _owner: Type["Configuration"]) -> bool:
return instance.get_setting(self.name) # type: ignore
def __set__(self, instance: Type["Configuration"], value: T) -> None:
diff --git a/eduvpn/connection.py b/eduvpn/connection.py
index 56b63a0d..0b4f60c5 100644
--- a/eduvpn/connection.py
+++ b/eduvpn/connection.py
@@ -3,9 +3,6 @@
from datetime import datetime, timedelta
from enum import IntEnum
from typing import Any, Dict, List, Optional
-from urllib.parse import urlparse
-
-from eduvpn.ovpn import Ovpn
class Token:
@@ -38,35 +35,22 @@ class Protocol(IntEnum):
WIREGUARDTCP = 3
-class Proxy:
+class ProxyConfig:
"""The class that represents a proxyguard instance
:param: peer: str: The remote peer string
- :param: listen: str: The listen proxy string
+ :param source_port: int: The source port for the TCP connection
+ :param: listen_port: str: The listen port for the proxy
"""
def __init__(
self,
peer: str,
source_port: int,
- listen: str,
+ listen_port: int,
):
self.peer = peer
self.source_port = source_port
- self.listen = listen
-
- @property
- def peer_scheme(self) -> str:
- try:
- parsed = urlparse(self.peer)
- return parsed.scheme
- except Exception:
- return ""
-
- @property
- def peer_port(self):
- if self.peer_scheme == "http":
- return 80
- return 443
+ self.listen_port = listen_port
class Config:
@@ -82,7 +66,7 @@ def __init__(
protocol: Protocol,
default_gateway: bool,
dns_search_domains: List[str],
- proxy: Proxy,
+ proxy: ProxyConfig,
should_failover: bool,
):
self.config = config
@@ -105,7 +89,7 @@ def parse_config(config_json: str) -> Config:
d = json.loads(config_json)
proxy = d.get("proxy", None)
if proxy:
- proxy = Proxy(proxy["peer"], proxy["source_port"], proxy["listen"])
+ proxy = ProxyConfig(proxy["peer"], proxy["source_port"], proxy["listen_port"])
cfg = Config(
d["config"],
Protocol(d["protocol"]),
@@ -186,14 +170,13 @@ def connect(self, callback):
class OpenVPNConnection(Connection):
- def __init__(self, ovpn: Ovpn):
- self.ovpn = ovpn
+ def __init__(self, config_str):
+ self.config_str = config_str
super().__init__()
@classmethod
def parse(cls, config_str: str) -> "OpenVPNConnection": # type: ignore
- ovpn = Ovpn.parse(config_str)
- return cls(ovpn=ovpn)
+ return cls(config_str=config_str)
def connect(
self,
@@ -202,11 +185,10 @@ def connect(
allow_lan,
dns_search_domains,
proxy,
- proxy_peer_ips,
callback,
):
manager.start_openvpn_connection(
- self.ovpn,
+ self.config_str,
default_gateway,
dns_search_domains,
callback=callback,
@@ -231,7 +213,6 @@ def connect(
allow_lan,
dns_search_domains,
proxy,
- proxy_peer_ips,
callback,
):
manager.start_wireguard_connection(
@@ -239,6 +220,5 @@ def connect(
default_gateway,
allow_wg_lan=allow_lan,
callback=callback,
- proxy_peer_ips=proxy_peer_ips,
proxy=proxy,
)
diff --git a/eduvpn/nm.py b/eduvpn/nm.py
index 4b8b81be..0d71f6e2 100644
--- a/eduvpn/nm.py
+++ b/eduvpn/nm.py
@@ -1,23 +1,22 @@
import enum
-import ipaddress
import logging
import os
import time
import uuid
from configparser import ConfigParser
+from contextlib import closing
from ipaddress import ip_address, ip_interface
from pathlib import Path
from shutil import rmtree
-from socket import AF_INET, AF_INET6, IPPROTO_TCP
+from socket import AF_INET, AF_INET6, IPPROTO_TCP, SOCK_DGRAM, socket
from tempfile import mkdtemp
-from typing import Any, Callable, Optional, TextIO, Tuple
+from typing import Any, Callable, Optional, TextIO
-from eduvpn_common.main import Jar
+from eduvpn_common.main import EduVPN, Jar
from gi.repository.Gio import Cancellable, Task # type: ignore
-from eduvpn.ovpn import Ovpn
-from eduvpn.storage import get_uuid, set_uuid, write_ovpn
-from eduvpn.utils import run_in_glib_thread
+from eduvpn.storage import get_uuid, set_uuid
+from eduvpn.utils import run_in_background_thread, run_in_glib_thread
from eduvpn.variants import ApplicationVariant
_logger = logging.getLogger(__name__)
@@ -79,17 +78,24 @@ def from_active_state(cls, state: "NM.ActiveConnectionState") -> "ConnectionStat
raise ValueError(state)
+def find_free_udp_port():
+ with closing(socket(AF_INET, SOCK_DGRAM)) as s:
+ s.bind(("", 0))
+ return s.getsockname()[1]
+
+
# A manager for a manager :-)
class NMManager:
- def __init__(self, variant: ApplicationVariant):
+ def __init__(self, variant: ApplicationVariant, common_lib: EduVPN):
self.variant = variant
self.proxy = None
try:
self._client = NM.Client.new(None)
- self.wg_gateway_ip: Optional[ipaddress.IPv4Address] = None
+ self.wg_gateway_ip: Optional[str] = None
except Exception:
self._client = None
self.cancel_jar = Jar(lambda x: x.cancel())
+ self.common_lib = common_lib
@property
def client(self) -> "NM.Client":
@@ -101,6 +107,15 @@ def client(self) -> "NM.Client":
def available(self) -> bool:
return self._client is not None
+ @run_in_background_thread("proxyguard-tunnel")
+ def proxy_tunnel(self, listen_port):
+ assert self.proxy is not None
+ _logger.debug(f"tunneling proxyguard with listen port: {listen_port}")
+ try:
+ self.proxy.tunnel(listen_port)
+ except Exception as e:
+ self.proxy.forward_exception(e)
+
# TODO: Move this somewhere else?
def open_stats_file(self, filename: str) -> Optional[TextIO]:
"""
@@ -304,7 +319,7 @@ def failover_endpoint_ip(self) -> Optional[str]:
if not self.wg_gateway_ip:
_logger.debug("no wg gateway ip found in failover endpoint")
return None
- return str(self.wg_gateway_ip)
+ return self.wg_gateway_ip
else:
_logger.debug(f"Unknown protocol: {protocol}")
return None
@@ -332,18 +347,7 @@ def ovpn_import(self, target: Path) -> Optional["NM.Connection"]:
conn.normalize()
return conn
- def import_ovpn_with_certificate(self, ovpn: Ovpn, private_key: str, certificate: str) -> "NM.SimpleConnection":
- """
- Import the OVPN string into Network Manager.
- """
- target_parent = Path(mkdtemp())
- target = target_parent / f"{self.variant.name}.ovpn"
- write_ovpn(ovpn, private_key, certificate, target)
- connection = self.ovpn_import(target)
- rmtree(target_parent)
- return connection
-
- def import_ovpn(self, ovpn: Ovpn) -> "NM.SimpleConnection":
+ def import_ovpn(self, ovpn: str) -> "NM.SimpleConnection":
"""
Import the OVPN string into Network Manager.
"""
@@ -351,7 +355,7 @@ def import_ovpn(self, ovpn: Ovpn) -> "NM.SimpleConnection":
target = target_parent / f"{self.variant.name}.ovpn"
_logger.debug(f"Writing configuration to {target}")
with open(target, mode="w+t") as f:
- ovpn.write(f)
+ f.write(ovpn)
connection = self.ovpn_import(target)
rmtree(target_parent)
return connection
@@ -396,7 +400,7 @@ def set_setting_ensure_permissions(self, con: "NM.SimpleConnection") -> "NM.Simp
con.add_setting(s_con)
return con
- def start_openvpn_connection(self, ovpn: Ovpn, default_gateway, dns_search_domains, *, callback=None) -> None:
+ def start_openvpn_connection(self, ovpn: str, default_gateway, dns_search_domains, *, callback=None) -> None:
_logger.debug("writing ovpn configuration to Network Manager")
new_con = self.import_ovpn(ovpn)
s_ip4 = new_con.get_setting_ip4_config()
@@ -441,7 +445,6 @@ def start_wireguard_connection( # noqa: C901
*,
allow_wg_lan=False,
proxy=None,
- proxy_peer_ips=None,
callback=None,
) -> None:
_logger.debug("writing wireguard configuration to Network Manager")
@@ -452,7 +455,8 @@ def start_wireguard_connection( # noqa: C901
addr = ip_interface(ip.strip())
if addr.version == 4:
if not self.wg_gateway_ip:
- self.wg_gateway_ip = addr.network[1]
+ net_str = str(addr.network)
+ self.wg_gateway_ip = self.common_lib.calculate_gateway(net_str)
ipv4s.append(NM.IPAddress(AF_INET, str(addr.ip), addr.network.prefixlen))
elif addr.version == 6:
ipv6s.append(NM.IPAddress(AF_INET6, str(addr.ip), addr.network.prefixlen))
@@ -546,6 +550,13 @@ def start_wireguard_connection( # noqa: C901
fwmark = int(os.environ.get("EDUVPN_WG_FWMARK", 51860))
listen_port = int(os.environ.get("EDUVPN_WG_LISTEN_PORT", 0))
+ # if we are using proxyguard we need to know the port beforehand
+ # just get an available port
+ # Yes, this can race but there is not other option right now
+ # the NM API just gives port 0 from device.get_listen_port()
+ if proxy is not None and listen_port == 0:
+ listen_port = find_free_udp_port()
+
s_ip4.set_property(NM.SETTING_IP_CONFIG_ROUTE_TABLE, fwmark)
s_ip6.set_property(NM.SETTING_IP_CONFIG_ROUTE_TABLE, fwmark)
w_con.set_property(NM.DEVICE_WIREGUARD_FWMARK, fwmark)
@@ -571,7 +582,7 @@ def start_wireguard_connection( # noqa: C901
if proxy:
dport_proxy = proxy.peer_port
- for proxy_peer_ip in proxy_peer_ips:
+ for proxy_peer_ip in proxy.peer_ips:
address = ip_address(proxy_peer_ip)
if address.version != ipver:
continue
@@ -612,7 +623,13 @@ def start_wireguard_connection( # noqa: C901
self.proxy = proxy
- self.set_connection(profile, callback) # type: ignore
+ def set_callback(success: bool):
+ if success and proxy is not None:
+ self.proxy_tunnel(listen_port)
+ if callback is not None:
+ callback(success)
+
+ self.set_connection(profile, set_callback) # type: ignore
def new_cancellable(self):
c = Cancellable.new()
@@ -882,16 +899,6 @@ def wrapped_connection_added(client: "NM.Client", active_con: "NM.ActiveConnecti
self.client.connect("active-connection-added", wrapped_connection_added)
return True
- def connection_status(
- self,
- ) -> Tuple[Optional[str], Optional["NM.ActiveConnectionState"]]:
- con = self.client.get_primary_connection()
- if not isinstance(con, NM.VpnConnection):
- return None, None
- uuid = con.get_uuid()
- status = con.get_state()
- return uuid, status
-
def action_with_mainloop(action: Callable):
_logger.debug("calling action with CLI mainloop")
diff --git a/eduvpn/ovpn.py b/eduvpn/ovpn.py
deleted file mode 100644
index f9cae47c..00000000
--- a/eduvpn/ovpn.py
+++ /dev/null
@@ -1,99 +0,0 @@
-from io import StringIO, TextIOWrapper
-from typing import Iterable, List
-
-
-class Item:
- def to_string(self) -> str:
- raise NotImplementedError
-
- def write(self, file: TextIOWrapper) -> None:
- line = self.to_string()
- file.write(f"{line}\n")
-
- def __eq__(self, other):
- return self.__class__ is other.__class__ and self.__dict__ == other.__dict__
-
- def __repr__(self):
- fields = ",".join(f" {k}={v!r}" for k, v in self.__dict__.items())
- return f"<{self.__class__.__name__}{fields}>"
-
-
-class Field(Item):
- def __init__(self, name: str, arguments: List[str]) -> None:
- self.name = name
- self.arguments = arguments
-
- def to_string(self) -> str:
- return f'{self.name} {" ".join(self.arguments)}'
-
-
-class Section(Item):
- def __init__(self, tag: str, content: List[str]) -> None:
- self.tag = tag
- self.content = content
-
- def to_string(self) -> str:
- lines = []
- lines.append(f"<{self.tag}>")
- lines.extend(self.content)
- lines.append(f"{self.tag}>")
- return "\n".join(lines)
-
-
-class Comment(Item):
- def __init__(self, content: str) -> None:
- self.content = content
-
- def to_string(self) -> str:
- return f"#{self.content}"
-
-
-class Empty(Item):
- def to_string(self):
- return ""
-
-
-class InvalidOVPN(Exception):
- pass
-
-
-def parse_ovpn(lines: Iterable[str]) -> Iterable[Item]:
- current_section = None
- for _lineno, line in enumerate(lines):
- if current_section is not None:
- if line.strip() == f"{current_section.tag}>":
- yield current_section
- current_section = None
- else:
- current_section.content.append(line)
- elif line.startswith("#"):
- yield Comment(line.rstrip()[1:])
- elif line.startswith("<"):
- line = line.rstrip()
- assert line.endswith(">")
- section = Section(line[1:-1], [])
- current_section = section
- elif line.rstrip() == "":
- yield Empty()
- else:
- field_name, *arguments = line.rstrip().split()
- yield Field(field_name, arguments)
- assert current_section is None
-
-
-class Ovpn:
- def __init__(self, content: List[Item]) -> None:
- self.content = content
-
- @classmethod
- def parse(cls, content: str) -> "Ovpn":
- return cls(list(parse_ovpn(content.splitlines())))
-
- def write(self, file: TextIOWrapper) -> None:
- for item in self.content:
- item.write(file)
-
- def to_string(self) -> str:
- file = StringIO()
- self.write(file)
- return file.getvalue()
diff --git a/eduvpn/proxy.py b/eduvpn/proxy.py
new file mode 100644
index 00000000..c6316e46
--- /dev/null
+++ b/eduvpn/proxy.py
@@ -0,0 +1,58 @@
+from typing import List
+from urllib.parse import urlparse
+
+from eduvpn.utils import handle_exception
+
+
+class Proxy:
+ """The class that represents a proxyguard instance
+ :param: common: The common library
+ :param: peer: str: The remote peer string
+ :param: listen: str: The listen proxy string
+ """
+
+ def __init__(
+ self,
+ common,
+ config,
+ wrapper,
+ ):
+ self.common = common
+ self.config = config
+ self.wrapper = wrapper
+
+ def forward_exception(self, error):
+ handle_exception(self.common, error)
+
+ def tunnel(self, wgport):
+ self.wrapper.tunnel(wgport)
+
+ @property
+ def peer(self) -> str:
+ return self.config.peer
+
+ @property
+ def source_port(self) -> int:
+ return self.config.source_port
+
+ @property
+ def listen_port(self) -> int:
+ return self.config.listen_port
+
+ @property
+ def peer_ips(self) -> List[str]:
+ return self.wrapper.peer_ips
+
+ @property
+ def peer_scheme(self) -> str:
+ try:
+ parsed = urlparse(self.config.peer)
+ return parsed.scheme
+ except Exception:
+ return ""
+
+ @property
+ def peer_port(self):
+ if self.peer_scheme == "http":
+ return 80
+ return 443
diff --git a/eduvpn/server.py b/eduvpn/server.py
index d5299a6e..bf3aebdb 100644
--- a/eduvpn/server.py
+++ b/eduvpn/server.py
@@ -15,7 +15,6 @@
from eduvpn.settings import IMAGE_PREFIX
logger = logging.getLogger(__name__)
-TranslatedStr = Union[str, Dict[str, str]]
class Profile:
@@ -23,16 +22,23 @@ class Profile:
:param: identifier: str: The identifier (id) of the profile
:param: display_name: str: The display name of the profile
:param: default_gateway: str: Whether or not this profile should have the default gateway set
+ :param: priority: int: The priority of the profile for sorting in the UI
"""
- def __init__(self, identifier: str, display_name: Dict[str, str], default_gateway: bool):
+ def __init__(self, identifier: str, display_name: Dict[str, str], default_gateway: bool, priority: int):
self.identifier = identifier
self.display_name = display_name
self.default_gateway = default_gateway
+ self.priority = priority
def __str__(self):
return extract_translation(self.display_name)
+ def __lt__(self, o) -> bool:
+ if self.priority == o.priority:
+ return str(self) < str(o)
+ return self.priority > o.priority
+
class Profiles:
"""The class that represents a list of profiles
@@ -85,14 +91,6 @@ def identifier(self) -> str:
def category_id(self) -> ServerType:
return ServerType.CUSTOM
- @property
- def category(self) -> str:
- """Return the category of the server as a string
- :return: The category string
- :rtype: str
- """
- return str(self.category_id)
-
class InstituteServer(Server):
"""The class that represents an Institute Access Server
@@ -117,14 +115,6 @@ def __init__(
def category_id(self) -> ServerType:
return ServerType.INSTITUTE_ACCESS
- @property
- def category(self) -> str:
- """Return the category of the server as a string
- :return: The category string
- :rtype: str
- """
- return str(self.category_id)
-
class SecureInternetServer(Server):
"""The class that represents a Secure Internet Server
@@ -157,14 +147,6 @@ def __init__(
def category_id(self) -> ServerType:
return ServerType.SECURE_INTERNET
- @property
- def category(self) -> str:
- """Return the category of the server as a string
- :return: The category string
- :rtype: str
- """
- return str(self.category_id)
-
def parse_secure_internet(si: dict) -> Optional[SecureInternetServer]:
profiles = parse_profiles(si["profiles"])
@@ -203,7 +185,7 @@ def parse_profiles(profiles: dict) -> Profiles:
profile_map = profiles.get("map", {})
for k, v in profile_map.items():
# TODO: Default gateway
- returned[k] = Profile(k, v["display_name"], False)
+ returned[k] = Profile(k, v["display_name"], False, int(v.get("priority", 0)))
return Profiles(returned, profiles["current"])
diff --git a/eduvpn/storage.py b/eduvpn/storage.py
index 2c933c74..8235b862 100644
--- a/eduvpn/storage.py
+++ b/eduvpn/storage.py
@@ -2,10 +2,8 @@
This module contains code to maintain a simple metadata storage in ~/.config/eduvpn/
"""
-from os import PathLike
from typing import Optional
-from eduvpn.ovpn import Ovpn
from eduvpn.settings import CONFIG_DIR_MODE, CONFIG_PREFIX
from eduvpn.utils import get_logger
@@ -46,17 +44,6 @@ def set_setting(variant, what: str, value: str):
f.write(value)
-def write_ovpn(ovpn: Ovpn, private_key: str, certificate: str, target: PathLike):
- """
- Write the OVPN configuration file to target.
- """
- logger.info(f"Writing configuration to {target}")
- with open(target, mode="w+t") as f:
- ovpn.write(f)
- f.writelines(f"\n\n{private_key}\n\n")
- f.writelines(f"\n\n{certificate}\n\n")
-
-
def get_uuid(variant) -> Optional[str]:
"""
Read the UUID of the last generated eduVPN Network Manager connection.
diff --git a/eduvpn/ui/app.py b/eduvpn/ui/app.py
index 82ad2edd..6f160b7c 100644
--- a/eduvpn/ui/app.py
+++ b/eduvpn/ui/app.py
@@ -139,7 +139,7 @@ def expired_deactivate():
expired_deactivate()
@ui_transition(State.GOT_CONFIG, StateType.ENTER)
- def enter_NoActiveConnection(self, old_state, new_state):
+ def enter_NoActiveConnection(self, _old_state, _new_state):
if not self.window.is_visible():
# Quit the app if no window is open when the connection is deactivated.
logger.debug("connection deactivated while window closed")
diff --git a/eduvpn/ui/ui.py b/eduvpn/ui/ui.py
index 2103ed23..7d18ab26 100644
--- a/eduvpn/ui/ui.py
+++ b/eduvpn/ui/ui.py
@@ -98,7 +98,6 @@ def is_dark(rgb):
class ValidityTimers:
def __init__(self):
self.cancel_timers = []
- self.num = 0
def add_absolute(self, call: Callable, abstime: datetime):
delta = abstime - datetime.now()
@@ -182,7 +181,6 @@ def setup(self, builder: Builder, application: Type["EduVpnGtkApplication"]) ->
# Whether or not the profile that is selected is the 'same' one as before
# This is used so it doesn't fully trigger the callback
self.set_same_profile = False
- self.is_selected = False
self.app_logo = builder.get_object("appLogo")
self.app_logo_info = builder.get_object("appLogoInfo")
@@ -591,7 +589,7 @@ def hide_page(self, page: Box) -> None:
def get_profile_combo_sorted(self, server_info) -> Tuple[int, Gtk.ListStore]:
profile_store = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_PYOBJECT) # type: ignore
active_profile = 0
- sorted_profiles = sorted(server_info.profiles.profiles.items(), key=lambda v: str(v[1]))
+ sorted_profiles = sorted(server_info.profiles.profiles.items(), key=lambda v: v[1])
index = 0
for _id, profile in sorted_profiles:
if _id == server_info.profiles.current_id:
@@ -681,29 +679,6 @@ def update_connection_validity(self, validity: Validity) -> None:
self.eduvpn_app.enter_SessionExpiredState()
- # Shows notifications according to https://docs.eduvpn.org/server/v3/client-implementation-notes.html#expiry
- # The 0th case is handled with a separate notification inside of the expiry text handler
- def ensure_expiry_notification_text(self, validity: Validity) -> None:
- hours = [4, 2, 1]
- for h in hours:
- if h in self.shown_notification_times:
- continue
- delta = validity.remaining - timedelta(hours=h)
- total_secs = delta.total_seconds()
- if total_secs <= 0 and total_secs >= -120:
- self.eduvpn_app.enter_SessionPendingExpiryState(h)
- self.shown_notification_times.add(h)
- break
-
- # Show renew button or not
- def update_connection_renew(self, expire_time) -> None:
- if self.app.model.should_renew_button():
- # Show renew button
- self.renew_session_button.show()
-
- validity = self.app.model.get_expiry(expire_time)
- self.ensure_expiry_notification_text(validity)
-
def update_connection_status(self, connected: bool) -> None:
if connected:
self.connection_status_label.set_text(_("Connected"))
@@ -805,10 +780,6 @@ def exit_search(self):
search.show_search_components(self, False)
search.exit_server_search(self)
- def exit_ConfigureCustomServer(self, old_state, new_state):
- if not self.app.variant.use_predefined_servers:
- self.add_custom_server_button_container.hide()
-
def fill_secure_location_combo(self, curr, locs):
locs_store = Gtk.ListStore(GdkPixbuf.Pixbuf, GObject.TYPE_STRING, GObject.TYPE_STRING)
active_loc = 0
@@ -869,46 +840,46 @@ def enter_MainState(self, old_state: str, servers):
self.app.config.ignore_keyring_warning = self.keyring_do_not_show.get_active()
@ui_transition(State.MAIN, StateType.LEAVE)
- def exit_MainState(self, old_state, new_state):
+ def exit_MainState(self, _old_state, _data):
search.show_result_components(self, False)
self.add_other_server_button_container.hide()
search.exit_server_search(self)
self.change_location_combo.hide()
@ui_transition(State.OAUTH_STARTED, StateType.ENTER)
- def enter_oauth_setup(self, old_state, url):
+ def enter_oauth_setup(self, _old_state, data):
self.show_page(self.oauth_page)
self.oauth_cancel_button.show()
@ui_transition(State.OAUTH_STARTED, StateType.LEAVE)
- def exit_oauth_setup(self, old_state, data):
+ def exit_oauth_setup(self, _old_state, _data):
self.hide_page(self.oauth_page)
self.oauth_cancel_button.hide()
@ui_transition(State.ADDING_SERVER, StateType.ENTER)
- def enter_chosenServerInformation(self, new_state, data):
+ def enter_chosenServerInformation(self, _old_state, _data):
self.show_loading_page(
_("Adding server"),
_("Loading server information..."),
)
@ui_transition(State.ADDING_SERVER, StateType.LEAVE)
- def exit_chosenServerInformation(self, old_state, data):
+ def exit_chosenServerInformation(self, _old_state, _data):
self.hide_loading_page()
@ui_transition(State.GETTING_CONFIG, StateType.ENTER)
- def enter_GettingConfig(self, new_state, data):
+ def enter_GettingConfig(self, _old_state, _data):
self.show_loading_page(
_("Getting a VPN configuration"),
_("Loading server information..."),
)
@ui_transition(State.GETTING_CONFIG, StateType.LEAVE)
- def exit_GettingConfig(self, old_state, data):
+ def exit_GettingConfig(self, _old_state, _data):
self.hide_loading_page()
@ui_transition(State.ASK_PROFILE, StateType.ENTER)
- def enter_ChooseProfile(self, new_state, data):
+ def enter_ChooseProfile(self, _old_state, data):
self.show_back_button(True)
self.show_page(self.choose_profile_page)
self.profile_list.show()
@@ -928,20 +899,27 @@ def enter_ChooseProfile(self, new_state, data):
profile_tree_view.append_column(column)
sorted_model = Gtk.TreeModelSort(model=profiles_list_model)
- sorted_model.set_sort_column_id(0, Gtk.SortType.ASCENDING)
+
+ def custom_sort_func(model, iter1, iter2, user_data):
+ obj1 = model.get_value(iter1, 1)[2]
+ obj2 = model.get_value(iter2, 1)[2]
+ return -1 if obj1 < obj2 else (1 if obj2 < obj1 else 0)
+
+ sorted_model.set_sort_func(1, custom_sort_func, None)
+ sorted_model.set_sort_column_id(1, Gtk.SortType.ASCENDING)
profile_tree_view.set_model(sorted_model)
profiles_list_model.clear()
for profile_id, profile in profiles.profiles.items():
- profiles_list_model.append([str(profile), (setter, profile_id)])
+ profiles_list_model.append([str(profile), (setter, profile_id, profile)])
@ui_transition(State.ASK_PROFILE, StateType.LEAVE)
- def exit_ChooseProfile(self, old_state, data):
+ def exit_ChooseProfile(self, old_state, _data):
self.show_back_button(False)
self.hide_page(self.choose_profile_page)
self.profile_list.hide()
@ui_transition(State.ASK_LOCATION, StateType.ENTER)
- def enter_ChooseSecureInternetLocation(self, old_state, data):
+ def enter_ChooseSecureInternetLocation(self, _old_state, data):
self.show_back_button(True)
self.show_page(self.choose_location_page)
self.location_list.show()
@@ -979,7 +957,7 @@ def enter_ChooseSecureInternetLocation(self, old_state, data):
location_list_model.append([retrieve_country_name(location), flag, (setter, location)])
@ui_transition(State.ASK_LOCATION, StateType.LEAVE)
- def exit_ChooseSecureInternetLocation(self, old_state, new_state):
+ def exit_ChooseSecureInternetLocation(self, _old_state, _data):
self.show_back_button(False)
self.hide_loading_page()
self.hide_page(self.choose_location_page)
@@ -990,7 +968,7 @@ def enter_GotConfig(self, old_state: str, server_info) -> None:
self.enter_connecting(old_state, server_info)
@ui_transition(State.DISCONNECTED, StateType.ENTER)
- def enter_ConnectionStatus(self, old_state: str, server_info):
+ def enter_ConnectionStatus(self, _old_state: str, server_info):
self.show_back_button(True)
self.show_page(self.connection_page)
self.update_connection_status(False)
@@ -1005,12 +983,12 @@ def enter_ConnectionStatus(self, old_state: str, server_info):
self.renew_session_button.hide()
@ui_transition(State.DISCONNECTED, StateType.LEAVE)
- def exit_ConnectionStatus(self, old_state, new_state):
+ def exit_ConnectionStatus(self, _old_state, _data):
self.show_back_button(False)
self.hide_page(self.connection_page)
@ui_transition(State.CONNECTED, StateType.LEAVE)
- def leave_ConnectedState(self, old_state, server_info):
+ def leave_ConnectedState(self, _old_state, _server_info):
logger.debug("leave connected state")
self.reconnect_tcp_button.hide()
self.reconnect_tcp_text.hide()
@@ -1025,7 +1003,7 @@ def leave_ConnectedState(self, old_state, server_info):
self.stop_connection_info()
@ui_transition(State.CONNECTED, StateType.ENTER)
- def enter_ConnectedState(self, old_state, server_data):
+ def enter_ConnectedState(self, _old_state, server_data):
self.renew_session_button.hide()
server_info, validity = server_data
self.connection_info_expander.show()
@@ -1081,11 +1059,11 @@ def stop_validity_threads(self) -> None:
self.connection_validity_thread_cancel = None
self.connection_validity_timers.clean()
- def on_info_delete(self, widget, event):
+ def on_info_delete(self, widget, _):
logger.debug("info dialog delete event")
return widget.hide_on_delete()
- def on_info_button(self, widget: EventBox, event: EventButton) -> None:
+ def on_info_button(self, _box: EventBox, _button: EventButton) -> None:
logger.debug("clicked info button")
self.info_dialog.set_title(f"{self.app.variant.name} - Info")
self.info_dialog.show()
@@ -1093,7 +1071,7 @@ def on_info_button(self, widget: EventBox, event: EventButton) -> None:
self.info_dialog.hide()
@ui_transition(SERVER_LIST_REFRESH_STATE, StateType.ENTER) # type: ignore
- def enter_server_list_refresh(self, old_state, servers) -> None:
+ def enter_server_list_refresh(self, _old_state, servers) -> None:
logger.debug("server list refresh")
if self.is_searching_server:
return
@@ -1233,7 +1211,7 @@ def on_change_location(self, combo):
# Set profile and connect
self.call_model("change_secure_location", location)
- def on_search_changed(self, _: Optional[SearchEntry] = None) -> None:
+ def on_search_changed(self, _searchentry: Optional[SearchEntry] = None) -> None:
query = self.find_server_search_input.get_text()
if self.app.variant.use_predefined_servers and query.count(".") < 2:
results = self.app.model.search_predefined(query)
@@ -1356,13 +1334,13 @@ def on_toggle_connection_info(self, _):
def on_profile_row_activated(self, widget: TreeView, row: TreePath, _col: TreeViewColumn) -> None:
model = widget.get_model()
- setter, profile = model[row][1]
- logger.debug(f"activated profile: {profile!r}")
+ setter, profile_id, _ = model[row][1]
+ logger.debug(f"activated profile: {profile_id!r}")
@run_in_background_thread("set-profile")
def set_profile():
try:
- setter(profile)
+ setter(profile_id)
except Exception as e:
if should_show_error(e):
self.show_error_revealer(str(e))
@@ -1455,7 +1433,7 @@ def on_renew(success: bool):
def on_reconnect_tcp_clicked(self, event):
logger.debug("clicked on reconnect TCP")
- def on_reconnected(_: bool):
+ def on_reconnected(_success: bool):
logger.debug("done reconnecting with tcp")
self.reconnect_tcp_button.hide()
self.reconnect_tcp_text.hide()
diff --git a/setup.cfg b/setup.cfg
index 3031b8ef..49a2a9bb 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -31,7 +31,7 @@ packages =
eduvpn.ui
python_requires = >= 3.6
install_requires =
- eduvpn_common >= 2.1.0,< 3.0.0
+ eduvpn_common >= 2.99.0,< 3.0.0
pygobject
[options.package_data]
diff --git a/tests/test_nm.py b/tests/test_nm.py
index 7bacd2e4..a93251bb 100644
--- a/tests/test_nm.py
+++ b/tests/test_nm.py
@@ -1,7 +1,6 @@
from unittest import TestCase, skipIf
from eduvpn.nm import NMManager
-from eduvpn.ovpn import Ovpn
from eduvpn.variants import EDUVPN
from tests.mock_config import mock_config
@@ -9,20 +8,18 @@
@skipIf(not NMManager(EDUVPN).available, "Network manager not available")
class TestNm(TestCase):
def test_nm_available(self):
- nm_manager = NMManager(EDUVPN)
+ nm_manager = NMManager(EDUVPN, None)
nm_manager.available
def test_import_ovpn(self):
- nm_manager = NMManager(EDUVPN)
- ovpn = Ovpn.parse(mock_config)
- nm_manager.import_ovpn(ovpn)
+ nm_manager = NMManager(EDUVPN, None)
+ nm_manager.import_ovpn(mock_config)
def test_get_add_connection(self):
- nm_manager = NMManager(EDUVPN)
- ovpn = Ovpn.parse(mock_config)
- simple_connection = nm_manager.import_ovpn(ovpn)
+ nm_manager = NMManager(EDUVPN, None)
+ simple_connection = nm_manager.import_ovpn(mock_config)
nm_manager.add_connection(simple_connection)
def test_get_uuid(self):
- nm_manager = NMManager(EDUVPN)
+ nm_manager = NMManager(EDUVPN, None)
nm_manager.uuid
diff --git a/tests/test_stats.py b/tests/test_stats.py
index 049a8343..eefe7146 100644
--- a/tests/test_stats.py
+++ b/tests/test_stats.py
@@ -27,7 +27,7 @@ def try_open(path: Path):
@patch("eduvpn.nm.NMManager.iface", new_callable=PropertyMock, return_value=MOCK_IFACE)
class TestStats(TestCase):
def test_stat_bytes(self, _):
- nm_manager = NMManager(EDUVPN)
+ nm_manager = NMManager(EDUVPN, None)
with TemporaryDirectory() as tempdir:
# Create test data in the wanted files
# Use the tempdir so it is cleaned up later