upgraded os managers and typo fix

This commit is contained in:
Adolfo Gómez García 2019-08-20 11:04:25 +02:00
parent 2b733c0e9e
commit b233e26a52
19 changed files with 329 additions and 269 deletions

View File

@ -32,7 +32,7 @@
"""
import typing
# Not imported in runtime, just for type checking
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from .authenticator import Authenticator

View File

@ -269,7 +269,7 @@ class Module(UserInterface, Environmentable, Serializable):
Returns:
Internacionalized (using ugettext) string of result of the check.
"""
return _("No check method provided.")
return _("No check method provided.")
def getUuid(self) -> str:
return self._uuid

View File

@ -42,7 +42,7 @@ from uds.core.transports import protocols
from uds.core.util import encoders
from uds.core.util import connection
# Not imported in runtime, just for type checking
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from django.http import HttpRequest # pylint: disable=ungrouped-imports
from uds.core.environment import Environment

View File

@ -36,7 +36,7 @@ import typing
from uds.models import Permissions
from uds.core.util import ot
# Not imported in runtime, just for type checking
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.models import User, Group

View File

@ -40,7 +40,7 @@ from .util import NEVER
logger = logging.getLogger(__name__)
# Not imported in runtime, just for type checking
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.models import UserService

View File

@ -45,7 +45,7 @@ from .managed_object_model import ManagedObjectModel
from .tag import TaggingMixin
from .util import NEVER
# Not imported in runtime, just for type checking
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from .user import User
from django.db.models import QuerySet # pylint: disable=ungrouped-imports

View File

@ -44,7 +44,7 @@ from .authenticator import Authenticator
from .user import User
from .util import UnsavedForeignKey, getSqlDatetime
# Not imported in runtime, just for type checking
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.core import auths

View File

@ -39,7 +39,7 @@ from django.db.models import signals
from .managed_object_model import ManagedObjectModel
from .tag import TaggingMixin
# Not imported in runtime, just for type checking
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.core import osmanagers

View File

@ -40,7 +40,7 @@ from uds.core.util import log
from .managed_object_model import ManagedObjectModel
from .tag import TaggingMixin
# Not imported in runtime, just for type checking
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.core import services

View File

@ -46,7 +46,7 @@ from .tag import TaggingMixin
from .proxy import Proxy
from .provider import Provider
# Not imported in runtime, just for type checking
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.core import services

View File

@ -60,7 +60,7 @@ from .util import NEVER
from .util import getSqlDatetime
# Not imported in runtime, just for type checking
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.models import UserService, ServicePoolPublication, User

View File

@ -44,7 +44,7 @@ from .util import NEVER
from .util import getSqlDatetime
from .uuid_model import UUIDModel
# Not imported in runtime, just for type checking
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.core import auths
from uds.models import Group

View File

@ -48,7 +48,7 @@ from .user import User
from .util import NEVER
from .util import getSqlDatetime
# Not imported in runtime, just for type checking
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.core import services
from uds.models import OSManager, ServicePool, ServicePoolPublication

View File

