Now correctly adds user to Remote Desktop Group before connection, so

access is granted for user (Note: user must exist in machine/domain)
This commit is contained in:
Adolfo Gómez García 2014-12-08 12:54:25 +01:00
parent 06a00d00b9
commit e9f38b044f
10 changed files with 188 additions and 139 deletions

View File

@ -162,11 +162,11 @@ class HTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler):
th.start()
return 'ok'
def get_preConnect(self, params):
def post_preConnect(self, params):
logger.debug('Received Pre connection')
if 'user' not in params:
if 'user' not in params or 'protocol' not in params:
raise Exception('Invalid preConnect parameters')
return HTTPServerHandler.service.preConnect(params.get('user'))
return HTTPServerHandler.service.preConnect(params.get('user'), params.get('protocol'))
def get_information(self, params):
# TODO: Return something useful? :)

View File

@ -296,7 +296,7 @@ class CommonService(object):
'''
logger.info('Service is being stopped')
def preConnect(self, user):
def preConnect(self, user, protocol):
'''
Invoked when received a PRE Connection request via REST
'''

View File

@ -163,7 +163,9 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
else:
self.multiStepJoin(name, domain, ou, account, password)
def preConnect(self, user):
def preConnect(self, user, protocol):
if protocol != 'rdp': # If connection is not using rdp, skip adding user
return 'ok'
# Well known SSID for Remote Desktop Users
REMOTE_USERS_SID = 'S-1-5-32-555'

View File

@ -428,6 +428,7 @@ class UserServiceManager(object):
return True
def isReady(self, uService):
return True
UserService.objects.update()
uService = UserService.objects.get(id=uService.id)
logger.debug('Checking ready of {0}'.format(uService))
@ -446,6 +447,25 @@ class UserServiceManager(object):
UserServiceOpChecker.makeUnique(uService, ui, state)
return False
def notifyPreconnect(self, uService, userName, protocol):
url = uService.getCommsUrl()
if url is None:
logger.debug('No notification is made because agent does not supports notifications')
return
url += '/preConnect'
try:
r = requests.post(url,
data=json.dumps({'user': userName, 'protocol': protocol}),
headers={'content-type': 'application/json'},
verify=False,
timeout=5)
r = json.loads(r.content)
logger.debug('Sent pre connection to client using {}: {}'.format(url, r))
# In fact we ignore result right now
except Exception as e:
logger.error('Exception caught notifiying preConnection: {}. Check connection on destination machine: {}'.format(e, url))
def sendScript(self, uService, script):
'''
If allowed, send script to user service

View File

@ -144,13 +144,13 @@ class Transport(Module):
'''
return {'protocol': self.protocol, 'username': '', 'password': '', 'domain': ''}
def preAccessScript(self, userService, user):
def processedUser(self, userService, userName):
'''
This gives us the chance to include "customized" initialization for any transport for an specifyc user & service on assignation to an user
such as "include" in allowed user list, etc...
Both values are db objects
Used to "transform" username that will be sent to service
This is used to make the "user" that will receive the service match with that sent in notification
@return: transformed username
'''
return None
return userName
def renderForHtml(self, userService, transport, ip, os, user, password):
'''

View File

@ -168,7 +168,7 @@ def unmarshalTRGS(data):
def transformTransports(apps, schema_editor):
'''
Adds uuids values to migrated models
Move serialization to a better model (it's time, the mode is there since 1.1 :) )
'''
model = apps.get_model("uds", 'Transport')
for t in model.objects.all():

View File

