From 710b56d4e12f905b77e3388bc3d6f4699c8643d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez?= Date: Mon, 26 Nov 2012 13:39:22 +0000 Subject: [PATCH] * Added support for OS Managers to change services passwords. * oVirt should be usable right now, testing and fixing minor bugs * Added support for Windows Support with password changing of user to new one * Minor improvements (to code) --- .../org.eclipse.core.resources.prefs | 1 + .../src/uds/core/osmanagers/BaseOsManager.py | 17 ++++ .../src/uds/core/transports/BaseTransport.py | 3 +- server/src/uds/models.py | 53 ++++++++++- .../LinuxOsManager/LinuxOsManager.py | 16 ++-- .../WindowsOsManager/WinDomainOsManager.py | 3 + .../WinRandomPassOsManager.py | 90 +++++++++++++++++++ .../WindowsOsManager/WindowsOsManager.py | 14 +-- .../osmanagers/WindowsOsManager/__init__.py | 6 +- .../services/OVirt/OVirtLinkedDeployment.py | 22 +++-- .../uds/services/OVirt/OVirtLinkedService.py | 1 + .../uds/services/OVirt/client/oVirtClient.py | 2 + server/src/uds/transports/NX/NXTransport.py | 6 +- server/src/uds/transports/RDP/RDPTransport.py | 5 +- .../src/uds/transports/RDP/TSRDPTransport.py | 5 +- .../src/uds/transports/TSNX/TSNXTransport.py | 4 +- server/src/uds/web/views.py | 2 +- 17 files changed, 224 insertions(+), 26 deletions(-) create mode 100644 server/src/uds/osmanagers/WindowsOsManager/WinRandomPassOsManager.py diff --git a/server/.settings/org.eclipse.core.resources.prefs b/server/.settings/org.eclipse.core.resources.prefs index 7fa0d364f..e125f364f 100644 --- a/server/.settings/org.eclipse.core.resources.prefs +++ b/server/.settings/org.eclipse.core.resources.prefs @@ -105,6 +105,7 @@ encoding//src/uds/models.py=utf-8 encoding//src/uds/osmanagers/LinuxOsManager/LinuxOsManager.py=utf-8 encoding//src/uds/osmanagers/LinuxOsManager/__init__.py=utf-8 encoding//src/uds/osmanagers/WindowsOsManager/WinDomainOsManager.py=utf-8 +encoding//src/uds/osmanagers/WindowsOsManager/WinRandomPassOsManager.py=utf-8 encoding//src/uds/osmanagers/WindowsOsManager/WindowsOsManager.py=utf-8 encoding//src/uds/osmanagers/WindowsOsManager/__init__.py=utf-8 encoding//src/uds/osmanagers/__init__.py=utf-8 diff --git a/server/src/uds/core/osmanagers/BaseOsManager.py b/server/src/uds/core/osmanagers/BaseOsManager.py index b196d74a5..cca8f16d8 100644 --- a/server/src/uds/core/osmanagers/BaseOsManager.py +++ b/server/src/uds/core/osmanagers/BaseOsManager.py @@ -116,6 +116,23 @@ class OSManager(Module): ''' pass + def processUserPassword(self, service, username, password): + ''' + This will be invoked prior to passsing username/password to Transport. + + This method allows us to "change" username and/or password "on the fly". + One example of use of this is an OS Manager that creates a random password for an user. + In that case, this method, if the username passed in is the same as the os manager changes the password for, return the changed password. + + MUST Return: + An array with 2 elements, [newUserName, newPassword]. + Default method simplt does nothing with in parameters, just returns it. (So, if your os manager does not need this, + simply do not implement it) + + Note: This method is, right now, invoked by Transports directly. So if you implement a Transport, remember to invoke this + ''' + return [username, password] + def destroy(self): ''' Invoked when OS Manager is deleted diff --git a/server/src/uds/core/transports/BaseTransport.py b/server/src/uds/core/transports/BaseTransport.py index 0fc4ba7d1..d4c3d9ad4 100644 --- a/server/src/uds/core/transports/BaseTransport.py +++ b/server/src/uds/core/transports/BaseTransport.py @@ -101,9 +101,10 @@ class Transport(Module): ''' return cls.supportedOss.count(osName) > 0 - def renderForHtml(self, id, ip, os, user, password): + def renderForHtml(self, userService, id, ip, os, user, password): ''' Requests the html rendering of connector for the destination ip, (dbUser) and password + @param: userService: DeployedUserService for witch we are rendering the connection (db model) @param id: id of the transport @param ip: ip of the destination @param user: user (dbUser) logged in diff --git a/server/src/uds/models.py b/server/src/uds/models.py index 05f6d1bfc..5f1439e45 100644 --- a/server/src/uds/models.py +++ b/server/src/uds/models.py @@ -1073,7 +1073,7 @@ class DeployedServicePublication(models.Model): if self.data != '' and self.data is not None: dpl.unserialize(self.data) return dpl - + def updateData(self, dsp): ''' Updates the data field with the serialized uds.core.services.Publication @@ -1236,6 +1236,57 @@ class UserService(models.Model): :note: This method do not saves the updated record, just updates the field ''' self.data = us.serialize() + + def getName(self): + ''' + Returns the name of the user deployed service + ''' + if self.friendly_name == '': + si = self.getInstance() + self.friendly_name = si.getName() + self.updateData(si) + + return self.friendly_name + + def getUniqueId(self): + ''' + Returns the unique id of the user deployed service + ''' + if self.unique_id == '': + si = self.getInstance() + self.unique_id = si.getUniqueId() + self.updateData(si) + return self.unique_id + + def storeValue(self, name, value): + ''' + Stores a value inside custom storage + + Args: + name: Name of the value to store + value: Value of the value to store + ''' + self.getEnvironment().storage().put(name, value) + + def recoverValue(self, name): + ''' + Recovers a value from custom storage + + Args: + name: Name of values to recover + + Returns: + Stored value, None if no value was stored + ''' + return self.getEnvironment().storage().get(name) + + def processUserPassword(self, username, password): + ds = self.deployed_service + serviceInstance = ds.service.getInstance() + if serviceInstance.needsManager is False: + return [username, password] + + return ds.osmanager.getInstance().processUserPassword(self, username, password) def setState(self, state): ''' diff --git a/server/src/uds/osmanagers/LinuxOsManager/LinuxOsManager.py b/server/src/uds/osmanagers/LinuxOsManager/LinuxOsManager.py index 6a125c1c3..d914a02f1 100644 --- a/server/src/uds/osmanagers/LinuxOsManager/LinuxOsManager.py +++ b/server/src/uds/osmanagers/LinuxOsManager/LinuxOsManager.py @@ -67,13 +67,13 @@ class LinuxOsManager(osmanagers.OSManager): ''' gets name from deployed ''' - si = service.getInstance() - name = si.getName() - service.updateData(si) - return name + return service.getName() def infoVal(self,service): return 'rename:' + self.getName(service) + + def infoValue(self,service): + return 'rename\r' + self.getName(service) def notifyIp(self, uid, si, data): # Notifies IP to deployed @@ -87,7 +87,8 @@ class LinuxOsManager(osmanagers.OSManager): def process(self,service,msg, data): ''' We understand this messages: - * msg = info, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class) + * msg = info, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class), old method + * msg = information, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class), new method * msg = logon, data = Username, Informs that the username has logged in inside the machine * msg = logoff, data = Username, Informs that the username has logged out of the machine * msg = ready, data = None, Informs machine ready to be used @@ -99,9 +100,14 @@ class LinuxOsManager(osmanagers.OSManager): notifyReady = False doRemove = False state = service.os_state + + # Old "info" state, will be removed in a near future if msg == "info": ret = self.infoVal(service) state = State.PREPARING + elif msg == "information": + ret = self.infoValue(service) + state = State.PREPARING elif msg == "login": si = service.getInstance() si.userLoggedIn(data) diff --git a/server/src/uds/osmanagers/WindowsOsManager/WinDomainOsManager.py b/server/src/uds/osmanagers/WindowsOsManager/WinDomainOsManager.py index d74b01d04..82b4d1eb2 100644 --- a/server/src/uds/osmanagers/WindowsOsManager/WinDomainOsManager.py +++ b/server/src/uds/osmanagers/WindowsOsManager/WinDomainOsManager.py @@ -56,6 +56,9 @@ class WinDomainOsManager(WindowsOsManager): super(WinDomainOsManager,self).release(service) # TODO: remove machine from active directory os, under ou or default location if not specified + 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, service): return 'domain:{0}\t{1}\t{2}\t{3}\t{4}'.format( self.getName(service), self._domain, self._ou, self._account, self._password) diff --git a/server/src/uds/osmanagers/WindowsOsManager/WinRandomPassOsManager.py b/server/src/uds/osmanagers/WindowsOsManager/WinRandomPassOsManager.py new file mode 100644 index 000000000..90cc3a870 --- /dev/null +++ b/server/src/uds/osmanagers/WindowsOsManager/WinRandomPassOsManager.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +# +# Copyright (c) 2012 Virtual Cable S.L. +# All rights reserved. +# + +''' +@author: Adolfo Gómez, dkmaster at dkmon dot com +''' + +from django.utils.translation import ugettext_noop as _ +from uds.core.ui.UserInterface import gui +from uds.core.managers.CryptoManager import CryptoManager +from uds.core import osmanagers +from WindowsOsManager import WindowsOsManager, scrambleMsg + +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 + + def __init__(self,environment, values): + super(WinRandomPassManager, self).__init__(environment, values) + if values != 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: + return [username, service.recoverValue('winOsRandomPass')] + return [username, password] + + def genPassword(self, service): + import random + import string + randomPass = service.recoverValue('winOsRandomPass') + if randomPass is None: + randomPass = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(16)) + service.storeValue('winOsRandomPass', randomPass) + 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 str.join( '\t', [ 'v1', self._userAccount, CryptoManager.manager().encrypt(self._password), base.encode('hex') ] ) + + def unmarshal(self, s): + data = s.split('\t') + if data[0] == 'v1': + self._userAccount = data[1] + self._password = CryptoManager.manager().decrypt(data[2]) + super(WinRandomPassManager, self).unmarshal(data[3].decode('hex')) + + def valuesDict(self): + dic = super(WinRandomPassManager,self).valuesDict() + dic['userAccount'] = self._userAccount + dic['password'] = self._password + return dic + diff --git a/server/src/uds/osmanagers/WindowsOsManager/WindowsOsManager.py b/server/src/uds/osmanagers/WindowsOsManager/WindowsOsManager.py index 0127ab652..20c46a401 100644 --- a/server/src/uds/osmanagers/WindowsOsManager/WindowsOsManager.py +++ b/server/src/uds/osmanagers/WindowsOsManager/WindowsOsManager.py @@ -68,13 +68,13 @@ class WindowsOsManager(osmanagers.OSManager): ''' gets name from deployed ''' - si = service.getInstance() - name = si.getName() - service.updateData(si) - return name + return service.getName() def infoVal(self,service): return 'rename:' + self.getName(service) + + def infoValue(self,service): + return 'rename\r' + self.getName(service) def notifyIp(self, uid, si, data): # Notifies IP to deployed @@ -88,7 +88,8 @@ class WindowsOsManager(osmanagers.OSManager): def process(self,service,msg, data): ''' We understand this messages: - * msg = info, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class) + * msg = info, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class) (old method) + * msg = information, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class) (new method) * msg = logon, data = Username, Informs that the username has logged in inside the machine * msg = logoff, data = Username, Informs that the username has logged out of the machine * msg = ready, data = None, Informs machine ready to be used @@ -103,6 +104,9 @@ class WindowsOsManager(osmanagers.OSManager): if msg == "info": ret = self.infoVal(service) state = State.PREPARING + elif msg == "information": + ret = self.infoValue(service) + state = State.PREPARING elif msg == "logon": si = service.getInstance() si.userLoggedIn(data) diff --git a/server/src/uds/osmanagers/WindowsOsManager/__init__.py b/server/src/uds/osmanagers/WindowsOsManager/__init__.py index b3b6bc0db..c5d475ad6 100644 --- a/server/src/uds/osmanagers/WindowsOsManager/__init__.py +++ b/server/src/uds/osmanagers/WindowsOsManager/__init__.py @@ -12,12 +12,14 @@ from django.utils.translation import ugettext_noop as _ from uds.core.osmanagers.OSManagersFactory import OSManagersFactory from uds.core.managers.DownloadsManager import DownloadsManager -from uds.osmanagers.WindowsOsManager.WindowsOsManager import WindowsOsManager -from uds.osmanagers.WindowsOsManager.WinDomainOsManager import WinDomainOsManager +from WindowsOsManager import WindowsOsManager +from WinDomainOsManager import WinDomainOsManager +from WinRandomPassOsManager import WinRandomPassManager import os.path, sys OSManagersFactory.factory().insert(WindowsOsManager) OSManagersFactory.factory().insert(WinDomainOsManager) +OSManagersFactory.factory().insert(WinRandomPassManager) DownloadsManager.manager().registerDownloadable('UDSActorSetup.exe', _('UDS Actor for windows machines (Important!! Requires .net framework 3.5 sp1)'), diff --git a/server/src/uds/services/OVirt/OVirtLinkedDeployment.py b/server/src/uds/services/OVirt/OVirtLinkedDeployment.py index e571e757a..d5c465507 100644 --- a/server/src/uds/services/OVirt/OVirtLinkedDeployment.py +++ b/server/src/uds/services/OVirt/OVirtLinkedDeployment.py @@ -215,10 +215,17 @@ class OVirtLinkedDeployment(UserDeployment): if state == 'unknown' and chkState != 'unknown': return self.__error('Machine not found') - if state != chkState: - return State.RUNNING + ret = State.RUNNING + if type(chkState) is list: + for cks in chkState: + if state == cks: + ret = State.FINISHED + break + else: + if state == chkState: + ret = State.FINISHED - return State.FINISHED + return ret def __getCurrentOp(self): if len(self._queue) == 0: @@ -306,8 +313,8 @@ class OVirtLinkedDeployment(UserDeployment): Deploys a machine from template for user/cache ''' templateId = self.publication().getTemplateId() - name = self.service().sanitizeVmName('UDS service ' + self.getName()) - comments = 'UDS Linked clone for' + name = self.service().sanitizeVmName(self.getName()) # oVirt don't let us to create machines with more than 15 chars!!! + comments = 'UDS Linked clone' self._vmid = self.service().deployFromTemplate(name, comments, templateId) if self._vmid is None: @@ -322,8 +329,9 @@ class OVirtLinkedDeployment(UserDeployment): if state == 'unknown': raise Exception('Machine not found') - if state != 'down' and state != 'suspended': + if state != 'down': self.__pushFrontOp(opStop) + self.__executeQueue() else: self.service().removeMachine(self._vmid) @@ -356,7 +364,7 @@ class OVirtLinkedDeployment(UserDeployment): if state == 'down': # Already stoped, return return - if state != 'up': + if state != 'up' and state != 'suspended': self.__pushBackOp(opRetry) # Will call "check Retry", that will finish inmediatly and again call this one else: self.service().stopMachine(self._vmid) diff --git a/server/src/uds/services/OVirt/OVirtLinkedService.py b/server/src/uds/services/OVirt/OVirtLinkedService.py index 2ccb0e52a..079cea18d 100644 --- a/server/src/uds/services/OVirt/OVirtLinkedService.py +++ b/server/src/uds/services/OVirt/OVirtLinkedService.py @@ -204,6 +204,7 @@ class OVirtLinkedService(Service): Returns: Id of the machine being created form template ''' + logger.debug('Deploying from template {0} machine {1}'.format(templateId, name)) return self.parent().deployFromTemplate(name, comments, templateId, self.cluster.value, self.display.value) def removeTemplate(self, templateId): diff --git a/server/src/uds/services/OVirt/client/oVirtClient.py b/server/src/uds/services/OVirt/client/oVirtClient.py index c8e6b499a..fec3cc450 100644 --- a/server/src/uds/services/OVirt/client/oVirtClient.py +++ b/server/src/uds/services/OVirt/client/oVirtClient.py @@ -421,6 +421,8 @@ class Client(object): api = self.__getApi() + logger.debug('Deploying machine {0}'.format(name)) + cluster = params.Cluster(id=clusterId) template = params.Template(id=templateId) display = params.Display(type_=displayType) diff --git a/server/src/uds/transports/NX/NXTransport.py b/server/src/uds/transports/NX/NXTransport.py index 0a0320c06..06cd63238 100644 --- a/server/src/uds/transports/NX/NXTransport.py +++ b/server/src/uds/transports/NX/NXTransport.py @@ -148,7 +148,7 @@ class NXTransport(Transport): self.cache().put(ip, 'N', READY_CACHE_TIMEOUT) return ready == 'Y' - def renderForHtml(self, theId, ip, os, user, password): + def renderForHtml(self, userService, theId, ip, os, user, password): prefs = user.prefs('nx') @@ -162,6 +162,8 @@ class NXTransport(Transport): if self._useEmptyCreds is True: username, password = '','' + # We have the credentials right now, let os manager + width, height = CommonPrefs.getWidthHeight(prefs) # Extra data @@ -170,6 +172,8 @@ class NXTransport(Transport): 'session' : self._session, 'cacheDisk': self._cacheDisk, 'cacheMem' : self._cacheMem } + # Fix username/password acording to os manager + username, password = userService.processUserPassword(username, password) return generateHtmlForNX(self, theId, ip, username, password, extra) diff --git a/server/src/uds/transports/RDP/RDPTransport.py b/server/src/uds/transports/RDP/RDPTransport.py index 3e1847097..7310661bc 100644 --- a/server/src/uds/transports/RDP/RDPTransport.py +++ b/server/src/uds/transports/RDP/RDPTransport.py @@ -105,7 +105,7 @@ class RDPTransport(Transport): self.cache().put(ip, 'N', READY_CACHE_TIMEOUT) return ready == 'Y' - def renderForHtml(self, id, ip, os, user, password): + def renderForHtml(self, userService, id, ip, os, user, password): # We use helper to keep this clean username = user.getUsernameForAuth() prefs = user.prefs('rdp') @@ -133,6 +133,9 @@ class RDPTransport(Transport): 'printers' : self._allowPrinters, 'smartcards' : self._allowSmartcards, 'drives' : self._allowDrives, 'serials' : self._allowSerials, 'compression':True } + # Fix username/password acording to os manager + username, password = userService.processUserPassword(username, password) + return generateHtmlForRdp(self, id, os, ip, '3389', username, password, domain, extra) def getHtmlComponent(self, id, os, componentId): diff --git a/server/src/uds/transports/RDP/TSRDPTransport.py b/server/src/uds/transports/RDP/TSRDPTransport.py index dc4ca8f15..8b743f1da 100644 --- a/server/src/uds/transports/RDP/TSRDPTransport.py +++ b/server/src/uds/transports/RDP/TSRDPTransport.py @@ -116,7 +116,7 @@ class TSRDPTransport(Transport): self.cache().put(ip, 'N', READY_CACHE_TIMEOUT) return ready == 'Y' - def renderForHtml(self, id, ip, os, user, password): + def renderForHtml(self, userService, id, ip, os, user, password): # We use helper to keep this clean username = user.getUsernameForAuth() prefs = user.prefs('rdp') @@ -156,6 +156,9 @@ class TSRDPTransport(Transport): 'drives' : self._allowDrives, 'serials' : self._allowSerials, 'tun': tun, 'compression':True } + # Fix username/password acording to os manager + username, password = userService.processUserPassword(username, password) + return generateHtmlForRdp(self, id, os, ip, '-1', username, password, domain, extra) def getHtmlComponent(self, id, os, componentId): diff --git a/server/src/uds/transports/TSNX/TSNXTransport.py b/server/src/uds/transports/TSNX/TSNXTransport.py index 7c6bd103b..ecfc38e43 100644 --- a/server/src/uds/transports/TSNX/TSNXTransport.py +++ b/server/src/uds/transports/TSNX/TSNXTransport.py @@ -158,7 +158,7 @@ class TSNXTransport(Transport): self.cache().put(ip, 'N', READY_CACHE_TIMEOUT) return ready == 'Y' - def renderForHtml(self, theId, ip, os, user, password): + def renderForHtml(self, userService, theId, ip, os, user, password): prefs = user.prefs('nx') @@ -191,6 +191,8 @@ class TSNXTransport(Transport): 'session' : self._session, 'cacheDisk': self._cacheDisk, 'cacheMem' : self._cacheMem, 'tun' : tun } + # Fix username/password acording to os manager + username, password = userService.processUserPassword(username, password) return generateHtmlForNX(self, theId, username, password, extra) diff --git a/server/src/uds/web/views.py b/server/src/uds/web/views.py index b8fc340f2..8ef20f419 100644 --- a/server/src/uds/web/views.py +++ b/server/src/uds/web/views.py @@ -222,7 +222,7 @@ def service(request, idService, idTransport): if ip is not None: itrans = trans.getInstance() if itrans.isAvailableFor(ip): - transport = itrans.renderForHtml(scrambleId(request, trans.id), ip, request.session['OS'], request.user, webPassword(request)) + transport = itrans.renderForHtml(ads, scrambleId(request, trans.id), ip, request.session['OS'], request.user, webPassword(request)) return render_to_response('uds/show_transport.html', {'transport' : transport, 'nolang' : True }, context_instance=RequestContext(request)) else: logger.debug('Transport is not ready for user service {0}'.format(ads))