forked from shaba/openuds
Implementing new IPC mechanics
This commit is contained in:
parent
20bceadda9
commit
a6cb350454
0
actor/src/udsactor/http/client.py
Normal file
0
actor/src/udsactor/http/client.py
Normal file
@ -1,3 +1,33 @@
|
||||
# -*- 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 typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
@ -12,5 +42,3 @@ class Handler:
|
||||
self._service = service
|
||||
self._method = method
|
||||
self._params = params
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# Copyright (c) 2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -36,11 +36,18 @@ if typing.TYPE_CHECKING:
|
||||
from ..service import CommonService
|
||||
|
||||
class LocalProvider(handler.Handler):
|
||||
|
||||
def post_login(self) -> typing.Any:
|
||||
return 'ok'
|
||||
result = self._service.login(self._params['username'])
|
||||
return result._asdict()
|
||||
|
||||
def post_logout(self) -> typing.Any:
|
||||
self._service.logout(self._params['username'])
|
||||
return 'ok'
|
||||
|
||||
def post_ping(self) -> typing.Any:
|
||||
return 'pong'
|
||||
|
||||
def post_register(self) -> typing.Any:
|
||||
self._service._registry.register(self._params['url']) # pylint: disable=protected-access
|
||||
return 'ok'
|
||||
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# Copyright (c) 2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
|
75
actor/src/udsactor/http/registry.py
Normal file
75
actor/src/udsactor/http/registry.py
Normal file
@ -0,0 +1,75 @@
|
||||
# -*- 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 json
|
||||
import typing
|
||||
|
||||
import requests
|
||||
|
||||
from ..log import logger
|
||||
|
||||
class UDSActorClientRegistry:
|
||||
_clientUrl: typing.List[str]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._clientUrl = []
|
||||
|
||||
def _post(self, method: str, data: typing.Any = None) -> None:
|
||||
removables: typing.List[str] = []
|
||||
for clientUrl in self._clientUrl:
|
||||
try:
|
||||
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)
|
||||
removables.append(clientUrl)
|
||||
|
||||
# Remove failed connections
|
||||
for clientUrl in removables:
|
||||
self.unregister(clientUrl)
|
||||
|
||||
def register(self, clientUrl: str) -> None:
|
||||
# Remove first if exists, to avoid duplicates
|
||||
self.unregister(clientUrl)
|
||||
# And add it again
|
||||
self._clientUrl.append(clientUrl)
|
||||
|
||||
def unregister(self, clientUrl: str) -> None:
|
||||
self._clientUrl = list((i for i in self._clientUrl if i != clientUrl))
|
||||
|
||||
def executeScript(self, script: str) -> None:
|
||||
self._post('script', script)
|
||||
|
||||
def logout(self) -> None:
|
||||
self._post('logout', None)
|
||||
|
||||
def ping(self) -> bool:
|
||||
self._post('ping', None)
|
||||
return bool(self._clientUrl) # if no clients available
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# Copyright (c) 2019 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -81,14 +81,15 @@ class HTTPServerHandler(http.server.BaseHTTPRequestHandler):
|
||||
handlerType = PublicProvider
|
||||
elif len(path) == 2 and path[0] == 'ui':
|
||||
# private method, only from localhost
|
||||
handlerType = LocalProvider
|
||||
if self.client_address[0][:3] == '127':
|
||||
handlerType = LocalProvider
|
||||
|
||||
if not handlerType:
|
||||
self.sendJsonResponse(error='Forbidden', code=403)
|
||||
return
|
||||
|
||||
try:
|
||||
result = getattr(handlerType(self._service, method, params), method + '_' + path[-1])()
|
||||
result = getattr(handlerType(self._service, method, params), method + '_' + path[-1])() # last part of path is method
|
||||
except AttributeError:
|
||||
self.sendJsonResponse(error='Method not found', code=404)
|
||||
return
|
||||
|
@ -69,7 +69,7 @@ class Logger:
|
||||
self.remoteLogger = remoteLogger
|
||||
self.own_token = own_token
|
||||
|
||||
def log(self, level: typing.Union[str, int], message: str) -> None:
|
||||
def log(self, level: typing.Union[str, int], message: str, *args) -> None:
|
||||
level = int(level)
|
||||
if level < self.logLevel: # Skip not wanted messages
|
||||
return
|
||||
@ -77,26 +77,26 @@ class Logger:
|
||||
# If remote logger is available, notify message to it
|
||||
try:
|
||||
if self.remoteLogger:
|
||||
self.remoteLogger.log(self.own_token, level, message)
|
||||
self.remoteLogger.log(self.own_token, level, message % args)
|
||||
except Exception as e:
|
||||
self.localLogger.log(FATAL, 'Error notifying log to broker: {}'.format(e))
|
||||
|
||||
self.localLogger.log(level, message)
|
||||
|
||||
def debug(self, message: str) -> None:
|
||||
self.log(DEBUG, message)
|
||||
def debug(self, message: str, *args) -> None:
|
||||
self.log(DEBUG, message, *args)
|
||||
|
||||
def warn(self, message: str) -> None:
|
||||
self.log(WARN, message)
|
||||
def warn(self, message: str, *args) -> None:
|
||||
self.log(WARN, message, *args)
|
||||
|
||||
def info(self, message: str) -> None:
|
||||
self.log(INFO, message)
|
||||
def info(self, message: str, *args) -> None:
|
||||
self.log(INFO, message, *args)
|
||||
|
||||
def error(self, message: str) -> None:
|
||||
self.log(ERROR, message)
|
||||
def error(self, message: str, *args) -> None:
|
||||
self.log(ERROR, message, *args)
|
||||
|
||||
def fatal(self, message: str) -> None:
|
||||
self.log(FATAL, message)
|
||||
def fatal(self, message: str, *args) -> None:
|
||||
self.log(FATAL, message, *args)
|
||||
|
||||
def exception(self) -> None:
|
||||
try:
|
||||
|
@ -93,23 +93,6 @@ class REST:
|
||||
|
||||
raise RESTError(result.content)
|
||||
|
||||
def _login(self, auth: str, username: str, password: str) -> typing.MutableMapping[str, str]:
|
||||
try:
|
||||
# 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)
|
||||
if not result.ok or result.json()['result'] == 'error':
|
||||
raise Exception() # Invalid credentials
|
||||
except requests.ConnectionError as e:
|
||||
raise RESTConnectionError(str(e))
|
||||
except Exception as e:
|
||||
raise RESTError('Invalid credentials')
|
||||
|
||||
headers['X-Auth-Token'] = result.json()['token']
|
||||
|
||||
return headers
|
||||
|
||||
def enumerateAuthenticators(self) -> typing.Iterable[types.AuthenticatorType]:
|
||||
try:
|
||||
result = requests.get(self.url + 'auth/auths', headers=self._headers, verify=self.validateCert, timeout=4)
|
||||
@ -153,8 +136,17 @@ class REST:
|
||||
'log_level': logLevel
|
||||
}
|
||||
|
||||
# First, try to login to REST api
|
||||
try:
|
||||
headers = self._login(auth, username, password)
|
||||
# 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)
|
||||
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)
|
||||
if result.ok:
|
||||
return result.json()['result']
|
||||
@ -162,8 +154,8 @@ class REST:
|
||||
raise RESTConnectionError(str(e))
|
||||
except RESTError:
|
||||
raise
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
raise RESTError('Invalid credentials')
|
||||
|
||||
raise RESTError(result.content)
|
||||
|
||||
@ -207,6 +199,23 @@ class REST:
|
||||
}
|
||||
self._actorPost('ipchange', payload) # Ignores result...
|
||||
|
||||
def login(self, own_token: str, username: str) -> types.LoginResultInfoType:
|
||||
payload = {
|
||||
'token': own_token,
|
||||
'username': username
|
||||
}
|
||||
result = self._actorPost('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:
|
||||
payload = {
|
||||
'token': own_token,
|
||||
'username': username
|
||||
}
|
||||
self._actorPost('logout', payload)
|
||||
|
||||
|
||||
def log(self, own_token: str, level: int, message: str) -> None:
|
||||
payLoad = {
|
||||
'token': own_token,
|
||||
|
@ -41,7 +41,7 @@ from . import rest
|
||||
from . import types
|
||||
# from .script_thread import ScriptExecutorThread
|
||||
from .log import logger
|
||||
|
||||
from .http import registry
|
||||
|
||||
# def setup() -> None:
|
||||
# cfg = platform.store.readConfig()
|
||||
@ -55,7 +55,6 @@ from .log import logger
|
||||
# else:
|
||||
# logger.setLevel(20000)
|
||||
|
||||
|
||||
class CommonService:
|
||||
_isAlive: bool = True
|
||||
_rebootRequested: bool = False
|
||||
@ -65,6 +64,7 @@ class CommonService:
|
||||
_api: rest.REST
|
||||
_interfaces: typing.List[types.InterfaceInfoType]
|
||||
_secret: str
|
||||
_registry: registry.UDSActorClientRegistry
|
||||
|
||||
@staticmethod
|
||||
def execute(cmdLine: str, section: str) -> bool:
|
||||
@ -81,6 +81,7 @@ class CommonService:
|
||||
self._interfaces = []
|
||||
self._api = rest.REST(self._cfg.host, self._cfg.validateCertificate)
|
||||
self._secret = secrets.token_urlsafe(33)
|
||||
self._registry = registry.UDSActorClientRegistry()
|
||||
|
||||
# Initialzies loglevel
|
||||
logger.setLevel(self._cfg.log_level * 10000)
|
||||
@ -179,7 +180,7 @@ class CommonService:
|
||||
while self._isAlive:
|
||||
if not self._interfaces:
|
||||
self._interfaces = list(platform.operations.getNetworkInfo())
|
||||
if not self._interfaces: # Wait a bit for interfaces to get initialized...
|
||||
if not self._interfaces: # Wait a bit for interfaces to get initialized... (has valid IPs)
|
||||
self.doWait(5000)
|
||||
continue
|
||||
|
||||
@ -281,6 +282,16 @@ class CommonService:
|
||||
'''
|
||||
logger.info('Base join invoked: {} on {}, {}'.format(name, domain, ou))
|
||||
|
||||
# Client notifications
|
||||
def login(self, username: str) -> types.LoginResultInfoType:
|
||||
if self._cfg.own_token:
|
||||
return self._api.login(self._cfg.own_token, username)
|
||||
return types.LoginResultInfoType(ip='', hostname='', dead_line=None)
|
||||
|
||||
def logout(self, username: str) -> None:
|
||||
if self._cfg.own_token:
|
||||
self._api.logout(self._cfg.own_token, username)
|
||||
|
||||
# ****************************************
|
||||
# Methods that CAN BE overriden by actors
|
||||
# ****************************************
|
||||
|
@ -48,3 +48,8 @@ class InitializationResultType(typing.NamedTuple):
|
||||
unique_id: typing.Optional[str] = None
|
||||
max_idle: typing.Optional[int] = None
|
||||
os: typing.Optional[ActorOsConfigurationType] = None
|
||||
|
||||
class LoginResultInfoType(typing.NamedTuple):
|
||||
ip: str
|
||||
hostname: str
|
||||
dead_line: typing.Optional[int]
|
||||
|
@ -192,7 +192,7 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
||||
return 'done'
|
||||
|
||||
def onLogout(self, userName) -> None:
|
||||
logger.debug('Windows onLogout invoked: {}, {}'.format(user, self._user))
|
||||
logger.debug('Windows onLogout invoked: {}, {}'.format(userName, self._user))
|
||||
try:
|
||||
p = win32security.GetBinarySid(REMOTE_USERS_SID)
|
||||
groupName = win32security.LookupAccountSid(None, p)[0]
|
||||
@ -216,7 +216,7 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
||||
# call the CoInitialize to allow the registration to run in an other
|
||||
# thread
|
||||
logger.debug('Initializing com...')
|
||||
|
||||
|
||||
pythoncom.CoInitialize()
|
||||
|
||||
if not self.initialize():
|
||||
|
Loading…
Reference in New Issue
Block a user