@ -0,0 +1,124 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 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
'''
from django.utils.translation import ugettext_noop as _
from uds.core.managers.UserPrefsManager import CommonPrefs
from uds.core.ui.UserInterface import gui
from uds.core.transports.BaseTransport import Transport
from uds.core.transports import protocols
from uds.core.util import connection
from .web import generateHtmlForRdp, getHtmlComponent
import logging
logger = logging.getLogger(__name__)
READY_CACHE_TIMEOUT = 30
class BaseRDPTransport(Transport):
'''
Provides access via RDP to service.
This transport can use an domain. If username processed by authenticator contains '@', it will split it and left-@-part will be username, and right password
'''
iconFile = 'rdp.png'
needsJava = True # If this transport needs java for rendering
protocol = protocols.RDP
useEmptyCreds = gui.CheckBoxField(label=_('Empty creds'), order=1, tooltip=_('If checked, the credentials used to connect will be emtpy'))
fixedName = gui.TextField(label=_('Username'), order=2, tooltip=_('If not empty, this username will be always used as credential'))
fixedPassword = gui.PasswordField(label=_('Password'), order=3, tooltip=_('If not empty, this password will be always used as credential'))
withoutDomain = gui.CheckBoxField(label=_('Without Domain'), order=4, tooltip=_('If checked, the domain part will always be emptied (to connecto to xrdp for example is needed)'))
fixedDomain = gui.TextField(label=_('Domain'), order=5, tooltip=_('If not empty, this domain will be always used as credential (used as DOMAIN\\user)'))
allowSmartcards = gui.CheckBoxField(label=_('Allow Smartcards'), order=6, tooltip=_('If checked, this transport will allow the use of smartcards'))
allowPrinters = gui.CheckBoxField(label=_('Allow Printers'), order=7, tooltip=_('If checked, this transport will allow the use of user printers'))
allowDrives = gui.CheckBoxField(label=_('Allow Drives'), order=8, tooltip=_('If checked, this transport will allow the use of user drives'))
allowSerials = gui.CheckBoxField(label=_('Allow Serials'), order=9, tooltip=_('If checked, this transport will allow the use of user serial ports'))
wallpaper = gui.CheckBoxField(label=_('Show wallpaper'), order=10, tooltip=_('If checked, the wallpaper and themes will be shown on machine (better user experience, more bandwidth)'))
def isAvailableFor(self, ip):
'''
Checks if the transport is available for the requested destination ip
Override this in yours transports
'''
logger.debug('Checking availability for {0}'.format(ip))
ready = self.cache().get(ip)
if ready is None:
# Check again for ready
if connection.testServer(ip, '3389') is True:
self.cache().put(ip, 'Y', READY_CACHE_TIMEOUT)
return True
else:
self.cache().put(ip, 'N', READY_CACHE_TIMEOUT)
return ready == 'Y'
def processedUser(self, userService, userName):
v = self.processUserPassword(userService, userName, '')
return v['username']
def processUserPassword(self, service, user, password):
username = user.getUsernameForAuth()
if self.fixedName.value is not '':
username = self.fixedName.value
proc = username.split('@')
if len(proc) > 1:
domain = proc[1]
else:
domain = ''
username = proc[0]
if self.fixedPassword.value is not '':
password = self.fixedPassword.value
if self.fixedDomain.value is not '':
domain = self.fixedDomain.value
if self.useEmptyCreds.isTrue():
username, password, domain = '', '', ''
if self.withoutDomain.isTrue():
domain = ''
if '.' in domain: # Dotter domain form
username = username + '@' + domain
domain = ''
# Fix username/password acording to os manager
username, password = service.processUserPassword(username, password)
return {'protocol': self.protocol, 'username': username, 'password': password, 'domain': domain}
def getHtmlComponent(self, _id, _os, componentId):
logger.debug('Component: ID={}'.format(id))
# We use helper to keep this clean
return getHtmlComponent(self.__module__, componentId)

View File

@ -38,6 +38,7 @@ from uds.core.transports.BaseTransport import Transport
from uds.core.transports import protocols
from uds.core.util import connection
from .web import generateHtmlForRdp, getHtmlComponent
from .BaseRDPTransport import BaseRDPTransport
import logging
@ -46,7 +47,7 @@ logger = logging.getLogger(__name__)
READY_CACHE_TIMEOUT = 30
class RDPTransport(Transport):
class RDPTransport(BaseRDPTransport):
'''
Provides access via RDP to service.
This transport can use an domain. If username processed by authenticator contains '@', it will split it and left-@-part will be username, and right password
@ -54,71 +55,20 @@ class RDPTransport(Transport):
typeName = _('RDP Transport (direct)')
typeType = 'RDPTransport'
typeDescription = _('RDP Transport for direct connection')
iconFile = 'rdp.png'
needsJava = True # If this transport needs java for rendering
protocol = protocols.RDP
useEmptyCreds = gui.CheckBoxField(label=_('Empty creds'), order=1, tooltip=_('If checked, the credentials used to connect will be emtpy'))
fixedName = gui.TextField(label=_('Username'), order=2, tooltip=_('If not empty, this username will be always used as credential'))
fixedPassword = gui.PasswordField(label=_('Password'), order=3, tooltip=_('If not empty, this password will be always used as credential'))
withoutDomain = gui.CheckBoxField(label=_('Without Domain'), order=4, tooltip=_('If checked, the domain part will always be emptied (to connecto to xrdp for example is needed)'))
fixedDomain = gui.TextField(label=_('Domain'), order=5, tooltip=_('If not empty, this domain will be always used as credential (used as DOMAIN\\user)'))
allowSmartcards = gui.CheckBoxField(label=_('Allow Smartcards'), order=6, tooltip=_('If checked, this transport will allow the use of smartcards'))
allowPrinters = gui.CheckBoxField(label=_('Allow Printers'), order=7, tooltip=_('If checked, this transport will allow the use of user printers'))
allowDrives = gui.CheckBoxField(label=_('Allow Drives'), order=8, tooltip=_('If checked, this transport will allow the use of user drives'))
allowSerials = gui.CheckBoxField(label=_('Allow Serials'), order=9, tooltip=_('If checked, this transport will allow the use of user serial ports'))
wallpaper = gui.CheckBoxField(label=_('Show wallpaper'), order=10, tooltip=_('If checked, the wallpaper and themes will be shown on machine (better user experience, more bandwidth)'))
def initialize(self, values):
return
def isAvailableFor(self, ip):
'''
Checks if the transport is available for the requested destination ip
Override this in yours transports
'''
logger.debug('Checking availability for {0}'.format(ip))
ready = self.cache().get(ip)
if ready is None:
# Check again for ready
if connection.testServer(ip, '3389') == True:
self.cache().put(ip, 'Y', READY_CACHE_TIMEOUT)
return True
else:
self.cache().put(ip, 'N', READY_CACHE_TIMEOUT)
return ready == 'Y'
useEmptyCreds = BaseRDPTransport.useEmptyCreds
fixedName = BaseRDPTransport.fixedName
fixedPassword = BaseRDPTransport.fixedPassword
withoutDomain = BaseRDPTransport.withoutDomain
fixedDomain = BaseRDPTransport.fixedDomain
allowSmartcards = BaseRDPTransport.allowSmartcards
allowPrinters = BaseRDPTransport.allowPrinters
allowDrives = BaseRDPTransport.allowDrives
allowSerials = BaseRDPTransport.allowSerials
wallpaper = BaseRDPTransport.wallpaper
def getConnectionInfo(self, service, user, password):
username = user.getUsernameForAuth()
if self.fixedName.value is not '':
username = self.fixedName.value
proc = username.split('@')
if len(proc) > 1:
domain = proc[1]
else:
domain = ''
username = proc[0]
if self.fixedPassword.value is not '':
password = self.fixedPassword.value
if self.fixedDomain.value is not '':
domain = self.fixedDomain.value
if self.useEmptyCreds.isTrue():
username, password, domain = '', '', ''
if self.withoutDomain.isTrue():
domain = ''
if '.' in domain: # Dotter domain form
username = username + '@' + domain
domain = ''
# Fix username/password acording to os manager
username, password = service.processUserPassword(username, password)
return {'protocol': self.protocol, 'username': username, 'password': password, 'domain': domain}
return self.processUserPassword(service, user, password)
def renderForHtml(self, userService, transport, ip, os, user, password):
# We use helper to keep this clean
@ -144,8 +94,3 @@ class RDPTransport(Transport):
}
return generateHtmlForRdp(self, userService.uuid, transport.uuid, os, ip, '3389', username, password, domain, extra)
def getHtmlComponent(self, id, os, componentId):
logger.debug('Component: ID={}'.format(id))
# We use helper to keep this clean
return getHtmlComponent(self.__module__, componentId)

