diff options
| -rw-r--r-- | exports/exports.go | 65 | ||||
| -rw-r--r-- | src/state.go | 3 | ||||
| -rw-r--r-- | wrappers/python/eduvpncommon/__init__.py | 20 | ||||
| -rw-r--r-- | wrappers/python/eduvpncommon/auth.py | 14 | ||||
| -rw-r--r-- | wrappers/python/eduvpncommon/discovery.py | 86 | ||||
| -rw-r--r-- | wrappers/python/eduvpncommon/main.py | 22 |
6 files changed, 51 insertions, 159 deletions
diff --git a/exports/exports.go b/exports/exports.go index e6be488..f0883a8 100644 --- a/exports/exports.go +++ b/exports/exports.go @@ -2,27 +2,38 @@ package main /* #include <stdlib.h> + +typedef void (*PythonCB)(const char* oldstate, const char* newstate); + +// FIXME: Remove this, see: https://stackoverflow.com/questions/58606884/multiple-definition-when-using-cgo +__attribute__((weak)) +void call_callback(PythonCB callback, const char* oldstate, const char* newstate) +{ + callback(oldstate, newstate); +} */ import "C" import "unsafe" import "github.com/jwijenbergh/eduvpn-common/src" -// Functions here should probably not take string parameters, see https://pkg.go.dev/cmd/cgo#hdr-C_references_to_Go -// GetOrganizationsList gets the list of organizations from the disco server. -// Returns the json data as a string and an error code. This is used as key for looking up data. -//export GetOrganizationsList -func GetOrganizationsList() (*C.char, *C.char) { - body, err := eduvpn.GetOrganizationsList() - if err != nil { - return nil, C.CString(err.Error()) +var P_StateCallback C.PythonCB + +func StateCallback(old_state string, new_state string) { + if P_StateCallback == nil { + return } - return C.CString(body), nil + oldState_c := C.CString(old_state) + newState_c := C.CString(new_state) + C.call_callback(P_StateCallback, oldState_c, newState_c) + C.free(unsafe.Pointer(oldState_c)) + C.free(unsafe.Pointer(newState_c)) } //export Register -func Register(name *C.char, url *C.char) { - eduvpn.Register(eduvpn.GetVPNState(), C.GoString(name), C.GoString(url)) +func Register(name *C.char, url *C.char, stateCallback C.PythonCB) { + P_StateCallback = stateCallback + eduvpn.Register(eduvpn.GetVPNState(), C.GoString(name), C.GoString(url), StateCallback) } //export InitializeOAuth @@ -34,42 +45,10 @@ func InitializeOAuth() (*C.char, *C.char) { return C.CString(url), nil } -// GetServersList gets the list of servers from the disco server. -// Returns the json data as a string and an error code. This is used as key for looking up data. -//export GetServersList -func GetServersList() (*C.char, *C.char) { - body, err := eduvpn.GetServersList() - if err != nil { - return nil, C.CString(err.Error()) - } - return C.CString(body), nil -} - //export FreeString func FreeString(addr *C.char) { C.free(unsafe.Pointer(addr)) } -// Verify verifies a signature on a JSON file. See eduvpn.Verify for more details. -// It returns 0 for a valid signature and a nonzero eduvpn.VerifyErrorCode otherwise. -// signatureFileContent must be UTF-8-encoded. -//export Verify -func Verify(signatureFileContent []byte, signedJson []byte, expectedFileName []byte, minSignTime uint64) (int8, *C.char) { - valid, err := eduvpn.Verify(string(signatureFileContent), signedJson, string(expectedFileName), minSignTime, false) - if valid { - return 0, nil - } else { - return 1, C.CString(err.Error()) - } -} - -// InsecureTestingSetExtraKey adds an extra allowed key for verification with Verify. -// ONLY USE FOR TESTING. Not Thread-safe. Do not call in parallel to Verify. -// keyString must be an ASCII Base64-encoded key. -//export InsecureTestingSetExtraKey -func InsecureTestingSetExtraKey(keyString []byte) { - eduvpn.InsecureTestingSetExtraKey(string(keyString)) -} - // Not used in library, but needed to compile. func main() { panic("compile with -buildmode=c-shared") } diff --git a/src/state.go b/src/state.go index 5d054bb..be85a45 100644 --- a/src/state.go +++ b/src/state.go @@ -13,7 +13,7 @@ type EduVPNState struct { OAuthSession *EduVPNOAuthSession } -func Register(state *EduVPNState, name string, server string) error { +func Register(state *EduVPNState, name string, server string, stateCallback func(string, string)) error { state.Name = name state.Server = server @@ -24,6 +24,7 @@ func Register(state *EduVPNState, name string, server string) error { } state.Endpoints = endpoints + stateCallback("START", "REGISTER") return nil } diff --git a/wrappers/python/eduvpncommon/__init__.py b/wrappers/python/eduvpncommon/__init__.py index 911c671..c21f8d4 100644 --- a/wrappers/python/eduvpncommon/__init__.py +++ b/wrappers/python/eduvpncommon/__init__.py @@ -19,27 +19,17 @@ _libfile = f"{_lib_prefixes[_os]}{_libname}{_lib_suffixes[_os]}" # Library should have been copied to the lib/ folder lib = cdll.LoadLibrary(str(pathlib.Path(__file__).parent / "lib" / _libfile)) - -class GoSlice(Structure): - _fields_ = [("data", POINTER(c_char)), ("len", c_int64), ("cap", c_int64)] - - @staticmethod - def make(bs: bytes) -> "GoSlice": - return GoSlice((c_char * len(bs))(*bs), len(bs), len(bs)) # type: ignore - - +# Data types class DataError(Structure): _fields_ = [('data', c_void_p), ('error', c_int64)] +GOCB_StateChange = CFUNCTYPE(None, c_char_p, c_char_p) + +# Exposed functions +lib.Register.argtypes, lib.Register.restype = [c_char_p, c_char_p, GOCB_StateChange], None # 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 -lib.Register.argtypes, lib.Register.restype = [c_char_p, c_char_p], None lib.InitializeOAuth.argtypes, lib.InitializeOAuth.restype = [], c_void_p -lib.GetOrganizationsList.argtypes, lib.GetOrganizationsList.restype = [], DataError -lib.GetServersList.argtypes, lib.GetServersList.restype = [], DataError lib.FreeString.argtypes, lib.FreeString.restype = [c_void_p], None -lib.Verify.argtypes, lib.Verify.restype = [GoSlice, GoSlice, GoSlice, c_uint64], c_int64 -lib.InsecureTestingSetExtraKey.argtypes, lib.InsecureTestingSetExtraKey.restype = [GoSlice], None - diff --git a/wrappers/python/eduvpncommon/auth.py b/wrappers/python/eduvpncommon/auth.py deleted file mode 100644 index b0d1410..0000000 --- a/wrappers/python/eduvpncommon/auth.py +++ /dev/null @@ -1,14 +0,0 @@ -from . import lib -from ctypes import * - -def Register(name, url): - name_bytes = name.encode('utf-8') - url_bytes = url.encode('utf-8') - lib.Register(name_bytes, url_bytes) - -def InitializeOAuth(): - ptr = lib.InitializeOAuth() - value = cast(ptr, c_char_p).value - authURL = value.decode() - lib.FreeString(ptr) - return authURL diff --git a/wrappers/python/eduvpncommon/discovery.py b/wrappers/python/eduvpncommon/discovery.py deleted file mode 100644 index 4820ca2..0000000 --- a/wrappers/python/eduvpncommon/discovery.py +++ /dev/null @@ -1,86 +0,0 @@ -from . import lib, GoSlice, DataError -from .error import GoError -from ctypes import * -from typing import Callable, List, Dict, Any -from enum import Enum -import json - -def getList(func: Callable) -> List[Dict[str, Any]]: - dataError = func() - ptr = dataError.data - error = dataError.error - body = "" - if not error: - body_value = cast(ptr, c_char_p).value - if body_value: - body = body_value.decode() - lib.FreeString(ptr) - if error: - raise RequestError(error) - - return json.loads(body) - -def GetOrganizationsList() -> List[Dict[str, Any]]: - return getList(lib.GetOrganizationsList) - -def GetServersList() -> List[Dict[str, Any]]: - return getList(lib.GetServersList) - - -class RequestErrorCode(Enum): - ErrRequestFileError = 1 # The request for the file has failed. - ErrVerifySigError = 2 # The signature failed to verify. - Unknown = -1 # Other unknown error. - -class RequestError(GoError): - def __init__(self, err: int): - super().__init__(RequestErrorCode(err), - { - RequestErrorCode.ErrRequestFileError: "file request error", - RequestErrorCode.ErrVerifySigError: "signature verify error", - RequestErrorCode.Unknown: "unknown error", - }) - - -class VerifyErrorCode(Enum): - ErrUnknownExpectedFileName = 1 # Unknown expected file name specified. The signature has not been verified. - ErrInvalidSignature = 2 # Signature is invalid (for the expected file type). - ErrInvalidSignatureUnknownKey = 3 # Signature was created with an unknown key and has not been verified. - ErrTooOld = 4 # Signature timestamp smaller than specified minimum signing time (rollback). - Unknown = -1 # Other unknown error. - -class VerifyError(GoError): - def __init__(self, err: int): - super().__init__(VerifyErrorCode(err), - { - VerifyErrorCode.ErrUnknownExpectedFileName: "unknown expected file name", - VerifyErrorCode.ErrInvalidSignature: "invalid signature", - VerifyErrorCode.ErrInvalidSignatureUnknownKey: "invalid signature (unknown key)", - VerifyErrorCode.ErrTooOld: "replay of previous signature (rollback)", - VerifyErrorCode.Unknown: "unknown error", - }) - - -def verify(signature: bytes, signed_json: bytes, expected_file_name: str, min_sign_time: int) -> None: - """ - Verifies the signature on the JSON server_list.json/organization_list.json file. - If the function returns, the signature is valid for the given file type. - - :param signature: .minisig signature file contents. - :param signed_json: Signed .json file contents. - :param expected_file_name: The file type to be verified, one of "server_list.json" or "organization_list.json". - :param min_sign_time: Minimum time for signature (UNIX timestamp, seconds). Should be set to at least the time of the previous signature. - - :raises VerifyException: If signature verification fails or expectedFileName is not one of the allowed values. - """ - - err = lib.Verify(GoSlice.make(signature), GoSlice.make(signed_json), - GoSlice.make(expected_file_name.encode()), min_sign_time) - if err: - raise VerifyError(err) - - -def _insecure_testing_set_extra_key(key_string: str) -> None: - """Use for testing only, see Go documentation.""" - - lib.InsecureTestingSetExtraKey(GoSlice.make(key_string.encode())) diff --git a/wrappers/python/eduvpncommon/main.py b/wrappers/python/eduvpncommon/main.py new file mode 100644 index 0000000..b8278ad --- /dev/null +++ b/wrappers/python/eduvpncommon/main.py @@ -0,0 +1,22 @@ +from . import lib, GOCB_StateChange +from ctypes import * + +@GOCB_StateChange +def state_change(old, new): + print(f"Python: State change {old.decode()} {new.decode()}") + +def InitializeOAuth(): + ptr = lib.InitializeOAuth() + value = cast(ptr, c_char_p).value + authURL = value.decode() + lib.FreeString(ptr) + return authURL + +# Registers the python app with the GO code +# name: The name of the app to be registered +# url: The url of the server to connect to, FIXME: To be removed +# state_callback: The callback to trigger whenever a state is changed, FIXME: Remove whenever this wrapper has implemented callbacks using function decorations +def Register(name, url, state_callback): + name_bytes = name.encode('utf-8') + url_bytes = url.encode('utf-8') + lib.Register(name_bytes, url_bytes, state_callback) |
