advancing

This commit is contained in:
Adolfo Gómez García 2019-12-04 14:10:26 +01:00
parent 72efa3221f
commit 3ed5d26245
16 changed files with 341 additions and 57 deletions

54
actor/src/actor_client.py Normal file
View File

@ -0,0 +1,54 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
# pylint: disable=invalid-name
import sys
import os
import PyQt5 # pylint: disable=unused-import
from PyQt5.QtWidgets import QApplication
from udsactor.log import logger, DEBUG
if __name__ == "__main__":
logger.setLevel(DEBUG)
if 'linux' in sys.platform:
os.environ['QT_X11_NO_MITSHM'] = '1'
logger.info('Started UDS Client Actor')
QApplication.setQuitOnLastWindowClosed(False)
app = QApplication(sys.argv)
# Execute backgroup thread for actions

View File

@ -71,8 +71,8 @@ class UDSConfigDialog(QDialog):
self.ui.password.setText('')
@property
def api(self) -> udsactor.rest.REST:
return udsactor.rest.REST(self.ui.host.text(), self.ui.validateCertificate.currentIndex() == 1)
def api(self) -> udsactor.rest.UDSServerApi:
return udsactor.rest.UDSServerApi(self.ui.host.text(), self.ui.validateCertificate.currentIndex() == 1)
def browse(self, lineEdit: 'QLineEdit', caption: str) -> None:
name = QFileDialog.getOpenFileName(parent=self, caption=caption, directory=os.path.dirname(lineEdit.text()))[0]

View File

@ -30,6 +30,7 @@
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
import sys
if sys.platform == 'win32':
from udsactor.windows import runner
else:

View File

@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
import threading
import http.server
import secrets
import json
import typing
from ..log import logger
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
UDSActorClient = typing.Any
class HTTPServerHandler(http.server.BaseHTTPRequestHandler):
protocol_version = 'HTTP/1.0'
server_version = 'UDS Actor Server'
sys_version = ''
_id: typing.ClassVar[str] # Random id for server
_app: typing.ClassVar['UDSActorClient']
def sendJsonResponse(self, result: typing.Optional[typing.Any] = None, error: typing.Optional[str] = None, code: int = 200) -> None:
data = json.dumps({'result': result, 'error': error})
self.send_response(code)
self.send_header('Content-type', 'application/json')
self.send_header('Content-Length', str(len(data)))
self.send_header('Server: ', self.server_version)
self.end_headers()
self.wfile.write(data.encode())
def do_POST(self) -> None:
# Only allows requests from localhost
if self.client_address[0][:3] != '127':
self.sendJsonResponse(error='Forbidden', code=403)
# Very simple path & params splitter
path = self.path.split('?')[0][1:].split('/')
if len(path) != 2 or path[0] != HTTPServerHandler._id:
self.sendJsonResponse(error='Forbidden', code=403)
try:
length = int(str(self.headers.get('content-length', '0')))
content = self.rfile.read(length)
params: typing.MutableMapping[str, str] = json.loads(content)
except Exception as e:
logger.error('Got exception executing POST {}: {}'.format(self.path, str(e)))
self.sendJsonResponse(error='Invalid request', code=400)
return
try:
result = getattr(self, 'method_' + path[1])(params) # last part of path is method
except AttributeError:
self.sendJsonResponse(error='Invalid request', code=400)
return
except Exception as e:
logger.error('Got exception executing {}: {}'.format('/'.join(path), str(e)))
self.sendJsonResponse(error='Internal error', code=500)
return
self.sendJsonResponse(result)
# Internal methods
def method_ping(self, params: typing.MutableMapping[str, str]) -> typing.Any:
return 'pong'
def method_logout(self, params: typing.MutableMapping[str, str]) -> typing.Any:
return self._app.logout()
def method_message(self, params: typing.MutableMapping[str, str]) -> typing.Any:
return self._app.message(params['message'])
def method_screenshot(self, params: typing.MutableMapping[str, str]) -> typing.Any:
return self._app.screenshot()
def do_GET(self) -> None:
self.sendJsonResponse(error='Forbidden', code=403)
def log_error(self, format: str, *args): # pylint: disable=redefined-builtin
logger.error('ERROR ' + format, *args)
def log_message(self, format: str, *args): # pylint: disable=redefined-builtin
logger.info('INFO ' + format, *args)
class HTTPServerThread(threading.Thread):
_server: typing.Optional[http.server.HTTPServer]
_app: 'UDSActorClient'
port: int
id: str
def __init__(self, app: 'UDSActorClient'):
super().__init__()
self._server = None
self._app = app
self.port = -1
self.id = secrets.token_urlsafe(16)
def stop(self) -> None:
logger.debug('Stopping Http-client Service')
if self._server:
self._server.shutdown()
self._server = None
def run(self):
HTTPServerHandler._app = self._app # pylint: disable=protected-access
HTTPServerHandler._id = self.id # pylint: disable=protected-access
self._server = http.server.HTTPServer(('0.0.0.0', 0), HTTPServerHandler)
self.port = self._server.socket.getsockname()[1]
self._server.serve_forever()

