summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--wrappers/python/eduvpn_common/discovery.py71
-rw-r--r--wrappers/python/eduvpn_common/error.py7
-rw-r--r--wrappers/python/eduvpn_common/event.py76
-rw-r--r--wrappers/python/eduvpn_common/loader.py14
-rw-r--r--wrappers/python/eduvpn_common/main.py261
-rw-r--r--wrappers/python/eduvpn_common/server.py123
-rw-r--r--wrappers/python/eduvpn_common/state.py6
-rw-r--r--wrappers/python/eduvpn_common/types.py133
8 files changed, 670 insertions, 21 deletions
diff --git a/wrappers/python/eduvpn_common/discovery.py b/wrappers/python/eduvpn_common/discovery.py
index be9d079..63cc7a0 100644
--- a/wrappers/python/eduvpn_common/discovery.py
+++ b/wrappers/python/eduvpn_common/discovery.py
@@ -11,6 +11,13 @@ from eduvpn_common.types import (
class DiscoOrganization:
+ """The class that represents an organization from discovery
+
+ :param: display_name: str: The display name of the organizations
+ :param: org_id: str: The organization ID
+ :param: secure_internet_home: str: Indicating which server is the secure internet home server
+ :param: keyword_list: The list of strings that the users gets to search on to find the server
+ """
def __init__(self, display_name: str, org_id: str, secure_internet_home: str, keyword_list: List[str]):
self.display_name = display_name
self.org_id = org_id
@@ -22,12 +29,29 @@ class DiscoOrganization:
class DiscoOrganizations:
+ """The class that represents the list of disco organizations from discovery.
+ Additionally it has provided a version which indicates which exact 'version' was used from discovery
+
+ :param: version: int: The version of the list as returned by Discovery
+ :param: organizations: List[DiscoOrganizations]: The actual list of discovery organizations
+ """
def __init__(self, version: int, organizations: List[DiscoOrganization]):
self.version = version
self.organizations = organizations
class DiscoServer:
+ """The class that represents a discovery server, this can be an institute access or secure internet server
+
+ :param: authentication_url_template: str: The OAuth template to use to skip WAYF
+ :param: base_url: str: The base URL of the server
+ :param: country_code: str: The country code of the server
+ :param: display_name: str: The display name of the server
+ :param: keyword_list: List[str]: The list of keywords that the user can use to find the server
+ :param: public_keys: List[str]: The list of public keys
+ :param: server_type: str: The server type as a string
+ :param: support_contacts: List[str]: The list of support contacts
+ """
def __init__(
self,
authentication_url_template: str,
@@ -53,12 +77,27 @@ class DiscoServer:
class DiscoServers:
- def __init__(self, version, servers):
+ """This class represents the list of discovery servers.
+ The version indicates which exact 'version' from Discovery was used.
+
+ :param: version: int: The version of the list as returned by Discovery
+ :param: servers: List[DiscoServers]: The list of discovery servers
+ """
+ def __init__(self, version: int, servers: List[DiscoServer]):
self.version = version
self.servers = servers
def get_disco_organization(ptr) -> Optional[DiscoOrganization]:
+ """Gets a discovery organization from the Go library in a C structure and returns a Python usable structure
+
+ :param ptr: The pointer returned by the go library that contains a discovery organization
+
+ :meta: private:
+
+ :return: The Discovery Organization if there is one
+ :rtype: Optional[DiscoOrganization]
+ """
if not ptr:
return None
@@ -71,6 +110,16 @@ def get_disco_organization(ptr) -> Optional[DiscoOrganization]:
def get_disco_server(lib: CDLL, ptr) -> Optional[DiscoServer]:
+ """Gets a discovery server from the Go library in a C structure and returns a Python usable structure
+
+ :param lib: CDLL: The Go shared library
+ :param ptr: The pointer to a discovery server returned by the Go library
+
+ :meta: private:
+
+ :return: The Discovery Server if there is one
+ :rtype: Optional[DiscoServer]
+ """
if not ptr:
return None
@@ -102,6 +151,16 @@ def get_disco_server(lib: CDLL, ptr) -> Optional[DiscoServer]:
def get_disco_servers(lib: CDLL, ptr: c_void_p) -> Optional[DiscoServers]:
+ """Gets servers from the Go library in a C structure and returns a Python usable structure
+
+ :param lib: CDLL: The Go shared library
+ :param ptr: c_void_p: The pointer returned by the Go library for the discovery servers
+
+ :meta: private:
+
+ :return: The Discovery Servers if there are any
+ :rtype: Optional[DiscoServers]
+ """
if ptr:
svrs = cast(ptr, POINTER(cDiscoveryServers)).contents
@@ -120,6 +179,16 @@ def get_disco_servers(lib: CDLL, ptr: c_void_p) -> Optional[DiscoServers]:
def get_disco_organizations(lib: CDLL, ptr: c_void_p) -> Optional[DiscoOrganizations]:
+ """Gets organizations from the Go library in a C structure and returns a Python usable structure
+
+ :param lib: CDLL: The Go shared library
+ :param ptr: c_void_p: The pointer returned by the Go library for the discovery organizations
+
+ :meta: private:
+
+ :return: The Discovery Organizations if there are any
+ :rtype: Optional[DiscoOrganizations]
+ """
if ptr:
orgs = cast(ptr, POINTER(cDiscoveryOrganizations)).contents
organizations = []
diff --git a/wrappers/python/eduvpn_common/error.py b/wrappers/python/eduvpn_common/error.py
index a5b59b4..626d9a2 100644
--- a/wrappers/python/eduvpn_common/error.py
+++ b/wrappers/python/eduvpn_common/error.py
@@ -2,6 +2,7 @@ from enum import Enum
class ErrorLevel(Enum):
+ """The error level enum"""
ERR_OTHER = 0
ERR_INFO = 1
ERR_WARNING = 2
@@ -9,6 +10,12 @@ class ErrorLevel(Enum):
class WrappedError(Exception):
+ """An exception returned by the Go library
+
+ :param: traceback: str: The traceback of the error including newlines
+ :param: cause: str: The cause of the error as a message
+ :param: level: ErrorLevel: The level of the error
+ """
def __init__(self, traceback: str, cause: str, level: ErrorLevel):
super(WrappedError, self).__init__(cause)
self.traceback = traceback
diff --git a/wrappers/python/eduvpn_common/event.py b/wrappers/python/eduvpn_common/event.py
index e2bfabb..193e659 100644
--- a/wrappers/python/eduvpn_common/event.py
+++ b/wrappers/python/eduvpn_common/event.py
@@ -10,12 +10,25 @@ from eduvpn_common.server import (
from eduvpn_common.state import State, StateType
from eduvpn_common.types import get_ptr_string
+# The attribute that callback functions get
EDUVPN_CALLBACK_PROPERTY = "_eduvpn_property_callback"
# A state transition decorator for classes
# To use this, make sure to register the class with `register_class_callbacks`
def class_state_transition(state: int, state_type: StateType) -> Callable:
+ """A decorator to be internally by classes to register the event
+
+ :param state: int: The state of the transition
+ :param state_type: StateType: The type of transition
+
+ :meta: private:
+ """
def wrapper(func):
+ """
+
+ :param func: The function to set the internal attribute for
+
+ """
setattr(func, EDUVPN_CALLBACK_PROPERTY, (state, state_type))
return func
@@ -23,6 +36,14 @@ def class_state_transition(state: int, state_type: StateType) -> Callable:
def convert_data(lib: CDLL, state: int, data: Any) -> None:
+ """The function that converts the C structure from the Go library to a Python structure
+
+ :param lib: CDLL: The Go shared library
+ :param state: int: The state to convert the data for
+ :param data: Any: The data itself that has to be converted
+
+ :meta: private:
+ """
if not data:
return None
if state is State.NO_SERVER:
@@ -43,11 +64,19 @@ def convert_data(lib: CDLL, state: int, data: Any) -> None:
class EventHandler(object):
+ """The class that neatly handles event callbacks from the internal Go FSM"""
def __init__(self, lib: CDLL):
self.handlers: Dict[Tuple[int, StateType], List[Callable]] = {}
self.lib = lib
def change_class_callbacks(self, cls: Any, add: bool = True) -> None:
+ """The function that is used to change class callbacks
+
+ :param cls: Any: The class to change the callbacks for
+ :param add: bool: (Default value = True): Whether or not to add or remove the event. If true the event gets added
+
+ :meta: private:
+ """
# Loop over method names
for method_name in dir(cls):
try:
@@ -68,6 +97,14 @@ class EventHandler(object):
self.remove_event(state, state_type, method)
def remove_event(self, state: int, state_type: StateType, func: Callable) -> None:
+ """Removes an event
+
+ :param state: int: The state to remove the event for
+ :param state_type: StateType: The state type to remove the event for
+ :param func: Callable: The function that needs to be removed from the event
+
+ :meta: private:
+ """
for key, values in self.handlers.copy().items():
if key == (state, state_type):
values.remove(func)
@@ -77,13 +114,32 @@ class EventHandler(object):
self.handlers[key] = values
def add_event(self, state: int, state_type: StateType, func: Callable) -> None:
+ """Adds an event
+
+ :param state: int: The state to add the event for
+ :param state_type: StateType: The state type to add the event for
+ :param func: Callable: The function that needs to be added to the event
+
+ :meta: private:
+ """
if (state, state_type) not in self.handlers:
self.handlers[(state, state_type)] = []
self.handlers[(state, state_type)].append(func)
- # A decorator for standalone functions
def on(self, state: int, state_type: StateType) -> Callable:
+ """The decorator for standalone functions
+
+ :param state: int: The state of the event
+ :param state_type: StateType: The state type of the event
+
+ :meta: private:
+ """
def wrapped_f(func):
+ """
+
+ :param func: The function to add the event for
+
+ """
self.add_event(state, state_type, func)
return func
@@ -92,6 +148,15 @@ class EventHandler(object):
def run_state(
self, state: int, other_state: int, state_type: StateType, data: str
) -> None:
+ """The function that runs the callback for a specific event
+
+ :param state: int: The state of the event
+ :param other_state: int: The other state of the event
+ :param state_type: StateType: The state type of the event
+ :param data: str: The data that gets passed to the function callback when the event is ran
+
+ :meta: private:
+ """
if (state, state_type) not in self.handlers:
return
for func in self.handlers[(state, state_type)]:
@@ -100,6 +165,15 @@ class EventHandler(object):
def run(
self, old_state: int, new_state: int, data: Any, convert: bool = True
) -> None:
+ """Run a specific event.
+ It converts the data and then runs the event for all state types
+
+ :param old_state: int: The previous state for running the event
+ :param new_state: int: The new state for running the event
+ :param data: Any: The data that gets passed to the event
+ :param convert: bool: (Default value = True): Whether or not to convert the data further
+
+ """
# First run leave transitions, then enter
# The state is done when the wait event finishes
converted = data
diff --git a/wrappers/python/eduvpn_common/loader.py b/wrappers/python/eduvpn_common/loader.py
index 170437a..f299f94 100644
--- a/wrappers/python/eduvpn_common/loader.py
+++ b/wrappers/python/eduvpn_common/loader.py
@@ -18,7 +18,12 @@ from eduvpn_common.types import (
)
-def load_lib():
+def load_lib() -> CDLL:
+ """The function that loads the Go shared library
+
+ :return: The Go shared library loaded with cdll.LoadLibrary from ctypes
+ :rtype: CDLL
+ """
lib_prefixes = defaultdict(
lambda: "lib",
{
@@ -51,7 +56,12 @@ def load_lib():
return lib
-def initialize_functions(lib: CDLL):
+def initialize_functions(lib: CDLL) -> None:
+ """Initializes the Go shared library functions
+
+ :param lib: CDLL: The Go shared library
+
+ """
# Exposed functions
# We have to use c_void_p instead of c_char_p to free it properly
# See https://stackoverflow.com/questions/13445568/python-ctypes-how-to-free-memory-getting-invalid-pointer-error
diff --git a/wrappers/python/eduvpn_common/main.py b/wrappers/python/eduvpn_common/main.py
index 49618d8..03e5b4c 100644
--- a/wrappers/python/eduvpn_common/main.py
+++ b/wrappers/python/eduvpn_common/main.py
@@ -1,16 +1,24 @@
import threading
from ctypes import c_char_p, c_int, c_void_p
-from typing import Any, Callable, Dict, List, Optional, Tuple
+from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple
from eduvpn_common.discovery import DiscoOrganizations, DiscoServers, get_disco_organizations, get_disco_servers
from eduvpn_common.event import EventHandler
from eduvpn_common.loader import initialize_functions, load_lib
-from eduvpn_common.server import Server, get_servers
+from eduvpn_common.server import Profiles, Server, get_servers
from eduvpn_common.state import State, StateType
from eduvpn_common.types import VPNStateChange, decode_res, encode_args, get_data_error
class EduVPN(object):
+ """The main class used to communicate with the Go library.
+ It registers the client with the library and then calls the needed appropriate functions
+
+ :param name: str: The name of the client. For commonly used names, see https://git.sr.ht/~fkooman/vpn-user-portal/tree/v3/item/src/OAuth/ClientDb.php. E.g. org.eduvpn.app.linux, if this name has "letsconnect" in it, then it is a Let's Connect! variant
+ :param config_directory: str: The directory (absolute/relative) where to store the files
+ :param language: str: The language of the client, e.g. en
+
+ """
def __init__(self, name: str, config_directory: str, language: str):
self.name = name
self.config_directory = config_directory
@@ -30,16 +38,37 @@ class EduVPN(object):
self.location_event: Optional[threading.Event] = None
@self.event.on(State.ASK_PROFILE, StateType.WAIT)
- def wait_profile_event(old_state: int, profiles: str):
+ def wait_profile_event(old_state: int, profiles: Profiles):
+ """This functions waits until the ask location thread event is finished
+
+ :param old_state: int: The old state of the profiles event
+ :param profiles: Profiles: The profiles
+
+ """
if self.profile_event:
self.profile_event.wait()
@self.event.on(State.ASK_LOCATION, StateType.WAIT)
- def wait_location_event(old_state: int, locations: str):
+ def wait_location_event(old_state: int, locations: List[str]):
+ """This functions waits until the location thread event is finished
+
+ :param old_state: int: The old state of the location event
+ :param locations: List[str]: The locations
+
+ """
if self.location_event:
self.location_event.wait()
- def go_function(self, func: Any, *args, decode_func: Optional[Callable] = None) -> Any:
+ def go_function(self, func: Any, *args: Iterator, decode_func: Optional[Callable] = None) -> Any:
+ """Call an internal go function and properly forward the arguments.
+ Also handles decoding the result
+
+ :param func: Any: The Go function to call from the shared library
+ :param \*args: Iterator: The arguments to call the function with
+ :param decode_func: Optional[Callable]: (Default value = None): The function to decode the result into a Python type
+
+ :meta private:
+ """
# The functions all have at least one arg type which is the name of the client
args_gen = encode_args(list(args), func.argtypes[1:])
res = func(self.name.encode("utf-8"), *(args_gen))
@@ -49,16 +78,26 @@ class EduVPN(object):
return decode_func(self.lib, res)
def cancel_oauth(self) -> None:
+ """Cancel the OAuth process"""
cancel_oauth_err = self.go_function(self.lib.CancelOAuth)
if cancel_oauth_err:
raise cancel_oauth_err
def deregister(self) -> None:
+ """Deregister the Go shared library.
+ This removes the object from internal bookkeeping and saves the configuration
+ """
self.go_function(self.lib.Deregister)
remove_as_global_object(self)
def register(self, debug: bool = False) -> None:
+ """Register the Go shared library.
+ This makes sure the FSM is initialized and that we can call Go functions
+
+ :param debug: bool: (Default value = False): Whether or not we want to enable debug logging
+
+ """
if not add_as_global_object(self):
raise Exception("Already registered")
@@ -74,6 +113,13 @@ class EduVPN(object):
raise register_err
def get_disco_servers(self) -> Optional[DiscoServers]:
+ """Get the discovery servers
+
+ :raises WrappedError: An error by the Go library
+
+ :return: The disco Servers if any
+ :rtype: Optional[DiscoServers]
+ """
servers, servers_err = self.go_function(
self.lib.GetDiscoServers,
decode_func=lambda lib, x: get_data_error(lib, x, get_disco_servers),
@@ -85,6 +131,13 @@ class EduVPN(object):
return servers
def get_disco_organizations(self) -> Optional[DiscoOrganizations]:
+ """Get the discovery organizations
+
+ :raises WrappedError: An error by the Go library
+
+ :return: The discovery Organizations if any
+ :rtype: Optional[DiscoOrganizations]
+ """
organizations, organizations_err = self.go_function(
self.lib.GetDiscoOrganizations,
decode_func=lambda lib, x: get_data_error(lib, x, get_disco_organizations),
@@ -95,19 +148,25 @@ class EduVPN(object):
return organizations
- def remove_secure_internet(self) -> None:
- remove_err = self.go_function(self.lib.RemoveSecureInternet)
+ def add_institute_access(self, url: str) -> None:
+ """Add an institute access server
- if remove_err:
- raise remove_err
+ :param url: str: The URL for the institute access server. Use the exact base_url as returned by Discovery
- def add_institute_access(self, url: str) -> None:
+ :raises WrappedError: An error by the Go library
+ """
add_err = self.go_function(self.lib.AddInstituteAccess, url)
if add_err:
raise add_err
def add_secure_internet_home(self, org_id: str) -> None:
+ """Add a secure internet server
+
+ :param org_id: str: The organization ID of the secure internet server. Use the exact organization as returned by Discovery
+
+ :raises WrappedError: An error by the Go library
+ """
self.location_event = threading.Event()
add_err = self.go_function(self.lib.AddSecureInternetHomeServer, org_id)
@@ -115,30 +174,71 @@ class EduVPN(object):
raise add_err
def add_custom_server(self, url: str) -> None:
+ """Add a custom server
+
+ :param url: str: The base URL of the server
+
+ :raises WrappedError: An error by the Go library
+ """
add_err = self.go_function(self.lib.AddCustomServer, url)
if add_err:
raise add_err
+ def remove_secure_internet(self) -> None:
+ """Remove the secure internet server
+
+ :raises WrappedError: An error by the Go library
+ """
+ remove_err = self.go_function(self.lib.RemoveSecureInternet)
+
+ if remove_err:
+ raise remove_err
+
def remove_institute_access(self, url: str) -> None:
+ """Remove an institute access server
+
+ :param url: str: The URL for the institute access server. Use the exact base_url as returned by Discovery
+
+ :raises WrappedError: An error by the Go library
+ """
remove_err = self.go_function(self.lib.RemoveInstituteAccess, url)
if remove_err:
raise remove_err
def remove_custom_server(self, url: str) -> None:
+ """Remove a custom server
+
+ :param url: str: The base URL of the server
+
+ :raises WrappedError: An error by the Go library
+ """
remove_err = self.go_function(self.lib.RemoveCustomServer, url)
if remove_err:
raise remove_err
- def get_config(self, url: str, func: Any, prefer_tcp: bool = False) -> Tuple[str, str]:
+ def get_config(self, identifier: str, func: Any, prefer_tcp: bool = False) -> Tuple[str, str]:
+ """Get an OpenVPN/WireGuard configuration from the server
+
+ :param identifier: str: The identifier of the server, e.g. URL or ORG ID
+ :param func: Any: The Go function to call
+ :param prefer_tcp: bool: (Default value = False): Whether or not to prefer TCP
+
+ :meta: private:
+
+ :raises WrappedError: An error by the Go library
+
+ :return: The configuration and configuration type ('openvpn' or 'wireguard')
+ :rtype: Tuple[str, str]
+ """
# Because it could be the case that a profile callback is started, store a threading event
# In the constructor, we have defined a wait event for Ask_Profile, this waits for this event to be set
# The event is set in self.set_profile
self.profile_event = threading.Event()
- config, config_type, config_err = self.go_function(func, url, prefer_tcp)
+ config, config_type, config_err = self.go_function(func, identifier, prefer_tcp)
self.profile_event = None
self.location_event = None
@@ -151,66 +251,144 @@ class EduVPN(object):
def get_config_custom_server(
self, url: str, prefer_tcp: bool = False
) -> Tuple[str, str]:
+ """Get an OpenVPN/WireGuard configuration from a custom server
+
+ :param url: str: The URL of the custom server
+ :param prefer_tcp: bool: (Default value = False): Whether or not to prefer TCP
+
+ :raises WrappedError: An error by the Go library
+
+ :return: The configuration and configuration type ('openvpn' or 'wireguard')
+ :rtype: Tuple[str, str]
+ """
return self.get_config(url, self.lib.GetConfigCustomServer, prefer_tcp)
def get_config_institute_access(
self, url: str, prefer_tcp: bool = False
) -> Tuple[str, str]:
+ """Get an OpenVPN/WireGuard configuration from an institute access server
+
+ :param url: str: The URL of the institute access server. Use the one from Discovery
+ :param prefer_tcp: bool: (Default value = False): Whether or not to prefer TCP
+
+ :raises WrappedError: An error by the Go library
+
+ :return: The configuration and configuration type ('openvpn' or 'wireguard')
+ :rtype: Tuple[str, str]
+ """
return self.get_config(url, self.lib.GetConfigInstituteAccess, prefer_tcp)
def get_config_secure_internet(
- self, url: str, prefer_tcp: bool = False
+ self, org_id: str, prefer_tcp: bool = False
) -> Tuple[str, str]:
- return self.get_config(url, self.lib.GetConfigSecureInternet, prefer_tcp)
+ """Get an OpenVPN/WireGuard configuration from a secure internet server
+
+ :param org_id: str: The organization ID of the secure internet server. Use the one from Discovery
+ :param prefer_tcp: bool: (Default value = False): Whether or not to prefer TCP
+
+ :raises WrappedError: An error by the Go library
+ """
+ return self.get_config(org_id, self.lib.GetConfigSecureInternet, prefer_tcp)
def go_back(self) -> None:
+ """Go back in the FSM"""
# Ignore the error
self.go_function(self.lib.GoBack)
def set_connected(self) -> None:
+ """Set the FSM to connected
+
+ :raises WrappedError: An error by the Go library
+ """
connect_err = self.go_function(self.lib.SetConnected)
if connect_err:
raise connect_err
def set_disconnecting(self) -> None:
+ """Set the FSM to disconnecting
+
+ :raises WrappedError: An error by the Go library
+ """
disconnecting_err = self.go_function(self.lib.SetDisconnecting)
if disconnecting_err:
raise disconnecting_err
def set_connecting(self) -> None:
+ """Set the FSM to connecting
+
+ :raises WrappedError: An error by the Go library
+ """
connecting_err = self.go_function(self.lib.SetConnecting)
if connecting_err:
raise connecting_err
def set_disconnected(self, cleanup: bool = True) -> None:
+ """Set the FSM to disconnected
+
+ :param cleanup: bool: (Default value = True): Whether or not to call /disconnect to the server. This invalidates the OpenVPN/WireGuard configuration
+
+ :raises WrappedError: An error by the Go library
+ """
disconnect_err = self.go_function(self.lib.SetDisconnected, cleanup)
if disconnect_err:
raise disconnect_err
def set_search_server(self) -> None:
+ """Set the FSM to search server
+
+ :raises WrappedError: An error by the Go library
+ """
search_err = self.go_function(self.lib.SetSearchServer)
if search_err:
raise search_err
def remove_class_callbacks(self, cls: Any) -> None:
+ """Remove class callbacks
+
+ :param cls: Any: The class to remove callbacks for
+
+ """
self.event_handler.change_class_callbacks(cls, add=False)
def register_class_callbacks(self, cls: Any) -> None:
+ """Register class callbacks
+
+ :param cls: Any: The class to register callbacks for
+
+ """
self.event_handler.change_class_callbacks(cls)
@property
def event(self) -> EventHandler:
+ """The property that gets the event handler
+
+ :return: The event handler
+ :rtype: EventHandler
+ """
return self.event_handler
def callback(self, old_state: State, new_state: State, data: Any) -> None:
+ """Run an event callback
+
+ :param old_state: State: The previous state
+ :param new_state: State: The new state
+ :param data: Any: The data to pass to the event
+
+ """
self.event.run(old_state, new_state, data)
def set_profile(self, profile_id: str) -> None:
+ """Set the profile of the current server
+
+ :param profile_id: str: The profile id of the chosen profile for the server
+
+ :raises WrappedError: An error by the Go library
+ """
# Set the profile id
profile_err = self.go_function(self.lib.SetProfileID, profile_id)
@@ -223,6 +401,10 @@ class EduVPN(object):
raise profile_err
def change_secure_location(self) -> None:
+ """Change the secure location. This calls the necessary events
+
+ :raises WrappedError: An error by the Go library
+ """
# Set the location by country code
self.location_event = threading.Event()
location_err = self.go_function(self.lib.ChangeSecureLocation)
@@ -231,6 +413,12 @@ class EduVPN(object):
raise location_err
def set_secure_location(self, country_code: str) -> None:
+ """Set the secure location
+
+ :param country_code: str: The country code of the new location
+
+ :raises WrappedError: An error by the Go library
+ """
# Set the location by country code
location_err = self.go_function(self.lib.SetSecureLocation, country_code)
@@ -243,18 +431,39 @@ class EduVPN(object):
raise location_err
def renew_session(self) -> None:
+ """Renew the session. This invalidates the tokens and runs the necessary callbacks to log back in
+
+ :raises WrappedError: An error by the Go library
+ """
renew_err = self.go_function(self.lib.RenewSession)
if renew_err:
raise renew_err
def should_renew_button(self) -> bool:
+ """Whether or not the UI should show the renew button
+
+ :return: Whether or not the return button should be shown
+ :rtype: bool
+ """
return self.go_function(self.lib.ShouldRenewButton)
def in_fsm_state(self, state_id: State) -> bool:
+ """Check whether or not the FSM is in the provided state
+
+ :param state_id: State: The state to check for
+
+ :return: Whether or not the FSM is in the provided state
+ :rtype: bool
+ """
return self.go_function(self.lib.InFSMState, state_id)
def get_saved_servers(self) -> Optional[List[Server]]:
+ """Get a list of saved servers
+
+ :return: The list of Servers if there are any
+ :rtype: Optional[List[Servers]]
+ """
servers, servers_err = self.go_function(
self.lib.GetSavedServers,
decode_func=lambda lib, x: get_data_error(lib, x, get_servers),
@@ -271,6 +480,15 @@ eduvpn_objects: Dict[str, EduVPN] = {}
@VPNStateChange
def state_callback(name: bytes, old_state: int, new_state: int, data: Any) -> None:
+ """The internal callback that is passed to the Go library
+
+ :param name: bytes: The name of the client
+ :param old_state: int: The old state
+ :param new_state: int: The new state
+ :param data: Any: The data that still needs to be converted
+
+ :meta: private:
+ """
name_decoded = name.decode()
if name_decoded not in eduvpn_objects:
return
@@ -278,6 +496,15 @@ def state_callback(name: bytes, old_state: int, new_state: int, data: Any) -> No
def add_as_global_object(eduvpn: EduVPN) -> bool:
+ """Add the provided parameter to the global objects lists so we can call the callback
+
+ :param eduvpn: EduVPN: The class to add
+
+ :meta: private:
+
+ :return: Whether or not the object was added
+ :rtype: bool
+ """
global eduvpn_objects
if eduvpn.name not in eduvpn_objects:
eduvpn_objects[eduvpn.name] = eduvpn
@@ -286,5 +513,11 @@ def add_as_global_object(eduvpn: EduVPN) -> bool:
def remove_as_global_object(eduvpn: EduVPN) -> None:
+ """Remove the provided parameter from the global objects list
+
+ :param eduvpn: EduVPN: The class to remove
+
+ :meta: private:
+ """
global eduvpn_objects
eduvpn_objects.pop(eduvpn.name, None)
diff --git a/wrappers/python/eduvpn_common/server.py b/wrappers/python/eduvpn_common/server.py
index 6df23a6..686114b 100644
--- a/wrappers/python/eduvpn_common/server.py
+++ b/wrappers/python/eduvpn_common/server.py
@@ -6,6 +6,12 @@ from eduvpn_common.types import cServer, cServerLocations, cServerProfiles, cSer
class Profile:
+ """The class that represents a server 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
+ """
def __init__(self, identifier: str, display_name: str, default_gateway: bool):
self.identifier = identifier
self.display_name = display_name
@@ -16,18 +22,35 @@ class Profile:
class Profiles:
+ """The class that represents a list of profiles
+
+ :param: profiles: List[Profile]: A list of profiles
+ :param: current: int: The current profile index
+ """
def __init__(self, profiles: List[Profile], current: int):
self.profiles = profiles
self.current_index = current
@property
def current(self) -> Optional[Profile]:
+ """Get the current profile if there is any
+
+ :return: The profile if there is a current one (meaning the index is valid)
+ :rtype: Optional[Profile]
+ """
if self.current_index < len(self.profiles):
return self.profiles[self.current_index]
return None
class Server:
+ """The class that represents a server. Use this for a custom server
+
+ :param: url: str: The base URL of the server
+ :param: display_name: str: The display name of the server
+ :param: profiles: Optional[Profiles]: The profiles if there are any already obtained, defaults to None
+ :param: expire_time: int: The expiry time in a Unix timestamp, defaults to 0
+ """
def __init__(
self,
url: str,
@@ -45,10 +68,23 @@ class Server:
@property
def category(self) -> str:
+ """Return the category of the server as a string
+
+ :return: The category string, "Custom Server"
+ :rtype: str
+ """
return "Custom Server"
class InstituteServer(Server):
+ """The class that represents an Institute Access Server
+
+ :param: url: str: The base URL of the Institute Access Server
+ :param: display_name: str: The display name of the Institute Access Server
+ :param: support_contact: List[str]: The list of support contacts
+ :param: profiles: Profiles: The profiles of the server
+ :param: expire_time: int: The expiry time in a Unix timestamp
+ """
def __init__(
self,
url: str,
@@ -62,10 +98,24 @@ class InstituteServer(Server):
@property
def category(self) -> str:
+ """Return the category of the institute server as a string
+
+ :return: The category string, "Institute Access Server"
+ :rtype: str
+ """
return "Institute Access Server"
class SecureInternetServer(Server):
+ """The class that represents a Secure Internet Server
+
+ :param: org_id: str: The organization ID of the Secure Internet Server as returned by Discovery
+ :param: display_name: str: The display name of the server
+ :param: support_contact: List[str]: The list of support contacts of the server
+ :param: profiles: Profiles: The list of profiles that the server has
+ :param: expire_time: int: The expiry time in a Unix timestamp
+ :param: country_code: str: The country code of the server
+ """
def __init__(
self,
org_id: str,
@@ -82,10 +132,22 @@ class SecureInternetServer(Server):
@property
def category(self) -> str:
+ """Return the category of the secure internet server as a string
+
+ :return: The category string, "Secure Internet Server"
+ :rtype: str
+ """
return "Secure Internet Server"
def get_type_for_str(type_str: str) -> Type[Server]:
+ """Get the right class type for a certain string input
+
+ :param type_str: str: The string that represents the type of server, one of secure_internet, institute_access, custom_server
+
+ :return: The server, defaults to Institute Server if an invalid input is given
+ :rtype: Type[Server]
+ """
if type_str == "secure_internet":
return SecureInternetServer
if type_str == "custom_server":
@@ -94,6 +156,15 @@ def get_type_for_str(type_str: str) -> Type[Server]:
def get_profiles(ptr) -> Optional[Profiles]:
+ """Get the profiles from the Go shared library and convert it to a Python usable structure
+
+ :param ptr: The pointer to the Profiles as returned by the Go library
+
+ :meta: private:
+
+ :return: Profiles if there are any
+ :rtype: Optional[Profiles]
+ """
if not ptr:
return None
profiles = []
@@ -115,7 +186,17 @@ def get_profiles(ptr) -> Optional[Profiles]:
return Profiles(profiles, current_profile)
-def get_server(ptr, _type=None) -> Optional[Server]:
+def get_server(ptr, _type: Optional[str] = None) -> Optional[Server]:
+ """Get the server from the Go shared library and convert it to a Python usable structure
+
+ :param ptr: The pointer as returned by the Go library
+ :param _type: (Default value = None): The optional parameter that represents whether or not the type is enforced to the input. If None it is automatically determined
+
+ :meta: private:
+
+ :return: Server if there is any
+ :rtype: Optional[Server]
+ """
if not ptr:
return None
@@ -154,18 +235,48 @@ def get_server(ptr, _type=None) -> Optional[Server]:
def get_transition_server(lib: CDLL, ptr: c_void_p) -> Optional[Server]:
+ """Get a server from a transition event
+
+ :param lib: CDLL: The Go shared library
+ :param ptr: c_void_p: The Go's returned C pointer that represents the Server
+
+ :meta: private:
+
+ :return: The server if there is any
+ :rtype: Optional[Server]
+ """
server = get_server(cast(ptr, POINTER(cServer)))
lib.FreeServer(ptr)
return server
def get_transition_profiles(lib: CDLL, ptr: c_void_p) -> Optional[Profiles]:
+ """Get profiles from a transition event
+
+ :param lib: CDLL: The Go shared library
+ :param ptr: c_void_p: The Go's returned C pointer that represents the profiles
+
+ :meta: private:
+
+ :return: The profiles if there is any
+ :rtype: Optional[Profiles]
+ """
profiles = get_profiles(cast(ptr, POINTER(cServerProfiles)))
lib.FreeProfiles(ptr)
return profiles
def get_servers(lib: CDLL, ptr: c_void_p) -> Optional[List[Server]]:
+ """Get servers from the Go library as a C structure and return a Python usable structure
+
+ :param lib: CDLL: The Go shared library
+ :param ptr: c_void_p: The C pointer to the servers structure
+
+ :meta: private:
+
+ :return: The list of Servers if there is any
+ :rtype: Optional[List[Server]]
+ """
if ptr:
returned = []
servers = cast(ptr, POINTER(cServers)).contents
@@ -193,6 +304,16 @@ def get_servers(lib: CDLL, ptr: c_void_p) -> Optional[List[Server]]:
def get_locations(lib: CDLL, ptr: c_void_p) -> Optional[List[str]]:
+ """Get locations from the Go library as a C structure and return a Python usable structure
+
+ :param lib: CDLL: The Go shared library
+ :param ptr: c_void_p: The C pointer to the locations structure
+
+ :meta: private:
+
+ :return: The list of servers if there are any
+ :rtype: Optional[List[str]]
+ """
if ptr:
locations = cast(ptr, POINTER(cServerLocations)).contents
location_list = []
diff --git a/wrappers/python/eduvpn_common/state.py b/wrappers/python/eduvpn_common/state.py
index dfa04c9..227d3c2 100644
--- a/wrappers/python/eduvpn_common/state.py
+++ b/wrappers/python/eduvpn_common/state.py
@@ -2,12 +2,18 @@ from enum import IntEnum
class StateType(IntEnum):
+ """
+ The State Type enum. Wait types are mostly used for internal code
+ """
ENTER = 1
LEAVE = 2
WAIT = 3
class State(IntEnum):
+ """
+ The State enum. Each state here also exists in the Go library
+ """
DEREGISTERED = 0
NO_SERVER = 1
ASK_LOCATION = 2
diff --git a/wrappers/python/eduvpn_common/types.py b/wrappers/python/eduvpn_common/types.py
index 9f5fba0..a58adff 100644
--- a/wrappers/python/eduvpn_common/types.py
+++ b/wrappers/python/eduvpn_common/types.py
@@ -17,6 +17,10 @@ from eduvpn_common.error import ErrorLevel, WrappedError
class cError(Structure):
+ """The C type that represents the Error as returned by the Go library
+
+ :meta: private:
+ """
_fields_ = [
("level", c_int),
("traceback", c_char_p),
@@ -25,10 +29,15 @@ class cError(Structure):
class cServerLocations(Structure):
+ """The C type that represents the Server Locations as returned by the Go library
+
+ :meta: private:
+ """
_fields_ = [("locations", POINTER(c_char_p)), ("total_locations", c_size_t)]
class cDiscoveryOrganization(Structure):
+ """The C type that represents a Discovery Organization as returned by the Go library"""
_fields_ = [
("display_name", c_char_p),
("org_id", c_char_p),
@@ -38,6 +47,10 @@ class cDiscoveryOrganization(Structure):
class cDiscoveryOrganizations(Structure):
+ """The C type that represents Discovery Organizations as returned by the Go library
+
+ :meta: private:
+ """
_fields_ = [
("version", c_ulonglong),
("organizations", POINTER(POINTER(cDiscoveryOrganization))),
@@ -46,6 +59,10 @@ class cDiscoveryOrganizations(Structure):
class cDiscoveryServer(Structure):
+ """The C type that represents a Discovery Server as returned by the Go library
+
+ :meta: private:
+ """
_fields_ = [
("authentication_url_template", c_char_p),
("base_url", c_char_p),
@@ -61,6 +78,10 @@ class cDiscoveryServer(Structure):
class cDiscoveryServers(Structure):
+ """The C type that represents Discovery Servers as returned by the Go library
+
+ :meta: private:
+ """
_fields_ = [
("version", c_ulonglong),
("servers", POINTER(POINTER(cDiscoveryServer))),
@@ -69,6 +90,10 @@ class cDiscoveryServers(Structure):
class cServerProfile(Structure):
+ """The C type that represents a Server Profile as returned by the Go library
+
+ :meta: private:
+ """
_fields_ = [
("identifier", c_char_p),
("display_name", c_char_p),
@@ -77,6 +102,10 @@ class cServerProfile(Structure):
class cServerProfiles(Structure):
+ """The C type that represents Server Profiles as returned by the Go library
+
+ :meta: private:
+ """
_fields_ = [
("current", c_int),
("profiles", POINTER(POINTER(cServerProfile))),
@@ -85,6 +114,10 @@ class cServerProfiles(Structure):
class cServer(Structure):
+ """The C type that represents a Server as returned by the Go library
+
+ :meta: private:
+ """
_fields_ = [
("identifier", c_char_p),
("display_name", c_char_p),
@@ -98,6 +131,10 @@ class cServer(Structure):
class cServers(Structure):
+ """The C type that represents Servers as returned by the Go library
+
+ :meta: private:
+ """
_fields_ = [
("custom_servers", POINTER(POINTER(cServer))),
("total_custom", c_size_t),
@@ -108,17 +145,36 @@ class cServers(Structure):
class DataError(Structure):
+ """The C type that represents a tuple of data and error as returned by the Go library
+
+ :meta: private:
+ """
_fields_ = [("data", c_void_p), ("error", c_void_p)]
class ConfigError(Structure):
+ """The C type that represents the data that gets by the Go library returned when a config is obtained
+
+ :meta: private:
+ """
_fields_ = [("config", c_void_p), ("config_type", c_void_p), ("error", c_void_p)]
+# The type for a Go state change callback
VPNStateChange = CFUNCTYPE(None, c_char_p, c_int, c_int, c_void_p)
def encode_args(args: List[Any], types: List[Any]) -> Iterator[Any]:
+ """Encode the arguments ready to be used by the Go library
+
+ :param args: List[Any]: The list of arguments
+ :param types: List[Any]: The list of the types of the arguments
+
+ :meta: private:
+
+ :return: The arg generator
+ :rtype: Iterator[Any]
+ """
for arg, t in zip(args, types):
# c_char_p needs the str to be encoded to bytes
if t is c_char_p:
@@ -126,7 +182,16 @@ def encode_args(args: List[Any], types: List[Any]) -> Iterator[Any]:
yield arg
-def decode_res(res: Any):
+def decode_res(res: Any) -> Any:
+ """Decode a result as obtained by the Go library
+
+ :param res: Any: The result
+
+ :meta: private:
+
+ :return: The argument decoded
+ :rtype: Any
+ """
decode_map = {
c_int: get_bool,
c_void_p: get_error,
@@ -137,6 +202,17 @@ def decode_res(res: Any):
def get_ptr_string(lib: CDLL, ptr: c_void_p) -> str:
+ """Convert a C string pointer to a Python usable string.
+ This makes sure to free all memory allocated by the Go library
+
+ :param lib: CDLL: The Go shared library
+ :param ptr: c_void_p: The pointer to the C string
+
+ :meta: private:
+
+ :return: The string converted to Python
+ :rtype: str
+ """
if ptr:
string = cast(ptr, c_char_p).value
lib.FreeString(ptr)
@@ -146,6 +222,18 @@ def get_ptr_string(lib: CDLL, ptr: c_void_p) -> str:
def get_ptr_list_strings(lib: CDLL, strings: pointer, total_strings: int) -> List[str]:
+ """Convert a list of C strings to a Python usable list of strings
+ This list is not freed here but is later freed in the Go library for convenience
+
+ :param lib: CDLL: The Go shared library
+ :param strings: pointer: The C pointer to the strings list
+ :param total_strings: int: The total strings in the list
+
+ :meta: private:
+
+ :return: The list of strings converted to Python
+ :rtype: List[str]
+ """
if strings:
strings_list = []
for i in range(total_strings):
@@ -155,6 +243,16 @@ def get_ptr_list_strings(lib: CDLL, strings: pointer, total_strings: int) -> Lis
def get_error(lib: CDLL, ptr: c_void_p) -> Optional[WrappedError]:
+ """Convert a C error structure to a Python usable error structure
+
+ :param lib: CDLL: The Go shared library
+ :param ptr: c_void_p: The pointer to the C error struct
+
+ :meta: private:
+
+ :return: The error if there is one
+ :rtype: Optional[WrappedError]
+ """
if not ptr:
return None
err = cast(ptr, POINTER(cError)).contents
@@ -168,6 +266,16 @@ def get_error(lib: CDLL, ptr: c_void_p) -> Optional[WrappedError]:
def get_config_error(
lib: CDLL, config_error: ConfigError
) -> Tuple[str, str, Optional[WrappedError]]:
+ """Convert a C config structure to a Python usable config structure
+
+ :param lib: CDLL: The Go shared library
+ :param config_error: ConfigError: The config error structure
+
+ :meta: private:
+
+ :return: The configuration, configuration type ('openvpn'/'wireguard') and an optional error
+ :rtype: Tuple[str, str, Optional[WrappedError]]
+ """
config = get_ptr_string(lib, config_error.config)
config_type = get_ptr_string(lib, config_error.config_type)
err = get_error(lib, config_error.error)
@@ -176,11 +284,32 @@ def get_config_error(
def get_data_error(
lib: CDLL, data_error: DataError, data_conv: Callable = get_ptr_string
-) -> Tuple[str, Optional[WrappedError]]:
+) -> Tuple[Any, Optional[WrappedError]]:
+ """Convert a C data+error structure to a Python usable data+error structure
+
+ :param lib: CDLL: The Go shared library
+ :param data_error: DataError: The data error C structure
+ :param data_conv: Callable: (Default value = get_ptr_string): The function that converts the data part
+
+ :meta: private:
+
+ :return: The data and optional error
+ :rtype: Tuple[Any, Optional[WrappedError]]
+ """
data = data_conv(lib, data_error.data)
error = get_error(lib, data_error.error)
return data, error
def get_bool(lib: CDLL, boolInt: c_int) -> bool:
+ """Get a bool from the Go shared library. Essentially just checking if an int represents 'True'
+
+ :param lib: CDLL: The Go shared library
+ :param boolInt: c_int: The C integer that needs to be converted to the Python bool
+
+ :meta: private:
+
+ :return: The boolean converted to Python
+ :rtype: bool
+ """
return boolInt == 1