View File

@ -38,7 +38,8 @@ from uds.core.transports.BaseTransport import Transport
from uds.core.transports import protocols
from uds.core.util import connection
from uds.core.util.Cache import Cache
from web import generateHtmlForRdp, getHtmlComponent
from .web import generateHtmlForRdp, getHtmlComponent
from .BaseRDPTransport import BaseRDPTransport
import logging
import random
@ -50,7 +51,7 @@ logger = logging.getLogger(__name__)
READY_CACHE_TIMEOUT = 30
class TSRDPTransport(Transport):
class TSRDPTransport(BaseRDPTransport):
'''
Provides access via RDP to service.
This transport can use an domain. If username processed by authenticator contains '@', it will split it and left-@-part will be username, and right password
@ -65,65 +66,28 @@ class TSRDPTransport(Transport):
tunnelServer = gui.TextField(label=_('Tunnel server'), order=1, tooltip=_('IP or Hostname of tunnel server send to client device ("public" ip) and port. (use HOST:PORT format)'))
tunnelCheckServer = gui.TextField(label=_('Tunnel host check'), order=2, tooltip=_('If not empty, this server will be used to check if service is running before assigning it to user. (use HOST:PORT format)'))
useEmptyCreds = gui.CheckBoxField(label=_('Empty creds'), order=3, tooltip=_('If checked, the credentials used to connect will be emtpy'))
fixedName = gui.TextField(label=_('Username'), order=4, tooltip=_('If not empty, this username will be always used as credential'))
fixedPassword = gui.PasswordField(label=_('Password'), order=5, tooltip=_('If not empty, this password will be always used as credential'))
withoutDomain = gui.CheckBoxField(label=_('Without Domain'), order=6, tooltip=_('If checked, the domain part will always be emptied (to connecto to xrdp for example is needed)'))
fixedDomain = gui.TextField(label=_('Domain'), order=7, tooltip=_('If not empty, this domain will be always used as credential (used as DOMAIN\\user)'))
allowSmartcards = gui.CheckBoxField(label=_('Allow Smartcards'), order=8, tooltip=_('If checked, this transport will allow the use of smartcards'))
allowPrinters = gui.CheckBoxField(label=_('Allow Printers'), order=9, tooltip=_('If checked, this transport will allow the use of user printers'))
allowDrives = gui.CheckBoxField(label=_('Allow Drives'), order=10, tooltip=_('If checked, this transport will allow the use of user drives'))
allowSerials = gui.CheckBoxField(label=_('Allow Serials'), order=11, tooltip=_('If checked, this transport will allow the use of user serial ports'))
wallpaper = gui.CheckBoxField(label=_('Show wallpaper'), order=12, tooltip=_('If checked, the wallpaper and themes will be shown on machine (better user experience, more bandwidth)'))
useEmptyCreds = BaseRDPTransport.useEmptyCreds
fixedName = BaseRDPTransport.fixedName
fixedPassword = BaseRDPTransport.fixedPassword
withoutDomain = BaseRDPTransport.withoutDomain
fixedDomain = BaseRDPTransport.fixedDomain
allowSmartcards = BaseRDPTransport.allowSmartcards
allowPrinters = BaseRDPTransport.allowPrinters
allowDrives = BaseRDPTransport.allowDrives
allowSerials = BaseRDPTransport.allowSerials
wallpaper = BaseRDPTransport.wallpaper
def initialize(self, values):
if values is not None:
if values['tunnelServer'].count(':') != 1:
raise Transport.ValidationException(_('Must use HOST:PORT in Tunnel Server Field'))
def isAvailableFor(self, ip):
'''
Checks if the transport is available for the requested destination ip
Override this in yours transports
'''
logger.debug('Checking availability for {0}'.format(ip))
ready = self.cache().get(ip)
if ready is None:
# Check again for readyness
if connection.testServer(ip, '3389') == True:
self.cache().put(ip, 'Y', READY_CACHE_TIMEOUT)
return True
else:
self.cache().put(ip, 'N', READY_CACHE_TIMEOUT)
return ready == 'Y'
def renderForHtml(self, userService, transport, ip, os, user, password):
# We use helper to keep this clean
username = user.getUsernameForAuth()
prefs = user.prefs('rdp')
if self.fixedName.value is not '':
username = self.fixedName.value
proc = username.split('@')
if len(proc) > 1:
domain = proc[1]
else:
domain = ''
username = proc[0]
if self.fixedPassword.value is not '':
password = self.fixedPassword.value
if self.fixedDomain.value is not '':
domain = self.fixedDomain.value
if self.useEmptyCreds.isTrue():
username, password, domain = '', '', ''
if '.' in domain: # Dotter domain form
username = username + '@' + domain
domain = ''
if self.withoutDomain.isTrue():
domain = ''
ci = self.getConnectionInfo(userService, user, password)
username, password, domain = ci['username'], ci['password'], ci['domain']
width, height = CommonPrefs.getWidthHeight(prefs)
depth = CommonPrefs.getDepth(prefs)
@ -153,11 +117,4 @@ class TSRDPTransport(Transport):
'wallpaper': self.wallpaper.isTrue()
}
# Fix username/password acording to os manager
username, password = userService.processUserPassword(username, password)
return generateHtmlForRdp(self, userService.uuid, transport.uuid, os, ip, '-1', username, password, domain, extra)
def getHtmlComponent(self, id, os, componentId):
# We use helper to keep this clean
return getHtmlComponent(self.__module__, componentId)

View File

@ -277,8 +277,9 @@ def service(request, idService, idTransport):
itrans = trans.getInstance()
if itrans.isAvailableFor(ip):
log.doLog(ads, log.INFO, "User service ready, rendering transport", log.WEB)
transport = itrans.renderForHtml(ads, trans, ip, request.session['OS'], request.user, webPassword(request))
return render_to_response(theme.template('show_transport.html'), {'transport': transport, 'nolang': True}, context_instance=RequestContext(request))
transportHtml = itrans.renderForHtml(ads, trans, ip, request.session['OS'], request.user, webPassword(request))
UserServiceManager.manager().notifyPreconnect(ads, itrans.processedUser(ads, request.user), itrans.protocol)
return render_to_response(theme.template('show_transport.html'), {'transport': transportHtml, 'nolang': True}, context_instance=RequestContext(request))
else:
log.doLog(ads, log.WARN, "User service is not accessible (ip {0})".format(ip), log.TRANSPORT)
logger.debug('Transport is not ready for user service {0}'.format(ads))