View File

@ -35,17 +35,18 @@ import requests
from ..log import logger
class UDSActorClientRegistry:
class UDSActorClientPool:
_clientUrl: typing.List[str]
def __init__(self) -> None:
self._clientUrl = []
def _post(self, method: str, data: typing.Any = None) -> None:
def _post(self, method: str, data: typing.MutableMapping[str, str]) -> typing.List[requests.Response]:
removables: typing.List[str] = []
result: typing.List[typing.Any] = []
for clientUrl in self._clientUrl:
try:
requests.post(clientUrl + '/' + method, data=json.dumps(data), verify=False)
result.append(requests.post(clientUrl + '/' + method, data=json.dumps(data), verify=False))
except Exception as e:
# If cannot request to a clientUrl, remove it from list
logger.info('Could not coneect with client %s: %s. Removed from registry.', e, clientUrl)
@ -55,6 +56,8 @@ class UDSActorClientRegistry:
for clientUrl in removables:
self.unregister(clientUrl)
return result
def register(self, clientUrl: str) -> None:
# Remove first if exists, to avoid duplicates
self.unregister(clientUrl)
@ -65,14 +68,17 @@ class UDSActorClientRegistry:
self._clientUrl = list((i for i in self._clientUrl if i != clientUrl))
def executeScript(self, script: str) -> None:
self._post('script', script)
self._post('script', {'script': script})
def logout(self) -> None:
self._post('logout', None)
self._post('logout', {})
def message(self, message: str) -> None:
self._post('message', message)
self._post('message', {'message': message})
def ping(self) -> bool:
self._post('ping', None)
self._post('ping', {})
return bool(self._clientUrl) # if no clients available
def screenshot(self) -> typing.List[bytes]:
return []

View File

@ -49,5 +49,8 @@ class LocalProvider(handler.Handler):
return 'pong'
def post_register(self) -> typing.Any:
self._service._registry.register(self._params['url']) # pylint: disable=protected-access
self._service._clientsPool.register(self._params['callback_url']) # pylint: disable=protected-access
return 'ok'
def post_unregister(self) -> typing.Any:
self._service._clientsPool.unregister(self._params['callback_url']) # pylint: disable=protected-access

View File

@ -39,19 +39,19 @@ if typing.TYPE_CHECKING:
from ..service import CommonService
class PublicProvider(handler.Handler):
def post_logoff(self) -> typing.Any:
def post_logout(self) -> typing.Any:
logger.debug('Sending LOGOFF to clients')
self._service._registry.logout() # pylint: disable=protected-access
self._service._clientsPool.logout() # pylint: disable=protected-access
return 'ok'
# Alias
post_logout = post_logoff
post_logoff = post_logout
def post_message(self) -> typing.Any:
logger.debug('Sending MESSAGE to clients')
if 'message' not in self._params:
raise Exception('Invalid message parameters')
self._service._registry.message(self._params['message']) # pylint: disable=protected-access
self._service._clientsPool.message(self._params['message']) # pylint: disable=protected-access
return 'ok'
def post_script(self) -> typing.Any:
@ -60,7 +60,7 @@ class PublicProvider(handler.Handler):
raise Exception('Invalid script parameters')
if 'user' in self._params:
logger.debug('Sending SCRIPT to client')
self._service._registry.message(self._params['scripts']) # pylint: disable=protected-access
self._service._clientsPool.message(self._params['scripts']) # pylint: disable=protected-access
else:
# Execute script at server space, that is, here
# as a parallel thread
@ -78,5 +78,9 @@ class PublicProvider(handler.Handler):
# Return something useful? :)
return 'UDS Actor Secure Server'
def get_screenshot(self) -> typing.Any:
return ''
def get_uuid(self) -> typing.Any:
return self._service._cfg.own_token # pylint: disable=protected-access

View File

