Removed encoders modules

This commit is contained in:
Adolfo Gómez García 2020-11-13 09:35:18 +01:00
parent 06b0f1396f
commit 45ca92b77e
12 changed files with 372 additions and 200 deletions

View File

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2019 Virtual Cable S.L.
# Copyright (c) 2014-2020 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -26,11 +25,11 @@
# 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 codecs
import logging
import typing
@ -46,7 +45,6 @@ from uds.core.managers import cryptoManager, userServiceManager
from uds.core.util.config import GlobalConfig
from uds.core.services.exceptions import ServiceNotReadyError
from uds.core import VERSION as UDS_VERSION
from uds.core.util import encoders
logger = logging.getLogger(__name__)
@ -147,7 +145,8 @@ class Client(Handler):
password = cryptoManager().symDecrpyt(data['password'], scrambler)
# userService.setConnectionSource(srcIp, hostname) # Store where we are accessing from so we can notify Service
if not ip:
raise ServiceNotReadyError
transportScript, signature, params = transportInstance.getEncodedTransportScript(userService, transport, ip, self._request.os, self._request.user, password, self._request)
logger.debug('Signature: %s', signature)
@ -156,7 +155,7 @@ class Client(Handler):
return Client.result(result={
'script': transportScript,
'signature': signature, # It is already on base64
'params': encoders.encode(encoders.encode(json.dumps(params), 'bz2'), 'base64', asText=True),
'params': codecs.encode(codecs.encode(json.dumps(params), 'bz2'), 'base64').decode(),
})
except ServiceNotReadyError as e:
# Refresh ticket and make this retrayable

View File