@ -1,96 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
from __future__ import unicode_literals
from django.utils.translation import ugettext_noop as _
from uds.core.ui import gui
from uds.core.managers import cryptoManager
from uds.core import osmanagers
from .WindowsOsManager import WindowsOsManager
from uds.core.util import log
from uds.core.util import encoders
import logging
logger = logging.getLogger(__name__)
class WinRandomPassManager(WindowsOsManager):
typeName = _('Windows Random Password OS Manager')
typeType = 'WinRandomPasswordManager'
typeDescription = _('Os Manager to control windows machines, with user password set randomly.')
iconFile = 'wosmanager.png'
# Apart form data from windows os manager, we need also domain and credentials
userAccount = gui.TextField(length=64, label=_('Account'), order=2, tooltip=_('User account to change password'), required=True)
password = gui.PasswordField(length=64, label=_('Password'), order=3, tooltip=_('Current (template) password of the user account'), required=True)
# Inherits base "onLogout"
onLogout = WindowsOsManager.onLogout
idle = WindowsOsManager.idle
def __init__(self, environment, values):
super(WinRandomPassManager, self).__init__(environment, values)
if values is not None:
if values['userAccount'] == '':
raise osmanagers.OSManager.ValidationException(_('Must provide an user account!!!'))
if values['password'] == '':
raise osmanagers.OSManager.ValidationException(_('Must provide a password for the account!!!'))
self._userAccount = values['userAccount']
self._password = values['password']
else:
self._userAccount = ''
self._password = ""
def release(self, service):
super(WinRandomPassManager, self).release(service)
def processUserPassword(self, service, username, password):
if username == self._userAccount:
password = service.recoverValue('winOsRandomPass')
return WindowsOsManager.processUserPassword(self, service, username, password)
def genPassword(self, service):
import random
import string
randomPass = service.recoverValue('winOsRandomPass')
if randomPass is None:
randomPass = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(16))
service.storeValue('winOsRandomPass', randomPass)
log.doLog(service, log.INFO, "Password set to \"{}\"".format(randomPass), log.OSMANAGER)
return randomPass
def infoVal(self, service):
return 'rename:{0}\t{1}\t{2}\t{3}'.format(self.getName(service), self._userAccount, self._password, self.genPassword(service))
def infoValue(self, service):
return 'rename\r{0}\t{1}\t{2}\t{3}'.format(self.getName(service), self._userAccount, self._password, self.genPassword(service))
def marshal(self):
base = super(WinRandomPassManager, self).marshal()
'''
Serializes the os manager data so we can store it in database
'''
return '\t'.join(['v1', self._userAccount, cryptoManager().encrypt(self._password), encoders.encode(base, 'hex', asText=True)]).encode('utf8')
def unmarshal(self, s):
data = s.decode('utf8').split('\t')
if data[0] == 'v1':
self._userAccount = data[1]
self._password = cryptoManager().decrypt(data[2])
super(WinRandomPassManager, self).unmarshal(encoders.decode(data[3], 'hex'))
def valuesDict(self):
dic = super(WinRandomPassManager, self).valuesDict()
dic['userAccount'] = self._userAccount
dic['password'] = self._password
return dic

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 Virtual Cable S.L.
# Copyright (c) 2012-2019 Virtual Cable S.L.
# All rights reserved.
#
#
@ -31,23 +31,24 @@
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
from django.utils.translation import ugettext_noop as _
from uds.core.osmanagers.osmfactory import OSManagersFactory
from uds.core.managers import downloadsManager
from .WindowsOsManager import WindowsOsManager
from .WinDomainOsManager import WinDomainOsManager
from .WinRandomPassOsManager import WinRandomPassManager
from uds.core import VERSION
import os.path
import sys
OSManagersFactory.factory().insert(WindowsOsManager)
OSManagersFactory.factory().insert(WinDomainOsManager)
OSManagersFactory.factory().insert(WinRandomPassManager)
from django.utils.translation import ugettext_noop as _
from uds.core import osmanagers
from uds.core import managers
from uds.core import VERSION
downloadsManager().registerDownloadable('UDSActorSetup-{version}.exe'.format(version=VERSION),
_('UDS Actor for windows machines'),
os.path.dirname(sys.modules[__package__].__file__) + '/files/UDSActorSetup-{version}.exe'.format(version=VERSION),
'application/x-msdos-program')
from .windows import WindowsOsManager
from .windows_domain import WinDomainOsManager
from .windows_random import WinRandomPassManager
osmanagers.factory().insert(WindowsOsManager)
osmanagers.factory().insert(WinDomainOsManager)
osmanagers.factory().insert(WinRandomPassManager)
managers.downloadsManager().registerDownloadable(
'UDSActorSetup-{version}.exe'.format(version=VERSION),
_('UDS Actor for windows machines'),
os.path.dirname(sys.modules[__package__].__file__) + '/files/UDSActorSetup-{version}.exe'.format(version=VERSION),
'application/x-msdos-program')

View File