@ -32,7 +32,6 @@ import os
import threading
import http.server
import json
import time
import ssl
import typing
@ -47,8 +46,6 @@ if typing.TYPE_CHECKING:
from ..service import CommonService
from .handler import Handler
startTime = time.time()
class HTTPServerHandler(http.server.BaseHTTPRequestHandler):
protocol_version = 'HTTP/1.0'
server_version = 'UDS Actor Server'
@ -111,11 +108,10 @@ class HTTPServerHandler(http.server.BaseHTTPRequestHandler):
try:
length = int(str(self.headers.get('content-length', '0')))
content = self.rfile.read(length)
logger.debug('length: {}, content >>{}<<'.format(length, content))
params: typing.MutableMapping[str, str] = json.loads(content)
except Exception as e:
logger.error('Got exception executing POST {}: {}'.format(self.path, str(e)))
self.sendJsonResponse(error=str(e), code=500)
self.sendJsonResponse(error='Invalid parameters', code=400)
return
self.process('post', params)
@ -139,7 +135,7 @@ class HTTPServerThread(threading.Thread):
self._certFile = None
def stop(self) -> None:
logger.debug('Stopping REST Service')
logger.debug('Stopping Http-server Service')
if self._server:
self._server.shutdown()
self._server = None

View File

@ -34,10 +34,6 @@ import tempfile
import logging
import typing
# Valid logging levels, from UDS Broker (uds.core.utils.log)
OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in range(6)) # @UndefinedVariable
class LocalLogger: # pylint: disable=too-few-public-methods
linux = False
windows = True

View File