@ -31,8 +31,9 @@
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import pickle
import logging
import datetime
import codecs
import logging
import typing
from uds import models
@ -40,7 +41,6 @@ from uds import models
from uds.core.util.stats import counters
from uds.core.util.cache import Cache
from uds.core.util.state import State
from uds.core.util import encoders
from uds.REST import Handler, RequestError, ResponseError
logger = logging.getLogger(__name__)
@ -53,10 +53,16 @@ SINCE = 365 # Days
USE_MAX = True
def getServicesPoolsCounters(servicePool: typing.Optional[models.ServicePool], counter_type: int) -> typing.List[typing.Dict[str, typing.Any]]:
# pylint: disable=no-value-for-parameter
def getServicesPoolsCounters(
servicePool: typing.Optional[models.ServicePool], counter_type: int
) -> typing.List[typing.Dict[str, typing.Any]]:
try:
cacheKey = (servicePool and servicePool.id or 'all') + str(counter_type) + str(POINTS) + str(SINCE)
cacheKey = (
(servicePool and str(servicePool.id) or 'all')
+ str(counter_type)
+ str(POINTS)
+ str(SINCE)
)
to = models.getSqlDatetime()
since: datetime.datetime = to - datetime.timedelta(days=SINCE)
@ -69,14 +75,22 @@ def getServicesPoolsCounters(servicePool: typing.Optional[models.ServicePool], c
us = servicePool
complete = False
val = []
for x in counters.getCounters(us, counter_type, since=since, to=to, max_intervals=POINTS, use_max=USE_MAX, all=complete):
for x in counters.getCounters(
us,
counter_type,
since=since,
to=to,
max_intervals=POINTS,
use_max=USE_MAX,
all=complete,
):
val.append({'stamp': x[0], 'value': int(x[1])})
if len(val) > 2:
cache.put(cacheKey, encoders.encode(pickle.dumps(val), 'zip'), 600)
cache.put(cacheKey, codecs.encode(pickle.dumps(val), 'zip'), 600)
else:
val = [{'stamp': since, 'value': 0}, {'stamp': to, 'value': 0}]
else:
val = pickle.loads(typing.cast(bytes, encoders.decode(val, 'zip')))
val = pickle.loads(codecs.decode(val, 'zip'))
return val
except:
@ -91,13 +105,17 @@ class System(Handler):
logger.debug('args: %s', self._args)
if len(self._args) == 1:
if self._args[0] == 'overview': # System overview
users: models.User = models.User.objects.count()
groups: models.Group = models.Group.objects.count()
services: models.Service = models.Service.objects.count()
service_pools: models.ServicePool = models.ServicePool.objects.count()
meta_pools: models.MetaPool = models.MetaPool.objects.count()
user_services: models.UserService = models.UserService.objects.exclude(state__in=(State.REMOVED, State.ERROR)).count()
restrained_services_pools: int = models.ServicePool.getRestrainedsQuerySet().count()
users: int = models.User.objects.count()
groups: int = models.Group.objects.count()
services: int = models.Service.objects.count()
service_pools: int = models.ServicePool.objects.count()
meta_pools: int = models.MetaPool.objects.count()
user_services: int = models.UserService.objects.exclude(
state__in=(State.REMOVED, State.ERROR)
).count()
restrained_services_pools: int = (
models.ServicePool.getRestrainedsQuerySet().count()
)
return {
'users': users,
'groups': groups,

View File

@ -393,7 +393,7 @@ class UserServiceManager:
checks if we can do a "remove" from a deployed service
serviceIsntance is just a helper, so if we already have unserialized deployedService
"""
removing = self.getServicesInStateForProvider(servicePool.service.provider_id, State.REMOVING)
removing = self.getServicesInStateForProvider(servicePool.service.provider.id, State.REMOVING)
serviceInstance = servicePool.service.getInstance()
if removing >= serviceInstance.parent().getMaxRemovingServices() and serviceInstance.parent().getIgnoreLimits() is False:
return False
@ -403,7 +403,7 @@ class UserServiceManager:
"""
Checks if we can start a new service
"""
preparing = self.getServicesInStateForProvider(servicePool.service.provider_id, State.PREPARING)
preparing = self.getServicesInStateForProvider(servicePool.service.provider.id, State.PREPARING)
serviceInstance = servicePool.service.getInstance()
if preparing >= serviceInstance.parent().getMaxPreparingServices() and serviceInstance.parent().getIgnoreLimits() is False:
return False
@ -539,7 +539,7 @@ class UserServiceManager:
def getService( # pylint: disable=too-many-locals, too-many-branches, too-many-statements
self,
user: User,
os: typing.Dict,
os: typing.MutableMapping,
srcIp: str,
idService: str,
idTransport: str,
@ -643,7 +643,7 @@ class UserServiceManager:
self,
user: User,
srcIp: str,
os: typing.Dict,
os: typing.MutableMapping,
idMetaPool: str,
clientHostName: typing.Optional[str] = None
) -> typing.Tuple[typing.Optional[str], UserService, typing.Optional['services.UserDeployment'], Transport, typing.Optional[transports.Transport]]:
@ -660,9 +660,9 @@ class UserServiceManager:
if meta.policy == MetaPool.PRIORITY_POOL:
sortPools = [(p.priority, p.pool) for p in meta.members.all()]
elif meta.policy == MetaPool.MOST_AVAILABLE_BY_NUMBER:
sortPools = [(p.usage(), p) for p in meta.pools.all()]
sortPools = [(p.pool.usage(), p.pool) for p in meta.members.all()]
else:
sortPools = [(random.randint(0, 10000), p) for p in meta.pools.all()] # Just shuffle them
sortPools = [(random.randint(0, 10000), p.pool) for p in meta.members.all()] # Just shuffle them
# Sort pools related to policy now, and xtract only pools, not sort keys
# Remove "full" pools (100%) from result and pools in maintenance mode, not ready pools, etc...

View File

@ -345,7 +345,7 @@ class UserDeployment(Environmentable, Serializable): # pylint: disable=too-many
"""
return State.FINISHED
def deployForCache(self, cacheLevel: int):
def deployForCache(self, cacheLevel: int) -> str:
"""
Deploys a user deployment as cache.

View File

@ -1,61 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017-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
import codecs
def __toBinary(data: typing.Union[str, bytes]) -> bytes:
if isinstance(data, str):
return data.encode('utf8')
return data
def encode(data: typing.Union[str, bytes], encoder: str, asText: bool = False) -> typing.Union[str, bytes]:
res = codecs.encode(__toBinary(data), encoder) # type: ignore
if asText:
return res.decode('utf8')
return res
def decode(data: typing.Union[str, bytes], encoder: str, asText: bool = False) -> typing.Union[str, bytes]:
res = codecs.decode(__toBinary(data), encoder)
if asText:
return res.decode('utf8') # type: ignore
return res
def encodeAsStr(data: typing.Union[str, bytes], encoder: str) -> str:
return codecs.encode(__toBinary(data), encoder).decode('utf8') # type: ignore
def decodeAsStr(data: typing.Union[str, bytes], encoder: str) -> str:
return codecs.decode(__toBinary(data), encoder).decode('utf8') # type: ignore

View File

@ -42,7 +42,7 @@ import django.template.defaultfilters as filters
from uds.core import services
class DictAsObj:
class DictAsObj(dict):
"""
Returns a mix between a dict and an obj
Can be accesses as .xxxx or ['xxx']

View File

@ -1,9 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations
from uds.core.ui import gui
from uds.core.util import encoders
# Disabled, not needed on 3.0 and forward migrations...
# (can be squashed, this transforms data, that is nonexistant on 3.0 new install or recent migrations)

View File

@ -32,6 +32,7 @@
"""
import random
import string
import codecs
import logging
import typing
@ -39,7 +40,6 @@ from django.utils.translation import ugettext_noop as _
from uds.core.ui import gui
from uds.core import osmanagers
from uds.core.util import log
from uds.core.util import encoders
from .linux_osmanager import LinuxOsManager
@ -61,6 +61,8 @@ class LinuxRandomPassManager(LinuxOsManager):
onLogout = LinuxOsManager.onLogout
idle = LinuxOsManager.idle
_userAccount: str
def __init__(self, environment, values):
super(LinuxRandomPassManager, self).__init__(environment, values)
if values is not None:
@ -104,13 +106,13 @@ class LinuxRandomPassManager(LinuxOsManager):
Serializes the os manager data so we can store it in database
"""
base = LinuxOsManager.marshal(self)
return '\t'.join(['v1', self._userAccount, typing.cast(str, encoders.encode(base, 'hex', asText=True))]).encode('utf8')
return '\t'.join(['v1', self._userAccount, codecs.encode(base, 'hex').decode()]).encode('utf8')
def unmarshal(self, data: bytes) -> None:
values = data.decode('utf8').split('\t')
if values[0] == 'v1':
self._userAccount = values[1]
LinuxOsManager.unmarshal(self, typing.cast(bytes, encoders.decode(values[2], 'hex')))
values = data.split(b'\t')
if values[0] == b'v1':
self._userAccount = values[1].decode()
LinuxOsManager.unmarshal(self, codecs.decode(values[2], 'hex'))
def valuesDict(self):
dic = LinuxOsManager.valuesDict(self)

View File

@ -8,6 +8,7 @@
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import codecs
import logging
import typing
@ -17,7 +18,6 @@ from uds.core.services import types as serviceTypes
from uds.core.ui import gui
from uds.core.managers import userServiceManager
from uds.core.util.state import State
from uds.core.util import encoders
from uds.core.util import log
from uds.models import TicketStore
@ -38,7 +38,7 @@ def scrambleMsg(msg: str) -> str:
for c in data[::-1]:
res += bytes([c ^ n])
n = (n + c) & 0xFF
return typing.cast(str, encoders.encode(res, 'hex', asText=True))
return codecs.encode(res, 'hex').decode()
class WindowsOsManager(osmanagers.OSManager):
@ -56,9 +56,12 @@ class WindowsOsManager(osmanagers.OSManager):
values=[
{'id': 'keep', 'text': ugettext_lazy('Keep service assigned')},
{'id': 'remove', 'text': ugettext_lazy('Remove service')},
{'id': 'keep-always', 'text': ugettext_lazy('Keep service assigned even on new publication')},
{
'id': 'keep-always',
'text': ugettext_lazy('Keep service assigned even on new publication'),
},
],
defvalue='keep'
defvalue='keep',
)
idle = gui.NumericField(
@ -67,18 +70,26 @@ class WindowsOsManager(osmanagers.OSManager):
defvalue=-1,
rdonly=False,
order=11,
tooltip=_('Maximum idle time (in seconds) before session is automatically closed to the user (<= 0 means no max. idle time)'),
required=True
tooltip=_(
'Maximum idle time (in seconds) before session is automatically closed to the user (<= 0 means no max. idle time)'
),
required=True,
)
_onLogout: str
_idle: int
@staticmethod
def validateLen(length):
try:
length = int(length)
except Exception:
raise osmanagers.OSManager.ValidationException(_('Length must be numeric!!'))
raise osmanagers.OSManager.ValidationException(
_('Length must be numeric!!')
)
if length > 6 or length < 1:
raise osmanagers.OSManager.ValidationException(_('Length must be betwen 1 and 6'))
raise osmanagers.OSManager.ValidationException(
_('Length must be betwen 1 and 6')
)
return length
def __setProcessUnusedMachines(self):
@ -96,11 +107,13 @@ class WindowsOsManager(osmanagers.OSManager):
self.__setProcessUnusedMachines()
def isRemovableOnLogout(self, userService: 'UserService') -> bool:
'''
"""
Says if a machine is removable on logout
'''
"""
if not userService.in_use:
if (self._onLogout == 'remove') or (not userService.isValidPublication() and self._onLogout == 'keep'):
if (self._onLogout == 'remove') or (
not userService.isValidPublication() and self._onLogout == 'keep'
):
return True
return False
@ -117,7 +130,9 @@ class WindowsOsManager(osmanagers.OSManager):
def infoValue(self, userService: 'UserService') -> str:
return 'rename\r' + self.getName(userService)
def notifyIp(self, uid: str, userService, data: typing.Dict[str, typing.Any]) -> None:
def notifyIp(
self, uid: str, userService, data: typing.Dict[str, typing.Any]
) -> None:
userServiceInstance = userService.getInstance()
ip = ''
@ -138,13 +153,15 @@ class WindowsOsManager(osmanagers.OSManager):
try:
level = int(levelStr)
except Exception:
logger.debug('Do not understand level %s', level)
logger.debug('Do not understand level %s', levelStr)
level = log.INFO
log.doLog(userService, level, msg, origin)
except Exception:
logger.exception('WindowsOs Manager message log: ')
log.doLog(userService, log.ERROR, "do not understand {0}".format(data), origin)
log.doLog(
userService, log.ERROR, "do not understand {0}".format(data), origin
)
# default "ready received" does nothing
def readyReceived(self, userService, data):
@ -162,13 +179,18 @@ class WindowsOsManager(osmanagers.OSManager):
def readyNotified(self, userService):
return
def actorData(self, userService: 'UserService') -> typing.MutableMapping[str, typing.Any]:
return {
'action': 'rename',
'name': userService.getName()
}
def actorData(
self, userService: 'UserService'
) -> typing.MutableMapping[str, typing.Any]:
return {'action': 'rename', 'name': userService.getName()}
def process(self, userService: 'UserService', message: str, data: typing.Any, options: typing.Optional[typing.Dict[str, typing.Any]] = None) -> str: # pylint: disable=too-many-branches
def process(
self,
userService: 'UserService',
message: str,
data: typing.Any,
options: typing.Optional[typing.Dict[str, typing.Any]] = None,
) -> str: # pylint: disable=too-many-branches
"""
We understand this messages:
* msg = info, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class) (old method)
@ -177,12 +199,19 @@ class WindowsOsManager(osmanagers.OSManager):
* msg = logoff, data = Username, Informs that the username has logged out of the machine
* msg = ready, data = None, Informs machine ready to be used
"""
logger.info("Invoked WindowsOsManager for %s with params: %s,%s", userService, message, data)
logger.info(
"Invoked WindowsOsManager for %s with params: %s,%s",
userService,
message,
data,
)
if message in ('ready', 'ip') and not isinstance(data, dict): # Old actors, previous to 2.5, convert it information..
if message in ('ready', 'ip') and not isinstance(
data, dict
): # Old actors, previous to 2.5, convert it information..
data = {
'ips': [v.split('=') for v in data.split(',')],
'hostname': userService.friendly_name
'hostname': userService.friendly_name,
}
# We get from storage the name for this userService. If no name, we try to assign a new one
@ -197,20 +226,25 @@ class WindowsOsManager(osmanagers.OSManager):
ret = self.infoValue(userService)
state = State.PREPARING
elif message == "log":
self.doLog(userService, data, log.ACTOR)
elif message in("logon", 'login'):
self.doLog(userService, str(data), log.ACTOR)
elif message in ("logon", 'login'):
if '\\' not in data:
osmanagers.OSManager.loggedIn(userService, data)
osmanagers.OSManager.loggedIn(userService, str(data))
userService.setInUse(True)
# We get the userService logged hostname & ip and returns this
ip, hostname = userService.getConnectionSource()
deadLine = userService.deployed_service.getDeadline()
if typing.cast(str, userService.getProperty('actor_version', '0.0.0')) >= '2.0.0':
ret = "{0}\t{1}\t{2}".format(ip, hostname, 0 if deadLine is None else deadLine)
if (
typing.cast(str, userService.getProperty('actor_version', '0.0.0'))
>= '2.0.0'
):
ret = "{0}\t{1}\t{2}".format(
ip, hostname, 0 if deadLine is None else deadLine
)
else:
ret = "{0}\t{1}".format(ip, hostname)
elif message in ('logoff', 'logout'):
osmanagers.OSManager.loggedOut(userService, data)
osmanagers.OSManager.loggedOut(userService, str(data))
doRemove = self.isRemovableOnLogout(userService)
elif message == "ip":
# This ocurss on main loop inside machine, so userService is usable
@ -230,7 +264,9 @@ class WindowsOsManager(osmanagers.OSManager):
userService.release()
else:
if notifyReady is False:
userService.save(update_fields=['in_use', 'in_use_date', 'os_state', 'state', 'data'])
userService.save(
update_fields=['in_use', 'in_use_date', 'os_state', 'state', 'data']
)
else:
logger.debug('Notifying ready')
userServiceManager().notifyReadyFromOsManager(userService, '')
@ -239,7 +275,9 @@ class WindowsOsManager(osmanagers.OSManager):
return ret
return scrambleMsg(ret)
def processUserPassword(self, userService: 'UserService', username: str, password: str) -> typing.Tuple[str, str]:
def processUserPassword(
self, userService: 'UserService', username: str, password: str
) -> typing.Tuple[str, str]:
if userService.getProperty('sso_available') == '1':
# Generate a ticket, store it and return username with no password
domain = ''
@ -248,15 +286,15 @@ class WindowsOsManager(osmanagers.OSManager):
elif '\\' in username:
username, domain = username.split('\\')
creds = {
'username': username,
'password': password,
'domain': domain
}
ticket = TicketStore.create(creds, validatorFnc=None, validity=300) # , owner=SECURE_OWNER, secure=True)
creds = {'username': username, 'password': password, 'domain': domain}
ticket = TicketStore.create(
creds, validatorFnc=None, validity=300
) # , owner=SECURE_OWNER, secure=True)
return ticket, ''
return osmanagers.OSManager.processUserPassword(self, userService, username, password)
return osmanagers.OSManager.processUserPassword(
self, userService, username, password
)
def processUnused(self, userService: 'UserService') -> None:
"""
@ -264,7 +302,12 @@ class WindowsOsManager(osmanagers.OSManager):
This function can update userService values. Normal operation will be remove machines if this state is not valid
"""
if self.isRemovableOnLogout(userService):
log.doLog(userService, log.INFO, 'Unused user service for too long. Removing due to OS Manager parameters.', log.OSMANAGER)
log.doLog(
userService,
log.INFO,
'Unused user service for too long. Removing due to OS Manager parameters.',
log.OSMANAGER,
)
userService.remove()
def isPersistent(self):
@ -279,7 +322,9 @@ class WindowsOsManager(osmanagers.OSManager):
"""
On production environments, will return no idle for non removable machines
"""
if self._idle <= 0: # or (settings.DEBUG is False and self._onLogout != 'remove'):
if (
self._idle <= 0
): # or (settings.DEBUG is False and self._onLogout != 'remove'):
return None
return self._idle
@ -299,9 +344,11 @@ class WindowsOsManager(osmanagers.OSManager):
elif vals[0] == 'v2':
self._onLogout, self._idle = vals[1], int(vals[2])
except Exception:
logger.exception('Exception unmarshalling. Some values left as default ones')
logger.exception(
'Exception unmarshalling. Some values left as default ones'
)
self.__setProcessUnusedMachines()
def valuesDict(self) -> gui.ValuesDictType:
return {'onLogout': self._onLogout, 'idle': self._idle}
return {'onLogout': self._onLogout, 'idle': str(self._idle)}

View File

@ -31,6 +31,7 @@
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import codecs
import logging
import typing
@ -42,7 +43,6 @@ from uds.core.ui import gui
from uds.core.managers import cryptoManager
from uds.core import osmanagers
from uds.core.util import log
from uds.core.util import encoders
from uds.core.util import ldaputil
from .windows import WindowsOsManager
@ -64,14 +64,69 @@ class WinDomainOsManager(WindowsOsManager):
iconFile = 'wosmanager.png'
# Apart form data from windows os manager, we need also domain and credentials
domain = gui.TextField(length=64, label=_('Domain'), order=1, tooltip=_('Domain to join machines to (use FQDN form, Netbios name not supported for most operations)'), required=True)
account = gui.TextField(length=64, label=_('Account'), order=2, tooltip=_('Account with rights to add machines to domain'), required=True)
password = gui.PasswordField(length=64, label=_('Password'), order=3, tooltip=_('Password of the account'), required=True)
ou = gui.TextField(length=256, label=_('OU'), order=4, tooltip=_('Organizational unit where to add machines in domain (check it before using it). i.e.: ou=My Machines,dc=mydomain,dc=local'))
grp = gui.TextField(length=64, label=_('Machine Group'), order=7, tooltip=_('Group to which add machines on creation. If empty, no group will be used. (experimental)'), tab=_('Advanced'))
removeOnExit = gui.CheckBoxField(label=_('Machine clean'), order=8, tooltip=_('If checked, UDS will try to remove the machine from the domain USING the provided credentials'), tab=_('Advanced'), defvalue=gui.TRUE)
serverHint = gui.TextField(length=64, label=_('Server Hint'), order=9, tooltip=_('In case of several AD servers, which one is preferred'), tab=_('Advanced'))
ssl = gui.CheckBoxField(label=_('Use SSL'), order=10, tooltip=_('If checked, a ssl connection to Active Directory will be used'), tab=_('Advanced'), defvalue=gui.TRUE)
domain = gui.TextField(
length=64,
label=_('Domain'),
order=1,
tooltip=_(
'Domain to join machines to (use FQDN form, Netbios name not supported for most operations)'
),
required=True,
)
account = gui.TextField(
length=64,
label=_('Account'),
order=2,
tooltip=_('Account with rights to add machines to domain'),
required=True,
)
password = gui.PasswordField(
length=64,
label=_('Password'),
order=3,
tooltip=_('Password of the account'),
required=True,
)
ou = gui.TextField(
length=256,
label=_('OU'),
order=4,
tooltip=_(
'Organizational unit where to add machines in domain (check it before using it). i.e.: ou=My Machines,dc=mydomain,dc=local'
),
)
grp = gui.TextField(
length=64,
label=_('Machine Group'),
order=7,
tooltip=_(
'Group to which add machines on creation. If empty, no group will be used. (experimental)'
),
tab=_('Advanced'),
)
removeOnExit = gui.CheckBoxField(
label=_('Machine clean'),
order=8,
tooltip=_(
'If checked, UDS will try to remove the machine from the domain USING the provided credentials'
),
tab=_('Advanced'),
defvalue=gui.TRUE,
)
serverHint = gui.TextField(
length=64,
label=_('Server Hint'),
order=9,
tooltip=_('In case of several AD servers, which one is preferred'),
tab=_('Advanced'),
)
ssl = gui.CheckBoxField(
label=_('Use SSL'),
order=10,
tooltip=_('If checked, a ssl connection to Active Directory will be used'),
tab=_('Advanced'),
defvalue=gui.TRUE,
)
# Inherits base "onLogout"
onLogout = WindowsOsManager.onLogout
@ -90,15 +145,23 @@ class WinDomainOsManager(WindowsOsManager):
super().__init__(environment, values)
if values:
if values['domain'] == '':
raise osmanagers.OSManager.ValidationException(_('Must provide a domain!'))
raise osmanagers.OSManager.ValidationException(
_('Must provide a domain!')
)
# if values['domain'].find('.') == -1:
# raise osmanagers.OSManager.ValidationException(_('Must provide domain in FQDN'))
if values['account'] == '':
raise osmanagers.OSManager.ValidationException(_('Must provide an account to add machines to domain!'))
raise osmanagers.OSManager.ValidationException(
_('Must provide an account to add machines to domain!')
)
if values['account'].find('\\') != -1:
raise osmanagers.OSManager.ValidationException(_('DOM\\USER form is not allowed!'))
raise osmanagers.OSManager.ValidationException(
_('DOM\\USER form is not allowed!')
)
if values['password'] == '':
raise osmanagers.OSManager.ValidationException(_('Must provide a password for the account!'))
raise osmanagers.OSManager.ValidationException(
_('Must provide a password for the account!')
)
self._domain = values['domain']
self._ou = values['ou'].strip()
self._account = values['account']
@ -127,10 +190,17 @@ class WinDomainOsManager(WindowsOsManager):
if self._serverHint != '':
yield (self._serverHint, 389)
for server in reversed(sorted(dns.resolver.query('_ldap._tcp.' + self._domain, 'SRV'), key=lambda i: i.priority * 10000 + i.weight)):
for server in reversed(
sorted(
dns.resolver.query('_ldap._tcp.' + self._domain, 'SRV'),
key=lambda i: i.priority * 10000 + i.weight,
)
):
yield (str(server.target)[:-1], server.port)
def __connectLdap(self, servers: typing.Optional[typing.Iterable[typing.Tuple[str, int]]] = None) -> typing.Any:
def __connectLdap(
self, servers: typing.Optional[typing.Iterable[typing.Tuple[str, int]]] = None
) -> typing.Any:
"""
Tries to connect to LDAP
Raises an exception if not found:
@ -150,7 +220,15 @@ class WinDomainOsManager(WindowsOsManager):
ssl = self._ssl == 'y'
port = server[1] if not ssl else -1
try:
return ldaputil.connection(account, self._password, server[0], port, ssl=ssl, timeout=10, debug=False)
return ldaputil.connection(
account,
self._password,
server[0],
port,
ssl=ssl,
timeout=10,
debug=False,
)
except Exception as e:
_str = 'Error: {}'.format(e)
@ -161,7 +239,17 @@ class WinDomainOsManager(WindowsOsManager):
group = ldaputil.escape(self._group)
obj: typing.Optional[typing.MutableMapping[str, typing.Any]]
try:
obj = next(ldaputil.getAsDict(ldapConnection, base, "(&(objectClass=group)(|(cn={0})(sAMAccountName={0})))".format(group), ['dn'], sizeLimit=50))
obj = next(
ldaputil.getAsDict(
ldapConnection,
base,
"(&(objectClass=group)(|(cn={0})(sAMAccountName={0})))".format(
group
),
['dn'],
sizeLimit=50,
)
)
except StopIteration:
obj = None
@ -176,10 +264,14 @@ class WinDomainOsManager(WindowsOsManager):
# else:
base = ','.join(['DC=' + i for i in self._domain.split('.')])
fltr = '(&(objectClass=computer)(sAMAccountName={}$))'.format(ldaputil.escape(machineName))
fltr = '(&(objectClass=computer)(sAMAccountName={}$))'.format(
ldaputil.escape(machineName)
)
obj: typing.Optional[typing.MutableMapping[str, typing.Any]]
try:
obj = next(ldaputil.getAsDict(ldapConnection, base, fltr, ['dn'], sizeLimit=50))
obj = next(
ldaputil.getAsDict(ldapConnection, base, fltr, ['dn'], sizeLimit=50)
)
except StopIteration:
obj = None
@ -208,21 +300,34 @@ class WinDomainOsManager(WindowsOsManager):
# #
# Direct LDAP operation "modify", maybe this need to be added to ldaputil? :)
# #
ldapConnection.modify_s(group, ((ldap.MOD_ADD, 'member', machine),)) # @UndefinedVariable
ldapConnection.modify_s(
group, ((ldap.MOD_ADD, 'member', machine),) # type: ignore # (valid)
) # @UndefinedVariable
error = None
break
except dns.resolver.NXDOMAIN: # No domain found, log it and pass
logger.warning('Could not find _ldap._tcp.%s', self._domain)
log.doLog(userService, log.WARN, "Could not remove machine from domain (_ldap._tcp.{0} not found)".format(self._domain), log.OSMANAGER)
except ldap.ALREADY_EXISTS: # @UndefinedVariable
log.doLog(
userService,
log.WARN,
"Could not remove machine from domain (_ldap._tcp.{0} not found)".format(
self._domain
),
log.OSMANAGER,
)
except ldap.ALREADY_EXISTS: # type: ignore # (valid)
# Already added this machine to this group, pass
error = None
break
except ldaputil.LDAPError:
logger.exception('Ldap Exception caught')
error = "Could not add machine (invalid credentials? for {0})".format(self._account)
error = "Could not add machine (invalid credentials? for {0})".format(
self._account
)
except Exception as e:
error = "Could not add machine {} to group {}: {}".format(userService.friendly_name, self._group, e)
error = "Could not add machine {} to group {}: {}".format(
userService.friendly_name, self._group, e
)
# logger.exception('Ldap Exception caught')
if error:
@ -238,31 +343,59 @@ class WinDomainOsManager(WindowsOsManager):
if '.' not in self._domain:
# logger.info('Releasing from a not FQDN domain is not supported')
log.doLog(userService, log.INFO, "Removing a domain machine form a non FQDN domain is not supported.", log.OSMANAGER)
log.doLog(
userService,
log.INFO,
"Removing a domain machine form a non FQDN domain is not supported.",
log.OSMANAGER,
)
return
try:
ldapConnection = self.__connectLdap()
except dns.resolver.NXDOMAIN: # No domain found, log it and pass
logger.warning('Could not find _ldap._tcp.%s', self._domain)
log.doLog(userService, log.WARN, "Could not remove machine from domain (_ldap._tcp.{} not found)".format(self._domain), log.OSMANAGER)
log.doLog(
userService,
log.WARN,
"Could not remove machine from domain (_ldap._tcp.{} not found)".format(
self._domain
),
log.OSMANAGER,
)
return
except ldaputil.LDAPError as e:
# logger.exception('Ldap Exception caught')
log.doLog(userService, log.WARN, "Could not remove machine from domain ({})".format(e), log.OSMANAGER)
log.doLog(
userService,
log.WARN,
"Could not remove machine from domain ({})".format(e),
log.OSMANAGER,
)
return
except Exception as e:
# logger.exception('Exception caught')
log.doLog(userService, log.WARN, "Could not remove machine from domain ({})".format(e), log.OSMANAGER)
log.doLog(
userService,
log.WARN,
"Could not remove machine from domain ({})".format(e),
log.OSMANAGER,
)
return
try:
res = self.__getMachine(ldapConnection, userService.friendly_name)
if res is None:
raise Exception('Machine {} not found on AD (permissions?)'.format(userService.friendly_name))
raise Exception(
'Machine {} not found on AD (permissions?)'.format(
userService.friendly_name
)
)
ldaputil.recursive_delete(ldapConnection, res)
except IndexError:
logger.error('Error deleting %s from BASE %s', userService.friendly_name, self._ou)
logger.error(
'Error deleting %s from BASE %s', userService.friendly_name, self._ou
)
except Exception:
logger.exception('Deleting from AD: ')
@ -272,30 +405,36 @@ class WinDomainOsManager(WindowsOsManager):
except ldaputil.LDAPError as e:
return _('Check error: {}').format(e)
except dns.resolver.NXDOMAIN:
return _('Could not find server parameters (_ldap._tcp.{0} can\'t be resolved)').format(self._domain)
return _(
'Could not find server parameters (_ldap._tcp.{0} can\'t be resolved)'
).format(self._domain)
except Exception as e:
logger.exception('Exception ')
return str(e)
try:
ldapConnection.search_st(self._ou, ldap.SCOPE_BASE) # @UndefinedVariable
ldapConnection.search_st(self._ou, ldap.SCOPE_BASE) # type: ignore # (valid)
except ldaputil.LDAPError as e:
return _('Check error: {}').format(e)
# Group
if self._group != '':
if self.__getGroup(ldapConnection) is None:
return _('Check Error: group "{}" not found (using "cn" to locate it)').format(self._group)
return _(
'Check Error: group "{}" not found (using "cn" to locate it)'
).format(self._group)
return _('Server check was successful')
# pylint: disable=protected-access
@staticmethod
def test(env: 'Environment', data: typing.Dict[str, str]) -> typing.List[typing.Any]:
def test(
env: 'Environment', data: typing.Dict[str, str]
) -> typing.List[typing.Any]:
logger.debug('Test invoked')
wd = WinDomainOsManager(env, data)
logger.debug(wd)
try:
wd: WinDomainOsManager = WinDomainOsManager(env, data)
logger.debug(wd)
try:
ldapConnection = wd.__connectLdap()
except ldaputil.LDAPError as e:
@ -306,22 +445,34 @@ class WinDomainOsManager(WindowsOsManager):
ou = 'cn=Computers,dc=' + ',dc='.join(wd._domain.split('.'))
logger.info('Checking %s with ou %s', wd._domain, ou)
r = ldapConnection.search_st(ou, ldap.SCOPE_BASE) # @UndefinedVariable
r = ldapConnection.search_st(ou, ldap.SCOPE_BASE) # type: ignore # (valid)
logger.info('Result of search: %s', r)
except ldaputil.LDAPError:
if not wd._ou:
return [False, _('The default path {0} for computers was not found!!!').format(wd._ou)]
if wd and not wd._ou:
return [
False,
_('The default path {0} for computers was not found!!!').format(
wd._ou
),
]
return [False, _('The ou path {0} was not found!!!').format(wd._ou)]
except dns.resolver.NXDOMAIN:
return [True, _('Could not check parameters (_ldap._tcp.{0} can\'r be resolved)').format(wd._domain)]
return [
True,
_(
'Could not check parameters (_ldap._tcp.{0} can\'r be resolved)'
).format(wd._domain),
]
except Exception as e:
logger.exception('Exception ')
return [False, str(e)]
return [True, _("All parameters seem to work fine.")]
def actorData(self, userService: 'UserService') -> typing.MutableMapping[str, typing.Any]:
def actorData(
self, userService: 'UserService'
) -> typing.MutableMapping[str, typing.Any]:
return {
'action': 'rename_ad',
'name': userService.getName(),
@ -332,23 +483,42 @@ class WinDomainOsManager(WindowsOsManager):
}
def infoVal(self, userService: 'UserService') -> str:
return 'domain:{0}\t{1}\t{2}\t{3}\t{4}'.format(self.getName(userService), self._domain, self._ou, self._account, self._password)
return 'domain:{0}\t{1}\t{2}\t{3}\t{4}'.format(
self.getName(userService),
self._domain,
self._ou,
self._account,
self._password,
)
def infoValue(self, userService: 'UserService') -> str:
return 'domain\r{0}\t{1}\t{2}\t{3}\t{4}'.format(self.getName(userService), self._domain, self._ou, self._account, self._password)
return 'domain\r{0}\t{1}\t{2}\t{3}\t{4}'.format(
self.getName(userService),
self._domain,
self._ou,
self._account,
self._password,
)
def marshal(self) -> bytes:
'''
"""
Serializes the os manager data so we can store it in database
'''
base = typing.cast(str, encoders.encode(super().marshal(), 'hex', asText=True))
return '\t'.join([
'v4',
self._domain, self._ou, self._account,
cryptoManager().encrypt(self._password),
base,
self._group, self._serverHint, self._ssl, self._removeOnExit
]).encode('utf8')
"""
base = codecs.encode(super().marshal(), 'hex').decode()
return '\t'.join(
[
'v4',
self._domain,
self._ou,
self._account,
cryptoManager().encrypt(self._password),
base,
self._group,
self._serverHint,
self._ssl,
self._removeOnExit,
]
).encode('utf8')
def unmarshal(self, data: bytes) -> None:
values = data.decode('utf8').split('\t')
@ -374,8 +544,7 @@ class WinDomainOsManager(WindowsOsManager):
else:
self._ssl = 'n'
self._removeOnExit = 'y'
super().unmarshal(typing.cast(bytes, encoders.decode(values[5], 'hex')))
super().unmarshal(codecs.decode(values[5].encode(), 'hex'))
def valuesDict(self) -> gui.ValuesDictType:
dct = super().valuesDict()

View File

@ -31,6 +31,7 @@
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import codecs
import random
import string
import logging
@ -41,7 +42,6 @@ from uds.core.ui import gui
from uds.core.managers import cryptoManager
from uds.core import osmanagers
from uds.core.util import log
from uds.core.util import encoders
from .windows import WindowsOsManager
@ -120,7 +120,7 @@ class WinRandomPassManager(WindowsOsManager):
'''
Serializes the os manager data so we can store it in database
'''
base = typing.cast(str, encoders.encode(super().marshal(), 'hex', asText=True))
base = codecs.encode(super().marshal(), 'hex').decode()
return '\t'.join(['v1', self._userAccount, cryptoManager().encrypt(self._password), base]).encode('utf8')
def unmarshal(self, data: bytes) -> None:
@ -128,7 +128,7 @@ class WinRandomPassManager(WindowsOsManager):
if values[0] == 'v1':
self._userAccount = values[1]
self._password = cryptoManager().decrypt(values[2])
super().unmarshal(typing.cast(bytes, encoders.decode(values[3], 'hex')))
super().unmarshal(codecs.decode(values[3].encode(), 'hex'))
def valuesDict(self) -> gui.ValuesDictType:
dic = super().valuesDict()

View File

@ -30,6 +30,7 @@
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import traceback
import codecs
import logging
import typing
@ -39,7 +40,6 @@ from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from uds.core.util import encoders
from uds.models import ServicePool, Transport, UserService, Authenticator
# Not imported at runtime, just for type checking
@ -108,7 +108,7 @@ def errorView(request: 'HttpRequest', errorCode: int) -> HttpResponseRedirect:
if code != 0:
errStr += ' (code {0:04X})'.format(code)
errStr = encoders.encodeAsStr(str(errStr), 'base64').replace('\n', '')
errStr = codecs.encode(str(errStr), 'base64').decode().replace('\n', '')
logger.debug('Redirection to error view with %s', errStr)
return HttpResponseRedirect(reverse('page.error', kwargs={'err': errStr}))