@ -12,34 +12,33 @@ import logging
import typing
from django.utils.translation import ugettext_noop as _, ugettext_lazy
from uds.core import osmanagers
from uds.core.services import types as serviceTypes
from uds.core.ui import gui
from uds.core import osmanagers
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
# Not imported in runtime, just for type checking
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.models import UserService
logger = logging.getLogger(__name__)
def scrambleMsg(data):
def scrambleMsg(msg: str) -> str:
"""
Simple scrambler so password are not seen at source page
"""
if isinstance(data, str):
data = data.encode('utf8')
res = []
data = msg.encode('utf8')
res = b''
n = 0x32
for c in data[::-1]:
res.append(chr(ord(c) ^ n))
n = (n + ord(c)) & 0xFF
return (b''.join(res).encode('hex')).decode('utf8')
res += bytes([c ^ n])
n = (n + c) & 0xFF
return typing.cast(str, encoders.encode(res, 'hex', asText=True))
class WindowsOsManager(osmanagers.OSManager):
@ -100,61 +99,61 @@ class WindowsOsManager(osmanagers.OSManager):
'''
Says if a machine is removable on logout
'''
if userService.in_use == False:
if not userService.in_use:
if (self._onLogout == 'remove') or (not userService.isValidPublication() and self._onLogout == 'keep'):
return True
return False
def release(self, service):
def release(self, userService: 'UserService') -> None:
pass
def getName(self, service):
def getName(self, userService: 'UserService') -> str:
"""
gets name from deployed
"""
return service.getName()
return userService.getName()
def infoVal(self, service):
return 'rename:' + self.getName(service)
def infoVal(self, userService: 'UserService') -> str:
return 'rename:' + self.getName(userService)
def infoValue(self, service):
return 'rename\r' + self.getName(service)
def infoValue(self, userService: 'UserService') -> str:
return 'rename\r' + self.getName(userService)
def notifyIp(self, uid, service, data):
si = service.getInstance()
def notifyIp(self, uid: str, userService, data: typing.Dict[str, typing.Any]) -> None:
userServiceInstance = userService.getInstance()
ip = ''
# Notifies IP to deployed
for p in data['ips']:
if p[0].lower() == uid.lower():
si.setIp(p[1])
userServiceInstance.setIp(p[1])
ip = p[1]
break
self.logKnownIp(service, ip)
service.updateData(si)
self.logKnownIp(userService, ip)
userService.updateData(userServiceInstance)
def doLog(self, service, data, origin=log.OSMANAGER):
def doLog(self, userService: 'UserService', data: str, origin=log.OSMANAGER):
# Stores a log associated with this service
try:
msg, level = data.split('\t')
msg, levelStr = data.split('\t')
try:
level = int(level)
level = int(levelStr)
except Exception:
logger.debug('Do not understand level {}'.format(level))
logger.debug('Do not understand level %s', level)
level = log.INFO
log.doLog(service, level, msg, origin)
log.doLog(userService, level, msg, origin)
except Exception:
logger.exception('WindowsOs Manager message log: ')
log.doLog(service, 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):
pass
def process(self, userService: 'UserService', msg, data, options=None):
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)
@ -163,29 +162,28 @@ 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 {0} with params: {1},{2}".format(userService, msg, data))
logger.info("Invoked WindowsOsManager for %s with params: %s,%s", userService, message, data)
if msg in ('ready', 'ip'):
if 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
}
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
}
# We get from storage the name for this userService. If no name, we try to assign a new one
ret = "ok"
notifyReady = False
doRemove = False
state = userService.os_state
if msg == "info":
if message == "info":
ret = self.infoVal(userService)
state = State.PREPARING
elif msg == "information":
elif message == "information":
ret = self.infoValue(userService)
state = State.PREPARING
elif msg == "log":
elif message == "log":
self.doLog(userService, data, log.ACTOR)
elif msg == "logon" or msg == 'login':
elif message in("logon", 'login'):
if '\\' not in data:
self.loggedIn(userService, data)
userService.setInUse(True)
@ -196,14 +194,14 @@ class WindowsOsManager(osmanagers.OSManager):
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 msg in ('logoff', 'logout'):
elif message in ('logoff', 'logout'):
self.loggedOut(userService, data)
doRemove = self.isRemovableOnLogout(userService)
elif msg == "ip":
elif message == "ip":
# This ocurss on main loop inside machine, so userService is usable
state = State.USABLE
self.notifyIp(userService.unique_id, userService, data)
elif msg == "ready":
elif message == "ready":
self.toReady(userService)
state = State.USABLE
notifyReady = True
@ -221,13 +219,13 @@ class WindowsOsManager(osmanagers.OSManager):
else:
logger.debug('Notifying ready')
userServiceManager().notifyReadyFromOsManager(userService, '')
logger.debug('Returning {} to {} message'.format(ret, msg))
logger.debug('Returning %s to %s message', ret, message)
if options is not None and options.get('scramble', True) is False:
return ret
return scrambleMsg(ret)
def processUserPassword(self, service, username, password):
if service.getProperty('sso_available') == '1':
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 = ''
if '@' in username:
@ -242,10 +240,10 @@ class WindowsOsManager(osmanagers.OSManager):
}
ticket = TicketStore.create(creds, validator=None, validity=300) # , owner=SECURE_OWNER, secure=True)
return ticket, ''
else:
return osmanagers.OSManager.processUserPassword(self, service, username, password)
def processUnused(self, userService):
return osmanagers.OSManager.processUserPassword(self, userService, username, password)
def processUnused(self, userService: 'UserService') -> None:
"""
This will be invoked for every assigned and unused user service that has been in this state at least 1/2 of Globalconfig.CHECK_UNUSED_TIME
This function can update userService values. Normal operation will be remove machines if this state is not valid
@ -256,8 +254,9 @@ class WindowsOsManager(osmanagers.OSManager):
def isPersistent(self):
return self._onLogout == 'keep-always'
def checkState(self, service):
logger.debug('Checking state for service {0}'.format(service))
def checkState(self, userService: 'UserService') -> str:
# will alway return true, because the check is done by an actor callback
logger.debug('Checking state for service %s', userService)
return State.RUNNING
def maxIdle(self):
@ -269,24 +268,24 @@ class WindowsOsManager(osmanagers.OSManager):
return self._idle
def marshal(self):
def marshal(self) -> bytes:
"""
Serializes the os manager data so we can store it in database
"""
return '\t'.join(['v2', self._onLogout, str(self._idle)]).encode('utf8')
def unmarshal(self, s):
data = s.decode('utf8').split('\t')
def unmarshal(self, data: bytes) -> None:
vals = data.decode('utf8').split('\t')
try:
if data[0] == 'v1':
self._onLogout = data[1]
if vals[0] == 'v1':
self._onLogout = vals[1]
self._idle = -1
elif data[0] == 'v2':
self._onLogout, self._idle = data[1], int(data[2])
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')
self.__setProcessUnusedMachines()
def valuesDict(self):
def valuesDict(self) -> gui.ValuesDictType:
return {'onLogout': self._onLogout, 'idle': self._idle}

View File

@ -1,14 +1,38 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 Virtual Cable S.L.
# Copyright (c) 2012-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 logging
import typing
import dns.resolver
import ldap
@ -21,7 +45,13 @@ from uds.core.util import log
from uds.core.util import encoders
from uds.core.util import ldaputil
from .WindowsOsManager import WindowsOsManager
from .windows import WindowsOsManager
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.core import Module
from uds.core.environment import Environment
from uds.models import UserService
logger = logging.getLogger(__name__)
@ -42,14 +72,23 @@ class WinDomainOsManager(WindowsOsManager):
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'))
# Inherits base "onLogout"
onLogout = WindowsOsManager.onLogout
idle = WindowsOsManager.idle
def __init__(self, environment, values):
super(WinDomainOsManager, self).__init__(environment, values)
if values is not None:
_domain: str
_ou: str
_account: str
_pasword: str
_group: str
_serverHint: str
_removeOnExit: str
_ssl: str
def __init__(self, environment: 'Environment', values: 'Module.ValuesType'):
super().__init__(environment, values)
if values:
if values['domain'] == '':
raise osmanagers.OSManager.ValidationException(_('Must provide a domain!'))
# if values['domain'].find('.') == -1:
@ -66,8 +105,8 @@ class WinDomainOsManager(WindowsOsManager):
self._password = values['password']
self._group = values['grp'].strip()
self._serverHint = values['serverHint'].strip()
self._ssl = 'y' if values['ssl'] else 'n'
self._removeOnExit = 'y' if values['removeOnExit'] else 'n'
self._ssl = 'y' if values['ssl'] else 'n'
self._removeOnExit = 'y' if values['removeOnExit'] else 'n'
else:
self._domain = ""
self._ou = ""
@ -84,14 +123,14 @@ class WinDomainOsManager(WindowsOsManager):
if self._ou.lower().find(lpath) == -1:
self._ou += ',' + lpath
def __getServerList(self):
def __getServerList(self) -> typing.Iterable[typing.Tuple[str, int]]:
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)):
yield (str(server.target)[:-1], server.port)
def __connectLdap(self, servers=None):
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:
@ -108,8 +147,8 @@ class WinDomainOsManager(WindowsOsManager):
_str = "No servers found"
# And if not possible, try using NON-SSL
for server in servers:
port = server[1] if self._ssl != 'y' else -1
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)
except Exception as e:
@ -117,11 +156,11 @@ class WinDomainOsManager(WindowsOsManager):
raise ldaputil.LDAPError(_str)
def __getGroup(self, l):
def __getGroup(self, ldapConnection: typing.Any) -> str:
base = ','.join(['DC=' + i for i in self._domain.split('.')])
group = ldaputil.escape(self._group)
try:
obj = next(ldaputil.getAsDict(l, 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
@ -130,7 +169,7 @@ class WinDomainOsManager(WindowsOsManager):
return obj['dn'] # Returns the DN
def __getMachine(self, l, machineName):
def __getMachine(self, ldapConnection, machineName: str) -> typing.Optional[str]:
# if self._ou:
# base = self._ou
# else:
@ -138,7 +177,7 @@ class WinDomainOsManager(WindowsOsManager):
fltr = '(&(objectClass=computer)(sAMAccountName={}$))'.format(ldaputil.escape(machineName))
try:
obj = next(ldaputil.getAsDict(l, base, fltr, ['dn'], sizeLimit=50))
obj = next(ldaputil.getAsDict(ldapConnection, base, fltr, ['dn'], sizeLimit=50))
except StopIteration:
obj = None
@ -147,29 +186,27 @@ class WinDomainOsManager(WindowsOsManager):
return obj['dn'] # Returns the DN
def readyReceived(self, userService, data):
def readyReceived(self, userService: 'UserService', data: str) -> None:
# No group to add
if self._group == '':
return
if not '.' in self._domain:
if '.' not in self._domain:
logger.info('Adding to a group for a non FQDN domain is not supported')
return
# The machine is on a AD for sure, and maybe they are not already sync
servers = list(self.__getServerList())
error = None
for s in servers:
error: typing.Optional[str] = None
for s in self.__getServerList():
try:
l = self.__connectLdap(servers=(s,))
ldapConnection = self.__connectLdap(servers=(s,))
machine = self.__getMachine(l, userService.friendly_name)
group = self.__getGroup(l)
machine = self.__getMachine(ldapConnection, userService.friendly_name)
group = self.__getGroup(ldapConnection)
# #
# Direct LDAP operation "modify", maybe this need to be added to ldaputil? :)
# #
l.modify_s(group, ((ldap.MOD_ADD, 'member', machine),)) # @UndefinedVariable
ldapConnection.modify_s(group, ((ldap.MOD_ADD, 'member', machine),)) # @UndefinedVariable
error = None
break
except dns.resolver.NXDOMAIN: # No domain found, log it and pass
@ -186,16 +223,13 @@ class WinDomainOsManager(WindowsOsManager):
error = "Could not add machine {} to group {}: {}".format(userService.friendly_name, self._group, e)
# logger.exception('Ldap Exception caught')
if error is not None:
if error:
log.doLog(userService, log.WARN, error, log.OSMANAGER)
logger.error(error)
def release(self, service):
"""
service is a db user service object
"""
super(WinDomainOsManager, self).release(service)
def release(self, userService: 'UserSrevice') -> None:
super().release(userService)
# If no removal requested, just return
if self._removeOnExit != 'y':
return
@ -205,62 +239,62 @@ class WinDomainOsManager(WindowsOsManager):
return
try:
l = self.__connectLdap()
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(service, log.WARN, "Could not remove machine from domain (_ldap._tcp.{0} not found)".format(self._domain), log.OSMANAGER)
log.doLog(userService, log.WARN, "Could not remove machine from domain (_ldap._tcp.{0} not found)".format(self._domain), log.OSMANAGER)
return
except ldaputil.LDAPError:
logger.exception('Ldap Exception caught')
log.doLog(service, log.WARN, "Could not remove machine from domain (invalid credentials for {0})".format(self._account), log.OSMANAGER)
log.doLog(userService, log.WARN, "Could not remove machine from domain (invalid credentials for {0})".format(self._account), log.OSMANAGER)
return
except Exception:
logger.exception('Exception caught')
return
try:
res = self.__getMachine(l, service.friendly_name)
res = self.__getMachine(ldapConnection, userService.friendly_name)
if res is None:
raise Exception('Machine {} not found on AD (permissions?)'.format(service.friendly_name))
ldaputil.recursive_delete(l, res)
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', service.friendly_name, self._ou)
logger.error('Error deleting %s from BASE %s', userService.friendly_name, self._ou)
except Exception:
logger.exception('Deleting from AD: ')
def check(self):
def check(self) -> str:
try:
l = self.__connectLdap()
ldapConnection = self.__connectLdap()
except ldaputil.LDAPError as e:
return _('Check error: {0}').format(self.__getLdapError(e))
return _('Check error: {}').format(self.__getLdapError(e))
except dns.resolver.NXDOMAIN:
return [True, _('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 [False, str(e)]
return str(e)
try:
l.search_st(self._ou, ldap.SCOPE_BASE) # @UndefinedVariable
ldapConnection.search_st(self._ou, ldap.SCOPE_BASE) # @UndefinedVariable
except ldaputil.LDAPError as e:
return _('Check error: {0}').format(self.__getLdapError(e))
return _('Check error: {}').format(self.__getLdapError(e))
# Group
if self._group != '':
if self.__getGroup(l) is None:
if self.__getGroup(ldapConnection) is None:
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, data):
def test(env: 'Environment', data: typing.Dict[str, str]) -> typing.List[typing.Any]:
logger.debug('Test invoked')
wd = None
wd: typing.Optional[WinDomainOsManager] = None
try:
wd = WinDomainOsManager(env, data)
logger.debug(wd)
try:
l = wd.__connectLdap()
ldapConnection = wd.__connectLdap()
except ldaputil.LDAPError as e:
return [False, _('Could not access AD using LDAP ({0})').format(wd.__getLdapError(e))]
@ -268,15 +302,14 @@ class WinDomainOsManager(WindowsOsManager):
if ou == '':
ou = 'cn=Computers,dc=' + ',dc='.join(wd._domain.split('.'))
logger.debug('Checking {0} with ou {1}'.format(wd._domain, ou))
r = l.search_st(ou, ldap.SCOPE_BASE) # @UndefinedVariable
logger.debug('Result of search: {0}'.format(r))
logger.info('Checking %s with ou %s', wd._domain, ou)
r = ldapConnection.search_st(ou, ldap.SCOPE_BASE) # @UndefinedVariable
logger.info('Result of search: %s', r)
except ldaputil.LDAPError:
if wd._ou == '':
if not wd._ou:
return [False, _('The default path {0} for computers was not found!!!').format(wd._ou)]
else:
return [False, _('The ou path {0} 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)]
except Exception as e:
@ -285,54 +318,54 @@ class WinDomainOsManager(WindowsOsManager):
return [True, _("All parameters seem to work fine.")]
def infoVal(self, service):
return 'domain:{0}\t{1}\t{2}\t{3}\t{4}'.format(self.getName(service), self._domain, self._ou, self._account, self._password)
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)
def infoValue(self, service):
return 'domain\r{0}\t{1}\t{2}\t{3}\t{4}'.format(self.getName(service), 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)
def marshal(self):
base = super(WinDomainOsManager, self).marshal()
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),
encoders.encode(base, 'hex', asText=True),
self._group, self._serverHint, self._ssl, self._removeOnExit]
).encode('utf8')
base,
self._group, self._serverHint, self._ssl, self._removeOnExit
]).encode('utf8')
def unmarshal(self, s):
data = s.decode('utf8').split('\t')
if data[0] in ('v1', 'v2', 'v3', 'v4'):
self._domain = data[1]
self._ou = data[2]
self._account = data[3]
self._password = cryptoManager().decrypt(data[4])
def unmarshal(self, data: bytes) -> None:
values = data.decode('utf8').split('\t')
if values[0] in ('v1', 'v2', 'v3', 'v4'):
self._domain = values[1]
self._ou = values[2]
self._account = values[3]
self._password = cryptoManager().decrypt(values[4])
if data[0] in ('v2', 'v3', 'v4'):
self._group = data[6]
if values[0] in ('v2', 'v3', 'v4'):
self._group = values[6]
else:
self._group = ''
if data[0] in ('v3', 'v4'):
self._serverHint = data[7]
if values[0] in ('v3', 'v4'):
self._serverHint = values[7]
else:
self._serverHint = ''
if data[0] == 'v4':
self._ssl = data[8]
self._removeOnExit = data[9]
if values[0] == 'v4':
self._ssl = values[8]
self._removeOnExit = values[9]
else:
self._ssl = 'n'
self._removeOnExit = 'y'
super(WinDomainOsManager, self).unmarshal(encoders.decode(data[5], 'hex'))
super().unmarshal(typing.cast(bytes, encoders.decode(values[5], 'hex')))
def valuesDict(self):
dct = super(WinDomainOsManager, self).valuesDict()
def valuesDict(self) -> gui.ValuesDictType:
dct = super().valuesDict()
dct['domain'] = self._domain
dct['ou'] = self._ou
dct['account'] = self._account

View File

@ -0,0 +1,123 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-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 random
import string
import logging
import typing
from django.utils.translation import ugettext_noop as _
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
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.core import Module
from uds.core.environment import Environment
from uds.models import UserService
logger = logging.getLogger(__name__)
class WinRandomPassManager(WindowsOsManager):
typeName = _('Windows Random Password OS Manager')
typeType = 'WinRandomPasswordManager'
typeDescription = _('Os Manager to control windows machines, with user password set randomly.')
iconFile = 'wosmanager.png'
# Apart form data from windows os manager, we need also domain and credentials
userAccount = gui.TextField(length=64, label=_('Account'), order=2, tooltip=_('User account to change password'), required=True)
password = gui.PasswordField(length=64, label=_('Password'), order=3, tooltip=_('Current (template) password of the user account'), required=True)
# Inherits base "onLogout"
onLogout = WindowsOsManager.onLogout
idle = WindowsOsManager.idle
def __init__(self, environment: 'Environment', values: 'Module.ValuesType'):
super().__init__(environment, values)
if values:
if values['userAccount'] == '':
raise osmanagers.OSManager.ValidationException(_('Must provide an user account!!!'))
if values['password'] == '':
raise osmanagers.OSManager.ValidationException(_('Must provide a password for the account!!!'))
self._userAccount = values['userAccount']
self._password = values['password']
else:
self._userAccount = ''
self._password = ""
def processUserPassword(self, userService: 'UserService', username: str, password: str) -> typing.Tuple[str, str]:
if username == self._userAccount:
password = userService.recoverValue('winOsRandomPass')
return WindowsOsManager.processUserPassword(self, userService, username, password)
def genPassword(self, userService: 'UserService'):
randomPass = userService.recoverValue('winOsRandomPass')
if not randomPass:
randomPass = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(16))
userService.storeValue('winOsRandomPass', randomPass)
log.doLog(userService, log.INFO, "Password set to \"{}\"".format(randomPass), log.OSMANAGER)
return randomPass
def infoVal(self, userService: 'UserService') -> str:
return 'rename:{0}\t{1}\t{2}\t{3}'.format(self.getName(userService), self._userAccount, self._password, self.genPassword(userService))
def infoValue(self, userService: 'UserService') -> str:
return 'rename\r{0}\t{1}\t{2}\t{3}'.format(self.getName(userService), self._userAccount, self._password, self.genPassword(userService))
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(['v1', self._userAccount, cryptoManager().encrypt(self._password), base]).encode('utf8')
def unmarshal(self, data: bytes) -> None:
values = data.decode('utf8').split('\t')
if values[0] == 'v1':
self._userAccount = values[1]
self._password = cryptoManager().decrypt(values[2])
super().unmarshal(typing.cast(bytes, encoders.decode(values[3], 'hex')))
def valuesDict(self) -> gui.ValuesDictType:
dic = super().valuesDict()
dic['userAccount'] = self._userAccount
dic['password'] = self._password
return dic

View File

@ -41,7 +41,7 @@ from uds.core.util import html
from uds.core.managers import userServiceManager
# Not imported in runtime, just for type checking
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from django.http import HttpRequest # pylint: disable=ungrouped-imports