@ -183,3 +183,6 @@ def getCurrentUser() -> str:
Returns current logged in user
'''
return os.environ['USER']
def forceTimeSync() -> None:
return

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# Copyright (c) 2014-2019 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -45,9 +45,8 @@ if typing.TYPE_CHECKING:
# Valid logging levels, from UDS Broker (uds.core.utils.log)
OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in range(6))
class Logger:
remoteLogger: typing.Optional['rest.REST']
remoteLogger: typing.Optional['rest.UDSServerApi']
own_token: str
logLevel: int
localLogger: LocalLogger
@ -69,6 +68,10 @@ class Logger:
self.remoteLogger = remoteLogger
self.own_token = own_token
def enableServiceLogger(self):
if self.localLogger.windows:
self.localLogger.serviceLogger = True
def log(self, level: typing.Union[str, int], message: str, *args) -> None:
level = int(level)
if level < self.logLevel: # Skip not wanted messages

View File

@ -58,11 +58,21 @@ class RESTUserServiceNotFoundError(RESTError):
class RESTOsManagerError(RESTError):
ERRCODE = 4
class REST:
#
#
#
class UDSApi:
"""
Base for remote api accesses
"""
_host: str
_validateCert: bool
_url: str
def __init__(self, host: str, validateCert: bool) -> None:
self.host = host
self.validateCert = validateCert
self.url = "https://{}/uds/rest/".format(self.host)
self._host = host
self._validateCert = validateCert
self._url = "https://{}/uds/rest/".format(self._host)
# Disable logging requests messages except for errors, ...
logging.getLogger("requests").setLevel(logging.CRITICAL)
logging.getLogger("urllib3").setLevel(logging.ERROR)
@ -75,7 +85,7 @@ class REST:
def _headers(self) -> typing.MutableMapping[str, str]:
return {'content-type': 'application/json'}
def _actorPost(
def _doPost(
self,
method: str, # i.e. 'initialize', 'ready', ....
payLoad: typing.MutableMapping[str, typing.Any],
@ -83,7 +93,7 @@ class REST:
) -> typing.Any:
headers = headers or self._headers
try:
result = requests.post(self.url + 'actor/v2/' + method, data=json.dumps(payLoad), headers=headers, verify=self.validateCert)
result = requests.post(self._url + 'actor/v2/' + method, data=json.dumps(payLoad), headers=headers, verify=self._validateCert)
if result.ok:
return result.json()['result']
except requests.ConnectionError as e:
@ -93,9 +103,14 @@ class REST:
raise RESTError(result.content)
#
# UDS Broker API access
#
class UDSServerApi(UDSApi):
def enumerateAuthenticators(self) -> typing.Iterable[types.AuthenticatorType]:
try:
result = requests.get(self.url + 'auth/auths', headers=self._headers, verify=self.validateCert, timeout=4)
result = requests.get(self._url + 'auth/auths', headers=self._headers, verify=self._validateCert, timeout=4)
if result.ok:
for v in sorted(result.json(), key=lambda x: x['priority']):
yield types.AuthenticatorType(
@ -141,13 +156,13 @@ class REST:
# First, try to login
authInfo = {'auth': auth, 'username': username, 'password': password}
headers = self._headers
result = requests.post(self.url + 'auth/login', data=json.dumps(authInfo), headers=headers, verify=self.validateCert)
result = requests.post(self._url + 'auth/login', data=json.dumps(authInfo), headers=headers, verify=self._validateCert)
if not result.ok or result.json()['result'] == 'error':
raise Exception() # Invalid credentials
headers['X-Auth-Token'] = result.json()['token']
result = requests.post(self.url + 'actor/v2/register', data=json.dumps(data), headers=headers, verify=self.validateCert)
result = requests.post(self._url + 'actor/v2/register', data=json.dumps(data), headers=headers, verify=self._validateCert)
if result.ok:
return result.json()['result']
except requests.ConnectionError as e:
@ -166,7 +181,7 @@ class REST:
'version': VERSION,
'id': [{'mac': i.mac, 'ip': i.ip} for i in interfaces]
}
r = self._actorPost('initialize', payload)
r = self._doPost('initialize', payload)
os = r['os']
return types.InitializationResultType(
own_token=r['own_token'],
@ -190,7 +205,7 @@ class REST:
'ip': ip,
'port': port
}
result = self._actorPost('ready', payload)
result = self._doPost('ready', payload)
return types.CertificateInfoType(
private_key=result['private_key'],
@ -205,7 +220,7 @@ class REST:
'ip': ip,
'port': port
}
result = self._actorPost('ipchange', payload)
result = self._doPost('ipchange', payload)
return types.CertificateInfoType(
private_key=result['private_key'],
@ -218,7 +233,7 @@ class REST:
'token': own_token,
'username': username
}
result = self._actorPost('login', payload)
result = self._doPost('login', payload)
return types.LoginResultInfoType(ip=result['ip'], hostname=result['hostname'], dead_line=result['dead_line'])
def logout(self, own_token: str, username: str) -> None:
@ -226,7 +241,7 @@ class REST:
'token': own_token,
'username': username
}
self._actorPost('logout', payload)
self._doPost('logout', payload)
def log(self, own_token: str, level: int, message: str) -> None:
@ -235,4 +250,39 @@ class REST:
'level': level,
'message': message
}
self._actorPost('log', payLoad) # Ignores result...
self._doPost('log', payLoad) # Ignores result...
class UDSClientApi(UDSApi):
def register(self, callbackUrl: str) -> None:
payLoad = {
'callback_url': callbackUrl
}
self._doPost('register', payLoad)
def unregister(self, callbackUrl: str) -> None:
payLoad = {
'callback_url': callbackUrl
}
self._doPost('unregister', payLoad)
def login(self, username: str) -> types.LoginResultInfoType:
payLoad = {
'username': username
}
result = self._doPost('login', payLoad)
return types.LoginResultInfoType(
ip=result['ip'],
hostname=result['hostname'],
dead_line=result['dead_line'],
max_idle=result['max_idle']
)
def logout(self, username: str) -> None:
payLoad = {
'username': username
}
self._doPost('logout', payLoad)
def ping(self) -> bool:
return self._doPost('ping', {}) == 'pong'

View File

@ -41,7 +41,7 @@ from . import rest
from . import types
from .log import logger
from .http import registry, server
from .http import clients_pool, server
# def setup() -> None:
# cfg = platform.store.readConfig()
@ -61,11 +61,11 @@ class CommonService: # pylint: disable=too-many-instance-attributes
_loggedIn = False
_cfg: types.ActorConfigurationType
_api: rest.REST
_api: rest.UDSServerApi
_interfaces: typing.List[types.InterfaceInfoType]
_secret: str
_certificate: types.CertificateInfoType
_registry: registry.UDSActorClientRegistry
_clientsPool: clients_pool.UDSActorClientPool
_http: typing.Optional[server.HTTPServerThread]
@staticmethod
@ -81,17 +81,16 @@ class CommonService: # pylint: disable=too-many-instance-attributes
def __init__(self) -> None:
self._cfg = platform.store.readConfig()
self._interfaces = []
self._api = rest.REST(self._cfg.host, self._cfg.validateCertificate)
self._api = rest.UDSServerApi(self._cfg.host, self._cfg.validateCertificate)
self._secret = secrets.token_urlsafe(33)
self._registry = registry.UDSActorClientRegistry()
self._clientsPool = clients_pool.UDSActorClientPool()
self._certificate = types.CertificateInfoType('', '', '')
self._http = None
# Initialzies loglevel and serviceLogger
logger.setLevel(self._cfg.log_level * 10000)
# If windows, enable service logger
if logger.localLogger.windows:
logger.localLogger.serviceLogger = True
logger.enableServiceLogger()
socket.setdefaulttimeout(20)
@ -135,14 +134,21 @@ class CommonService: # pylint: disable=too-many-instance-attributes
srvInterface = self.serviceInterfaceInfo()
if srvInterface:
# Rery while RESTConnectionError (that is, cannot connect)
counter = 8
while self._isAlive:
counter -= 1
try:
self._certificate = self._api.ready(self._cfg.own_token, self._secret, srvInterface.ip, self._cfg.port)
except rest.RESTConnectionError:
except rest.RESTConnectionError as e:
logger.info('Error connecting with UDS Broker: %s', e)
self.doWait(5000)
continue
except Exception as e:
logger.error('Unhandled exception while setting ready: %s', e)
if counter > 0:
self.doWait(10000) # A long wait on other error...
continue
platform.operations.reboot() # On too many errors, simply reboot
# Success or any error that is not recoverable (retunerd by UDS). if Error, service will be cleaned in a while.
break
@ -169,8 +175,10 @@ class CommonService: # pylint: disable=too-many-instance-attributes
# That is, the COMMAND itself has to restart the machine!
return False # If the command fails, continue with the rest of the operations...
# Retry configuration while not stop service, config in case of error 10 times
# Retry configuration while not stop service, config in case of error 10 times, reboot vm
counter = 10
while self._isAlive:
counter -= 1
try:
if self._cfg.config and self._cfg.config.os:
osData = self._cfg.config.os
@ -188,7 +196,11 @@ class CommonService: # pylint: disable=too-many-instance-attributes
break
except Exception as e:
logger.error('Got exception operating machine: {}'.format(e))
self.doWait(5000)
if counter > 0:
self.doWait(5000)
else:
platform.operations.reboot()
return False
return True
@ -196,6 +208,9 @@ class CommonService: # pylint: disable=too-many-instance-attributes
if not self._cfg.host: # Not configured
return False
# Force time sync, just in case...
platform.operations.forceTimeSync()
# Wait for Broker to be ready
while self._isAlive:
if not self._interfaces:
@ -312,11 +327,14 @@ class CommonService: # pylint: disable=too-many-instance-attributes
# Client notifications
def login(self, username: str) -> types.LoginResultInfoType:
result = types.LoginResultInfoType(ip='', hostname='', dead_line=None, max_idle=None)
self._loggedIn = True
if self._cfg.own_token:
return self._api.login(self._cfg.own_token, username)
return types.LoginResultInfoType(ip='', hostname='', dead_line=None)
result = self._api.login(self._cfg.own_token, username)
return result
def logout(self, username: str) -> None:
self._loggedIn = False
if self._cfg.own_token:
self._api.logout(self._cfg.own_token, username)

View File

@ -58,6 +58,7 @@ class LoginResultInfoType(typing.NamedTuple):
ip: str
hostname: str
dead_line: typing.Optional[int]
max_idle: typing.Optional[int] = None # Not provided by broker
class CertificateInfoType(typing.NamedTuple):
private_key: str

View File

@ -35,7 +35,7 @@ import tempfile
import servicemanager # @UnresolvedImport, pylint: disable=import-error
# Valid logging levels, from UDS Broker (uds.core.utils.log)
# Valid logging levels, from UDS Broker (uds.core.utils.log).
OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in range(6))
class LocalLogger: # pylint: disable=too-few-public-methods

View File

@ -30,6 +30,7 @@
'''
# pylint: disable=invalid-name
import os
import subprocess
import ctypes
from ctypes.wintypes import DWORD, LPCWSTR
import typing
@ -217,7 +218,6 @@ def getIdleDuration() -> float:
logger.error('Getting idle duration: {}'.format(e))
return 0
def getCurrentUser() -> str:
'''
Returns current logged in username
@ -235,3 +235,9 @@ def writeToPipe(pipeName: str, bytesPayload: bytes, waitForResponse: bool) -> ty
return b'ok'
except Exception:
return None
def forceTimeSync() -> None:
try:
subprocess.call([r'c:\WINDOWS\System32\w32tm.exe', ' /resync']) # , '/rediscover'])
except Exception as e:
logger.error('Error invoking time sync command: %s', e)