class IPServerService(services.Service): # Gui remoteHost = gui.TextField( oder=1, length=64, label=_('Remote Server'), tooltip=_( 'IP or Hostname of remote host (must be resolvable by clients)'), required=True, ) # Description of service typeName = _('Single server service by IP') typeType = 'IPServerService' typeDescription = _( 'This service provides access to POWERED-ON Server by IP') iconFile = 'machine.png' # Characteristics of service maxDeployed = -1 # If the service provides more than 1 "provided service" (-1 = no limit, 0 = ???? (do not use it!!!), N = max number to deploy usesCache = False # Cache are running machine awaiting to be assigned usesCache_L2 = False # L2 Cache are running machines in suspended state needsManager = False # If the service needs a s.o. manager (managers are related to agents provided by services itselfs, i.e. virtual machines with agent) mustAssignManually = False # If true, the system can't do an automatic assignation of a deployed user service from this service deployedType = IPMachineDeployed servicesTypeProvided = (serviceTypes.VDI, ) def initialize(self, values): """ We check here form values to see if they are valid. Note that we check them throught FROM variables, that already has been initialized by __init__ method of base class, before invoking this. """ if values is not None: if self.remoteHost.value == '': raise services.Service.ValidationException( 'No remote host found') # 172.27.0.1~00000 def getData(self): return self.nameGenerator().get(self.remoteHost.value + '~') def releaseData(self, data): baseName = data.split('~')[0] + '~' self.nameGenerator().free(baseName, data)
class IPSingleMachineService(services.Service): # Gui ip = gui.TextField(length=64, label=_('Machine IP'), order=1, tooltip=_('Machine IP'), required=True) # Description of service typeName = _('Static Single IP') typeType = 'IPSingleMachineService' typeDescription = _( 'This service provides access to POWERED-ON Machine by IP') iconFile = 'machine.png' # Characteristics of service maxDeployed = -1 # If the service provides more than 1 "provided service" (-1 = no limit, 0 = ???? (do not use it!!!), N = max number to deploy usesCache = False # Cache are running machine awaiting to be assigned usesCache_L2 = False # L2 Cache are running machines in suspended state needsManager = False # If the service needs a s.o. manager (managers are related to agents provided by services itselfs, i.e. virtual machines with agent) mustAssignManually = False # If true, the system can't do an automatic assignation of a deployed user service from this service deployedType = IPMachineDeployed servicesTypeProvided = (serviceTypes.VDI, ) def initialize(self, values): pass def getUnassignedMachine(self): ip = None try: self.storage.lock() counter = self.storage.getPickle('counter') counter = counter + 1 if counter is not None else 1 self.storage.putPickle('counter', counter) ip = '{}~{}'.format(self.ip.value, counter) except Exception: ip = None logger.exception("Exception at getUnassignedMachine") finally: self.storage.unlock() return ip def unassignMachine(self, ip): pass
class HTML5RDPTransport(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 ''' typeName = _('HTML5 RDP Transport') typeType = 'HTML5RDPTransport' typeDescription = _('RDP Transport using HTML5 client') iconFile = 'rdp.png' ownLink = True supportedOss = OsDetector.allOss protocol = protocols.RDP guacamoleServer = gui.TextField( label=_('Tunnel Server'), order=1, tooltip= _('Host of the tunnel server (use http/https & port if needed) as accesible from users' ), defvalue='https://', length=64, required=True) useEmptyCreds = gui.CheckBoxField( label=_('Empty creds'), order=2, tooltip=_('If checked, the credentials used to connect will be emtpy')) fixedName = gui.TextField( label=_('Username'), order=3, tooltip=_( 'If not empty, this username will be always used as credential')) fixedPassword = gui.PasswordField( label=_('Password'), order=4, tooltip=_( 'If not empty, this password will be always used as credential')) withoutDomain = gui.CheckBoxField( label=_('Without Domain'), order=5, tooltip= _('If checked, the domain part will always be emptied (to connecto to xrdp for example is needed)' )) fixedDomain = gui.TextField( label=_('Domain'), order=6, tooltip= _('If not empty, this domain will be always used as credential (used as DOMAIN\\user)' )) enableAudio = gui.CheckBoxField( label=_('Enable Audio'), order=7, tooltip= _('If checked, the audio will be redirected to client (if client browser supports it)' )) enablePrinting = gui.CheckBoxField( label=_('Enable Printing'), order=8, tooltip= _('If checked, the printing will be redirected to client (if client browser supports it)' )) def initialize(self, values): if values is None: return if self.guacamoleServer.value[0:4] != 'http': raise Transport.ValidationException( _('The server must be http or https')) # Same check as normal RDP transport 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') 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 getLink(self, userService, transport, ip, os, user, password, request): ci = self.processUserPassword(userService, user, password) username, password, domain = ci['username'], ci['password'], ci[ 'domain'] if domain != '': username = domain + '\\' + username # Build params dict params = { 'protocol': 'rdp', 'hostname': ip, 'username': username, 'password': password, 'ignore-cert': 'true' } if self.enableAudio.isTrue() is False: params['disable-audio'] = 'true' if self.enablePrinting.isTrue() is True: params['enable-printing'] = 'true' logger.debug('RDP Params: {0}'.format(params)) ticket = TicketStore.create(params) return HttpResponseRedirect("{}/transport/?{}&{}".format( self.guacamoleServer.value, ticket, request.build_absolute_uri(reverse('Index'))))
class RegexLdap(auths.Authenticator): host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('Ldap Server Host'), required=True) port = gui.NumericField(length=5, label=_('Port'), defvalue='389', order=2, tooltip=_('Ldap port (usually 389 for non ssl and 636 for ssl)'), required=True) ssl = gui.CheckBoxField(label=_('Use SSL'), order=3, tooltip=_('If checked, the connection will be ssl, using port 636 instead of 389')) username = gui.TextField(length=64, label=_('User'), order=4, tooltip=_('Username with read privileges on the base selected'), required=True, tab=gui.CREDENTIALS_TAB) password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the ldap user'), required=True, tab=gui.CREDENTIALS_TAB) timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=6, tooltip=_('Timeout in seconds of connection to LDAP'), required=True, minValue=1) ldapBase = gui.TextField(length=64, label=_('Base'), order=7, tooltip=_('Common search base (used for "users" and "groups")'), required=True, tab=_('Ldap info')) userClass = gui.TextField(length=64, label=_('User class'), defvalue='posixAccount', order=8, tooltip=_('Class for LDAP users (normally posixAccount)'), required=True, tab=_('Ldap info')) userIdAttr = gui.TextField(length=64, label=_('User Id Attr'), defvalue='uid', order=9, tooltip=_('Attribute that contains the user id'), required=True, tab=_('Ldap info')) userNameAttr = gui.TextField(length=640, label=_('User Name Attr'), multiline=2, defvalue='uid', order=10, tooltip=_('Attributes that contains the user name (list of comma separated values)'), required=True, tab=_('Ldap info')) groupNameAttr = gui.TextField(length=640, label=_('Group Name Attr'), multiline=2, defvalue='cn', order=11, tooltip=_('Attribute that contains the group name'), required=True, tab=_('Ldap info')) # regex = gui.TextField(length=64, label = _('Regular Exp. for groups'), defvalue = '^(.*)', order = 12, tooltip = _('Regular Expression to extract the group name'), required = True) typeName = _('Regex LDAP Authenticator') typeType = 'RegexLdapAuthenticator' typeDescription = _('Regular Expressions LDAP authenticator') iconFile = 'auth.png' # If it has and external source where to get "new" users (groups must be declared inside UDS) isExternalSource = True # If we need to enter the password for this user needsPassword = False # Label for username field userNameLabel = _('Username') # Label for group field groupNameLabel = _("Group") # Label for password field passwordLabel = _("Password") def __init__(self, dbAuth, environment, values=None): super(RegexLdap, self).__init__(dbAuth, environment, values) if values is not None: self.__validateField(values['userNameAttr'], str(self.userNameAttr.label)) self.__validateField(values['userIdAttr'], str(self.userIdAttr.label)) self.__validateField(values['groupNameAttr'], str(self.groupNameAttr.label)) self._host = values['host'] self._port = values['port'] self._ssl = gui.strToBool(values['ssl']) self._username = values['username'] self._password = values['password'] self._timeout = values['timeout'] self._ldapBase = values['ldapBase'] self._userClass = values['userClass'] self._userIdAttr = values['userIdAttr'] self._groupNameAttr = values['groupNameAttr'] # self._regex = values['regex'] self._userNameAttr = values['userNameAttr'] else: self._host = None self._port = None self._ssl = None self._username = None self._password = None self._timeout = None self._ldapBase = None self._userClass = None self._userIdAttr = None self._groupNameAttr = None # self._regex = None self._userNameAttr = None self._connection = None def __validateField(self, field, fieldLabel): ''' Validates the multi line fields refering to attributes ''' for line in field.splitlines(): if line.find('=') != -1: _, pattern = line.split('=')[0:2] if pattern.find('(') == -1: pattern = '(' + pattern + ')' try: re.search(pattern, '') except: raise auths.Authenticator.ValidationException('Invalid pattern in {0}: {1}'.format(fieldLabel, line)) def __getAttrsFromField(self, field): res = [] for line in field.splitlines(): equalPos = line.find('=') if equalPos != -1: attr = line[:equalPos] else: attr = line res.append(attr.encode('utf-8')) return res def __processField(self, field, attributes): res = [] logger.debug('Attributes: {}'.format(attributes)) for line in field.splitlines(): equalPos = line.find('=') if equalPos == -1: line += '=(.*)' equalPos = line.find('=') attr, pattern = (line[:equalPos], line[equalPos + 1:]) attr = attr.lower() # if pattern do not have groups, define one with full re if pattern.find('(') == -1: pattern = '(' + pattern + ')' val = attributes.get(attr, []) if type(val) is not list: # May we have a single value val = [val] logger.debug('Pattern: {0}'.format(pattern)) for vv in val: try: v = vv.decode('utf-8') logger.debug('v, vv: {}, {}'.format(v, vv)) srch = re.search(pattern, v, re.IGNORECASE) logger.debug("Found against {0}: {1} ".format(v, srch.groups())) if srch is None: continue res.append(''.join(srch.groups())) except Exception: pass # Ignore exceptions here return res def valuesDict(self): return { 'host': self._host, 'port': self._port, 'ssl': gui.boolToStr(self._ssl), 'username': self._username, 'password': self._password, 'timeout': self._timeout, 'ldapBase': self._ldapBase, 'userClass': self._userClass, 'userIdAttr': self._userIdAttr, 'groupNameAttr': self._groupNameAttr, 'userNameAttr': self._userNameAttr } def __str__(self): return "Ldap Auth: {0}:{1}@{2}:{3}, base = {4}, userClass = {5}, userIdAttr = {6}, groupNameAttr = {7}, userName attr = {8}".format( self._username, self._password, self._host, self._port, self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, self._userNameAttr) def marshal(self): return '\t'.join([ 'v2', self._host, self._port, gui.boolToStr(self._ssl), self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, self._userNameAttr ]) def unmarshal(self, val): data = val.split('\t') if data[0] == 'v1': logger.debug("Data: {0}".format(data[1:])) self._host, self._port, self._ssl, self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, _regex, self._userNameAttr = data[1:] self._ssl = gui.strToBool(self._ssl) self._groupNameAttr = self._groupNameAttr + '=' + _regex self._userNameAttr = '\n'.join(self._userNameAttr.split(',')) elif data[0] == 'v2': logger.debug("Data v2: {0}".format(data[1:])) self._host, self._port, self._ssl, self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, self._userNameAttr = data[1:] self._ssl = gui.strToBool(self._ssl) def __connection(self, username=None, password=None): if self._connection is None or username is not None: # We want this method also to check credentials l = None cache = False try: if password is not None: password = password.encode('utf-8') # ldap.set_option(ldap.OPT_DEBUG_LEVEL, 9) ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) schema = self._ssl and 'ldaps' or 'ldap' port = self._port != '389' and ':' + self._port or '' uri = "%s://%s%s" % (schema, self._host, port) logger.debug('Ldap uri: {0}'.format(uri)) l = ldap.initialize(uri=uri) l.set_option(ldap.OPT_REFERRALS, 0) l.network_timeout = l.timeout = int(self._timeout) l.protocol_version = ldap.VERSION3 if username is None: cache = True username = self._username password = self._password l.simple_bind_s(who=username, cred=password) except ldap.LDAPError as e: str_ = _('Ldap connection error: ') if isinstance(e.message, dict): str_ += ', '.join(e.message.get('info', ''), e.message.get('desc')) else: str_ += six.text_type(e) raise Exception(str_) if cache is True: self._connection = l else: return l # Do not cache nor overwrite "global" connection return self._connection def __getUser(self, username): try: con = self.__connection() filter_ = '(&(objectClass=%s)(%s=%s))' % (self._userClass, self._userIdAttr, ldap.filter.escape_filter_chars(username, 0)) attrlist = [self._userIdAttr.encode('utf-8')] + self.__getAttrsFromField(self._userNameAttr) + self.__getAttrsFromField(self._groupNameAttr) logger.debug('Getuser filter_: {0}, attr list: {1}'.format(filter_, attrlist)) res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr=filter_, attrlist=attrlist, sizelimit=LDAP_RESULT_LIMIT)[0] usr = dict((k, '') for k in attrlist) dct = {k.lower(): v for k, v in six.iteritems(res[1])} usr.update(dct) usr.update({'dn': res[0], '_id': username}) logger.debug('Usr: {0}'.format(usr)) return usr except Exception: logger.exception('Exception:') return None def __getGroups(self, usr): return self.__processField(self._groupNameAttr, usr) def __getUserRealName(self, usr): return ' '.join(self.__processField(self._userNameAttr, usr)) # return ' '.join([ (type(usr.get(id_, '')) is list and ' '.join(( str(k) for k in usr.get(id_, ''))) or str(usr.get(id_, ''))) for id_ in self._userNameAttr.split(',') ]).strip() def authenticate(self, username, credentials, groupsManager): ''' Must authenticate the user. We can have to different situations here: 1.- The authenticator is external source, what means that users may be unknown to system before callig this 2.- The authenticator isn't external source, what means that users have been manually added to system and are known before this call We receive the username, the credentials used (normally password, but can be a public key or something related to pk) and a group manager. The group manager is responsible for letting know the authenticator which groups we currently has active. @see: uds.core.auths.GroupsManager ''' try: # Locate the user at LDAP usr = self.__getUser(username) if usr is None: return False # Let's see first if it credentials are fine self.__connection(usr['dn'], credentials) # Will raise an exception if it can't connect groupsManager.validate(self.__getGroups(usr)) return True except Exception: return False def createUser(self, usrData): ''' We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..) External sources already has the user cause they are managed externally, so, it can at most test if the users exists on external source before accepting it. Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to @param usrData: Contains data received from user directly, that is, a dictionary with at least: name, real_name, comments, state & password @return: Raises an exception (AuthException) it things didn't went fine ''' res = self.__getUser(usrData['name']) if res is None: raise AuthenticatorException(_('Username not found')) # Fills back realName field usrData['real_name'] = self.__getUserRealName(res) def getRealName(self, username): ''' Tries to get the real name of an user ''' res = self.__getUser(username) if res is None: return username return self.__getUserRealName(res) def modifyUser(self, usrData): ''' We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..) Modify user has no reason on external sources, so it will never be used (probably) Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to @param usrData: Contains data received from user directly, that is, a dictionary with at least: name, realName, comments, state & password @return: Raises an exception it things doesn't go fine ''' return self.createUser(usrData) def createGroup(self, groupData): ''' We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..) External sources already has its own groups and, at most, it can check if it exists on external source before accepting it Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to @params groupData: a dict that has, at least, name, comments and active @return: Raises an exception it things doesn't go fine ''' pass def getGroups(self, username, groupsManager): ''' Looks for the real groups to which the specified user belongs Updates groups manager with valid groups Remember to override it in derived authentication if needed (external auths will need this, for internal authenticators this is never used) ''' user = self.__getUser(username) if user is None: raise AuthenticatorException(_('Username not found')) groups = self.__getGroups(user) groupsManager.validate(groups) def searchUsers(self, pattern): try: con = self.__connection() res = [] for r in con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(&(objectClass=%s)(%s=%s*))' % (self._userClass, self._userIdAttr, pattern), sizelimit=LDAP_RESULT_LIMIT): if r[0] is not None: # Must have a dn, we do not accept references to other dct = {k.lower(): v for k, v in six.iteritems(r[1])} logger.debug('R: {0}'.format(dct)) usrId = dct.get(self._userIdAttr.lower(), '') usrId = type(usrId) == list and usrId[0] or usrId res.append({ 'id': usrId, 'name': self.__getUserRealName(dct) }) logger.debug(res) return res except Exception: logger.exception("Exception: ") raise AuthenticatorException(_('Too many results, be more specific')) @staticmethod def test(env, data): try: auth = RegexLdap(None, env, data) return auth.testConnection() except Exception as e: logger.error("Exception found testing Simple LDAP auth {0}: {1}".format(e.__class__, e)) return [False, "Error testing connection"] def testConnection(self): try: con = self.__connection() except Exception as e: return [False, str(e)] try: con.search_s(base=self._ldapBase, scope=ldap.SCOPE_BASE) except Exception: return [False, _('Ldap search base is incorrect')] try: if len(con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(objectClass=%s)' % self._userClass, sizelimit=1)) == 1: raise Exception() return [False, _('Ldap user class seems to be incorrect (no user found by that class)')] except Exception as e: # If found 1 or more, all right pass # Now test objectclass and attribute of users try: if len(con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(&(objectClass=%s)(%s=*))' % (self._userClass, self._userIdAttr), sizelimit=1)) == 1: raise Exception() return [False, _('Ldap user id attr is probably wrong (can\'t find any user with both conditions)')] except Exception as e: # If found 1 or more, all right pass for grpNameAttr in self._groupNameAttr.split('\n'): vals = grpNameAttr.split('=')[0] if vals == 'dn': continue try: if len(con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(%s=*)' % vals, sizelimit=1)) == 1: continue except: continue return [False, _('Ldap group id attribute seems to be incorrect (no group found by that attribute)')] # Now try to test regular expression to see if it matches anything ( try: # Check the existence of at least a () grouping # Check validity of regular expression (try to compile it) # this only right now pass except Exception as e: pass return [True, _("Connection params seem correct, test was succesfully executed")]
class BaseSpiceTransport(Transport): ''' Provides access via SPICE 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 = 'spice.png' protocol = protocols.SPICE allowedProviders = oVirtProvider.offers useEmptyCreds = gui.CheckBoxField( order=1, label=_('Empty credentials'), tooltip=_('If checked, the credentials used to connect will be emtpy'), tab=gui.CREDENTIALS_TAB ) fixedName = gui.TextField( order=2, label=_('Username'), tooltip=_('If not empty, this username will be always used as credential'), tab=gui.CREDENTIALS_TAB ) fixedPassword = gui.PasswordField( order=3, label=_('Password'), tooltip=_('If not empty, this password will be always used as credential'), tab=gui.CREDENTIALS_TAB ) serverCertificate = gui.TextField( order=4, length=4096, multiline=4, label=_('Certificate'), tooltip=_('Server certificate (public), can be found on your ovirt engine, probably at /etc/pki/ovirt-engine/certs/ca.der (Use the contents of this file).'), required=False ) fullScreen = gui.CheckBoxField( order=5, label=_('Fullscreen Mode'), tooltip=_('If checked, viewer will be shown on fullscreen mode-'), tab=gui.ADVANCED_TAB ) smartCardRedirect = gui.CheckBoxField( order=6, label=_('Smartcard Redirect'), tooltip=_('If checked, SPICE protocol will allow smartcard redirection.'), defvalue=gui.FALSE, tab=gui.ADVANCED_TAB ) usbShare = gui.CheckBoxField( order=7, label=_('Enable USB'), tooltip=_('If checked, USB redirection will be allowed.'), defvalue=gui.FALSE, tab=gui.ADVANCED_TAB ) autoNewUsbShare = gui.CheckBoxField( order=8, label=_('New USB Auto Sharing'), tooltip=_('Auto-redirect USB devices when plugged in.'), defvalue=gui.FALSE, tab=gui.ADVANCED_TAB ) def isAvailableFor(self, userService, ip): ''' Checks if the transport is available for the requested destination ip ''' ready = self.cache.get(ip) if ready is None: userServiceInstance = userService.getInstance() con = userServiceInstance.getConsoleConnection() logger.debug('Connection data: {}'.format(con)) if con is None: return False port, secure_port = con['port'], con['secure_port'] port = -1 if port is None else port secure_port = -1 if secure_port is None else secure_port # test ANY of the ports port_to_test = port if port != -1 else secure_port if port_to_test == -1: self.cache.put('cachedMsg', 'Could not find the PORT for connection', 120) # Write a message, that will be used from getCustom logger.info('SPICE didn\'t find has any port: {}'.format(con)) return False self.cache.put('cachedMsg', 'Could not reach server "{}" on port "{}" from broker (prob. causes are name resolution & firewall rules)'.format(con['address'], port_to_test), 120) if connection.testServer(con['address'], port_to_test) is True: self.cache.put(ip, 'Y', READY_CACHE_TIMEOUT) ready = 'Y' return ready == 'Y' def getCustomAvailableErrorMsg(self, userService, ip): msg = self.cache.get('cachedMsg') if msg is None: return Transport.getCustomAvailableErrorMsg(self, userService, ip) return msg 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 != '': username = self.fixedName.value if self.fixedPassword.value != '': password = self.fixedPassword.value if self.useEmptyCreds.isTrue(): username, password = '', '' # Fix username/password acording to os manager username, password = service.processUserPassword(username, password) return {'protocol': self.protocol, 'username': username, 'password': password} def getConnectionInfo(self, service, user, password): return self.processUserPassword(service, user, password) def getScript(self, script): with open(os.path.join(os.path.dirname(__file__), script)) as f: data = f.read() return data
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.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.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
class TSNXTransport(Transport): """ Provides access via NX 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 """ typeName = _('NX v3.5') typeType = 'TSNXTransport' typeDescription = _('NX protocol v3.5. Tunneled connection.') iconFile = 'nx.png' protocol = protocols.NX group = TUNNELED_GROUP tunnelServer = gui.TextField(label=_('Tunnel server'), order=1, tooltip=_('IP or Hostname of tunnel server sent to client device ("public" ip) and port. (use HOST:PORT format)'), tab=gui.TUNNEL_TAB) 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)'), tab=gui.TUNNEL_TAB) useEmptyCreds = gui.CheckBoxField(label=_('Empty creds'), order=3, tooltip=_('If checked, the credentials used to connect will be emtpy'), tab=gui.CREDENTIALS_TAB) fixedName = gui.TextField(label=_('Username'), order=4, tooltip=_('If not empty, this username will be always used as credential'), tab=gui.CREDENTIALS_TAB) fixedPassword = gui.PasswordField(label=_('Password'), order=5, tooltip=_('If not empty, this password will be always used as credential'), tab=gui.CREDENTIALS_TAB) listenPort = gui.NumericField(label=_('Listening port'), length=5, order=6, tooltip=_('Listening port of NX (ssh) at client machine'), defvalue='22') connection = gui.ChoiceField(label=_('Connection'), order=7, tooltip=_('Connection speed for this transport (quality)'), values=[ {'id': 'modem', 'text': 'modem'}, {'id': 'isdn', 'text': 'isdn'}, {'id': 'adsl', 'text': 'adsl'}, {'id': 'wan', 'text': 'wan'}, {'id': 'lan', 'text': 'lan'}, ], tab=gui.PARAMETERS_TAB) session = gui.ChoiceField(label=_('Session'), order=8, tooltip=_('Desktop session'), values=[ {'id': 'gnome', 'text': 'gnome'}, {'id': 'kde', 'text': 'kde'}, {'id': 'cde', 'text': 'cde'}, ], tab=gui.PARAMETERS_TAB) cacheDisk = gui.ChoiceField(label=_('Disk Cache'), order=9, tooltip=_('Cache size en Mb stored at disk'), values=[ {'id': '0', 'text': '0 Mb'}, {'id': '32', 'text': '32 Mb'}, {'id': '64', 'text': '64 Mb'}, {'id': '128', 'text': '128 Mb'}, {'id': '256', 'text': '256 Mb'}, {'id': '512', 'text': '512 Mb'}, ], tab=gui.PARAMETERS_TAB) cacheMem = gui.ChoiceField(label=_('Memory Cache'), order=10, tooltip=_('Cache size en Mb kept at memory'), values=[ {'id': '4', 'text': '4 Mb'}, {'id': '8', 'text': '8 Mb'}, {'id': '16', 'text': '16 Mb'}, {'id': '32', 'text': '32 Mb'}, {'id': '64', 'text': '64 Mb'}, {'id': '128', 'text': '128 Mb'}, ], tab=gui.PARAMETERS_TAB) def __init__(self, environment, values=None): super(TSNXTransport, self).__init__(environment, values) if values is not None: if values['tunnelServer'].find(':') == -1: raise Transport.ValidationException(_('Must use HOST:PORT in Tunnel Server Field')) self._tunnelServer = values['tunnelServer'] self._tunnelCheckServer = values['tunnelCheckServer'] self._useEmptyCreds = gui.strToBool(values['useEmptyCreds']) self._fixedName = values['fixedName'] self._fixedPassword = values['fixedPassword'] self._listenPort = values['listenPort'] self._connection = values['connection'] self._session = values['session'] self._cacheDisk = values['cacheDisk'] self._cacheMem = values['cacheMem'] else: self._tunnelServer = '' self._tunnelCheckServer = '' self._useEmptyCreds = '' self._fixedName = '' self._fixedPassword = '' self._listenPort = '' self._connection = '' self._session = '' self._cacheDisk = '' self._cacheMem = '' def marshal(self): """ Serializes the transport data so we can store it in database """ return str.join('\t', ['v1', gui.boolToStr(self._useEmptyCreds), self._fixedName, self._fixedPassword, self._listenPort, self._connection, self._session, self._cacheDisk, self._cacheMem, self._tunnelServer, self._tunnelCheckServer]) def unmarshal(self, string): data = string.split('\t') if data[0] == 'v1': self._useEmptyCreds = gui.strToBool(data[1]) self._fixedName, self._fixedPassword, self._listenPort, self._connection, self._session, self._cacheDisk, self._cacheMem, self._tunnelServer, self._tunnelCheckServer = data[2:] def valuesDict(self): return { 'useEmptyCreds': gui.boolToStr(self._useEmptyCreds), 'fixedName': self._fixedName, 'fixedPassword': self._fixedPassword, 'listenPort': self._listenPort, 'connection': self._connection, 'session': self._session, 'cacheDisk': self._cacheDisk, 'cacheMem': self._cacheMem, 'tunnelServer': self._tunnelServer, 'tunnelCheckServer': self._tunnelCheckServer } def isAvailableFor(self, userService, 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 self.testServer(userService, ip, self._listenPort) 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 getScript(self, script): with open(os.path.join(os.path.dirname(__file__), script)) as f: data = f.read() return data def getUDSTransportScript(self, userService, transport, ip, os, user, password, request): prefs = user.prefs('nx') username = user.getUsernameForAuth() proc = username.split('@') username = proc[0] if self._fixedName is not '': username = self._fixedName if self._fixedPassword is not '': password = self._fixedPassword if self._useEmptyCreds is True: username, password = '', '' tunpass = ''.join(random.choice(string.letters + string.digits) for _i in range(12)) tunuser = TicketStore.create(tunpass) sshServer = self._tunnelServer if ':' not in sshServer: sshServer += ':443' sshHost, sshPort = sshServer.split(':') logger.debug('Username generated: {0}, password: {1}'.format(tunuser, tunpass)) width, height = CommonPrefs.getWidthHeight(prefs) # Fix username/password acording to os manager username, password = userService.processUserPassword(username, password) m = { 'ip': ip, 'tunUser': tunuser, 'tunPass': tunpass, 'tunHost': sshHost, 'tunPort': sshPort, 'password': password, 'port': self._listenPort } r = NXFile(username=username, password=password, width=width, height=height) r.host = '{address}' r.port = '{port}' r.connection = self._connection r.desktop = self._session r.cachedisk = self._cacheDisk r.cachemem = self._cacheMem os = { OsDetector.Windows: 'windows', OsDetector.Linux: 'linux', OsDetector.Macintosh: 'macosx' }.get(os['OS']) if os is None: return super(self.__class__, self).getUDSTransportScript(userService, transport, ip, os, user, password, request) return self.getScript('scripts/{}/tunnel.py'.format(os)).format( r=r, m=DictAsObj(m), )
class TX2GOTransport(BaseX2GOTransport): ''' Provides access via SPICE 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 ''' typeName = _('X2Go Transport Experimental (tunneled)') typeType = 'TX2GOTransport' typeDescription = _('X2Go Transport for tunneled connection (EXPERIMENTAL)') group = TUNNELED_GROUP tunnelServer = gui.TextField(label=_('Tunnel server'), order=1, tooltip=_('IP or Hostname of tunnel server sent to client device ("public" ip) and port. (use HOST:PORT format)'), tab=gui.TUNNEL_TAB) fixedName = BaseX2GOTransport.fixedName # fullScreen = BaseX2GOTransport.fullScreen desktopType = BaseX2GOTransport.desktopType sound = BaseX2GOTransport.sound exports = BaseX2GOTransport.exports speed = BaseX2GOTransport.speed soundType = BaseX2GOTransport.soundType keyboardLayout = BaseX2GOTransport.keyboardLayout pack = BaseX2GOTransport.pack quality = BaseX2GOTransport.quality def initialize(self, values): if values is not None: if values['tunnelServer'].count(':') != 1: raise BaseX2GOTransport.ValidationException(_('Must use HOST:PORT in Tunnel Server Field')) def getUDSTransportScript(self, userService, transport, ip, os, user, password, request): prefs = user.prefs('nx') ci = self.getConnectionInfo(userService, user, password) username = ci['username'] priv, pub = self.getAndPushKey(username, userService) width, height = CommonPrefs.getWidthHeight(prefs) logger.debug('') xf = x2gofile.getTemplate( speed=self.speed.value, pack=self.pack.value, quality=self.quality.value, sound=self.sound.isTrue(), soundSystem=self.sound.value, windowManager=self.desktopType.value, exports=self.exports.isTrue(), width=width, height=height, user=username ) tunpass = ''.join(random.choice(string.letters + string.digits) for _i in range(12)) tunuser = TicketStore.create(tunpass) sshHost, sshPort = self.tunnelServer.value.split(':') # data data = { 'os': os['OS'], 'ip': ip, 'port': 22, 'tunUser': tunuser, 'tunPass': tunpass, 'tunHost': sshHost, 'tunPort': sshPort, 'username': username, 'key': priv, 'width': width, 'height': height, 'printers': True, 'drives': self.exports.isTrue(), 'fullScreen': width == -1 or height == -1, 'this_server': request.build_absolute_uri('/'), 'xf': xf } m = tools.DictAsObj(data) os = { OsDetector.Windows: 'windows', OsDetector.Linux: 'linux', # OsDetector.Macintosh: 'macosx' }.get(m.os) if os is None: return super(self.__class__, self).getUDSTransportScript(userService, transport, ip, os, user, password, request) return self.getScript('scripts/{}/tunnel.py'.format(os)).format(m=m)
class TSNXTransport(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 ''' typeName = _('NX Transport (tunneled)') typeType = 'TSNXTransport' typeDescription = _('NX Transport for tunneled connection') iconFile = 'nx.png' needsJava = True # If this transport needs java for rendering supportedOss = ['Windows', 'Macintosh', 'Linux'] protocol = protocols.NX tunnelServer = gui.TextField(label=_('Tunnel server'), order=1, tooltip=_('IP or Hostname of tunnel server sent 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')) listenPort = gui.NumericField(label=_('Listening port'), length=5, order=6, tooltip=_('Listening port of NX (ssh) at client machine'), defvalue='22') connection = gui.ChoiceField(label=_('Connection'), order=7, tooltip=_('Connection speed for this transport (quality)'), values=[ {'id': 'modem', 'text': 'modem'}, {'id': 'isdn', 'text': 'isdn'}, {'id': 'adsl', 'text': 'adsl'}, {'id': 'wan', 'text': 'wan'}, {'id': 'lan', 'text': 'lan'}, ]) session = gui.ChoiceField(label=_('Session'), order=8, tooltip=_('Desktop session'), values=[ {'id': 'gnome', 'text': 'gnome'}, {'id': 'kde', 'text': 'kde'}, {'id': 'cde', 'text': 'cde'}, ]) cacheDisk = gui.ChoiceField(label=_('Disk Cache'), order=9, tooltip=_('Cache size en Mb stored at disk'), values=[ {'id': '0', 'text': '0 Mb'}, {'id': '32', 'text': '32 Mb'}, {'id': '64', 'text': '64 Mb'}, {'id': '128', 'text': '128 Mb'}, {'id': '256', 'text': '256 Mb'}, {'id': '512', 'text': '512 Mb'}, ]) cacheMem = gui.ChoiceField(label=_('Memory Cache'), order=10, tooltip=_('Cache size en Mb kept at memory'), values=[ {'id': '4', 'text': '4 Mb'}, {'id': '8', 'text': '8 Mb'}, {'id': '16', 'text': '16 Mb'}, {'id': '32', 'text': '32 Mb'}, {'id': '64', 'text': '64 Mb'}, {'id': '128', 'text': '128 Mb'}, ]) def __init__(self, environment, values=None): super(TSNXTransport, self).__init__(environment, values) if values != None: if values['tunnelServer'].find(':') == -1: raise Transport.ValidationException(_('Must use HOST:PORT in Tunnel Server Field')) self._tunnelServer = values['tunnelServer'] self._tunnelCheckServer = values['tunnelCheckServer'] self._useEmptyCreds = gui.strToBool(values['useEmptyCreds']) self._fixedName = values['fixedName'] self._fixedPassword = values['fixedPassword'] self._listenPort = values['listenPort'] self._connection = values['connection'] self._session = values['session'] self._cacheDisk = values['cacheDisk'] self._cacheMem = values['cacheMem'] else: self._tunnelServer = '' self._tunnelCheckServer = '' self._useEmptyCreds = '' self._fixedName = '' self._fixedPassword = '' self._listenPort = '' self._connection = '' self._session = '' self._cacheDisk = '' self._cacheMem = '' def marshal(self): ''' Serializes the transport data so we can store it in database ''' return str.join('\t', [ 'v1', gui.boolToStr(self._useEmptyCreds), self._fixedName, self._fixedPassword, self._listenPort, self._connection, self._session, self._cacheDisk, self._cacheMem, self._tunnelServer, self._tunnelCheckServer ]) def unmarshal(self, string): data = string.split('\t') if data[0] == 'v1': self._useEmptyCreds = gui.strToBool(data[1]) self._fixedName, self._fixedPassword, self._listenPort, self._connection, self._session, self._cacheDisk, self._cacheMem, self._tunnelServer, self._tunnelCheckServer = data[2:] def valuesDict(self): return { 'useEmptyCreds': gui.boolToStr(self._useEmptyCreds), 'fixedName': self._fixedName, 'fixedPassword': self._fixedPassword, 'listenPort': self._listenPort, 'connection': self._connection, 'session': self._session, 'cacheDisk': self._cacheDisk, 'cacheMem': self._cacheMem, 'tunnelServer': self._tunnelServer, 'tunnelCheckServer': self._tunnelCheckServer } 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, self._listenPort) 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 renderForHtml(self, userService, transport, ip, os, user, password): prefs = user.prefs('nx') username = user.getUsernameForAuth() proc = username.split('@') username = proc[0] if self._fixedName is not '': username = self._fixedName if self._fixedPassword is not '': password = self._fixedPassword if self._useEmptyCreds is True: username, password = '', '' width, height = CommonPrefs.getWidthHeight(prefs) cache = Cache('pam') tunuser = ''.join(random.choice(string.letters + string.digits) for _i in range(12)) + ("%f" % time.time()).split('.')[1] tunpass = ''.join(random.choice(string.letters + string.digits) for _i in range(12)) cache.put(tunuser, tunpass, 60 * 10) # Credential valid for ten minutes, and for 1 use only sshHost, sshPort = self._tunnelServer.split(':') logger.debug('Username generated: {0}, password: {1}'.format(tunuser, tunpass)) tun = "{0} {1} {2} {3} {4} {5} {6}".format(tunuser, tunpass, sshHost, sshPort, ip, self._listenPort, '9') # Extra data extra = { 'width': width, 'height': height, 'connection': self._connection, '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, userService.uuid, transport.uuid, os, username, password, extra) def getHtmlComponent(self, theId, os, componentId): # We use helper to keep this clean return getHtmlComponent(self.__module__, componentId)
class WinDomainOsManager(WindowsOsManager): typeName = _('Windows Domain OS Manager') typeType = 'WinDomainManager' typeDescription = _('Os Manager to control windows machines with domain.') 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=64, 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')) # 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: if values['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!')) if values['account'].find('\\') != -1: raise osmanagers.OSManager.ValidationException( _('DOM\\USER form is not allowed!')) if values['password'] == '': raise osmanagers.OSManager.ValidationException( _('Must provide a password for the account!')) self._domain = values['domain'] self._ou = values['ou'].strip() self._account = values['account'] 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' else: self._domain = "" self._ou = "" self._account = "" self._password = "" self._group = "" self._serverHint = "" self._removeOnExit = 'n' self._ssl = 'n' # self._ou = self._ou.replace(' ', ''), do not remove spaces if self._domain != '' and self._ou != '': lpath = 'dc=' + ',dc='.join( (s.lower() for s in self._domain.split('.'))) if self._ou.lower().find(lpath) == -1: self._ou += ',' + lpath def __getServerList(self): 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 (six.text_type(server.target)[:-1], server.port) def __connectLdap(self, servers=None): """ Tries to connect to LDAP Raises an exception if not found: dns.resolver.NXDOMAIN ldaputil.LDAPError """ if servers is None: servers = self.__getServerList() account = self._account if account.find('@') == -1: account += '@' + self._domain _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' try: return ldaputil.connection(account, self._password, server[0], port, ssl=ssl, timeout=10, debug=False) except Exception as e: _str = 'Error: {}'.format(e) raise ldaputil.LDAPError(_str) def __getGroup(self, l): 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)) except StopIteration: obj = None if obj is None: return None return obj['dn'] # Returns the DN def __getMachine(self, l, machineName): # if self._ou: # base = self._ou # else: base = ','.join(['DC=' + i for i in self._domain.split('.')]) fltr = '(&(objectClass=computer)(sAMAccountName={}$))'.format( ldaputil.escape(machineName)) try: obj = next(ldaputil.getAsDict(l, base, fltr, ['dn'], sizeLimit=50)) except StopIteration: obj = None if obj is None: return None return obj['dn'] # Returns the DN def readyReceived(self, userService, data): # No group to add if self._group == '': return 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: try: l = self.__connectLdap(servers=(s, )) machine = self.__getMachine(l, userService.friendly_name) group = self.__getGroup(l) # # # Direct LDAP operation "modify", maybe this need to be added to ldaputil? :) # # l.modify_s(group, ((ldap.MOD_ADD, 'member', machine), )) # @UndefinedVariable error = None break except dns.resolver.NXDOMAIN: # No domain found, log it and pass logger.warning('Could not find _ldap._tcp.' + 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 # 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) except Exception as e: error = "Could not add machine {} to group {}: {}".format( userService.friendly_name, self._group, e) # logger.exception('Ldap Exception caught') if error is not None: 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) # If no removal requested, just return if self._removeOnExit != 'y': return if not '.' in self._domain: logger.info('Releasing from a not FQDN domain is not supported') return try: l = self.__connectLdap() except dns.resolver.NXDOMAIN: # No domain found, log it and pass logger.warning('Could not find _ldap._tcp.' + self._domain) log.doLog( service, 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) return except Exception: logger.exception('Exception caught') return try: res = self.__getMachine(l, service.friendly_name) if res is None: raise Exception( 'Machine {} not found on AD (permissions?)'.format( service.friendly_name)) ldaputil.recursive_delete(l, res) except IndexError: logger.error('Error deleting {} from BASE {}'.format( service.friendly_name, self._ou)) except Exception: logger.exception('Deleting from AD: ') def check(self): try: l = self.__connectLdap() except ldaputil.LDAPError as e: return _('Check error: {0}').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) ] except Exception as e: logger.exception('Exception ') return [False, str(e)] try: l.search_st(self._ou, ldap.SCOPE_BASE) # @UndefinedVariable except ldaputil.LDAPError as e: return _('Check error: {0}').format(self.__getLdapError(e)) # Group if self._group != '': if self.__getGroup(l) is None: return _( 'Check Error: group "{}" not found (using "cn" to locate it)' ).format(self._group) return _('Server check was successful') @staticmethod def test(env, data): logger.debug('Test invoked') wd = None try: wd = WinDomainOsManager(env, data) logger.debug(wd) try: l = wd.__connectLdap() except ldaputil.LDAPError as e: return [ False, _('Could not access AD using LDAP ({0})').format( wd.__getLdapError(e)) ] ou = wd._ou 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)) except ldaputil.LDAPError: if 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) ] except dns.resolver.NXDOMAIN: 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 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 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 marshal(self): base = super(WinDomainOsManager, self).marshal() ''' Serializes the os manager data so we can store it in database ''' return '\t'.join([ 'v4', self._domain, self._ou, self._account, CryptoManager.manager().encrypt(self._password), encoders.encode(base, 'hex', asText=True), 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.manager().decrypt(data[4]) if data[0] in ('v2', 'v3', 'v4'): self._group = data[6] else: self._group = '' if data[0] in ('v3', 'v4'): self._serverHint = data[7] else: self._serverHint = '' if data[0] == 'v4': self._ssl = data[8] self._removeOnExit = data[9] else: self._ssl = 'n' self._removeOnExit = 'y' super(WinDomainOsManager, self).unmarshal(encoders.decode(data[5], 'hex')) def valuesDict(self): dct = super(WinDomainOsManager, self).valuesDict() dct['domain'] = self._domain dct['ou'] = self._ou dct['account'] = self._account dct['password'] = self._password dct['grp'] = self._group dct['serverHint'] = self._serverHint dct['ssl'] = self._ssl == 'y' dct['removeOnExit'] = self._removeOnExit == 'y' return dct
class SimpleLDAPAuthenticator(Authenticator): host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('Ldap Server IP or Hostname'), required=True) port = gui.NumericField( length=5, label=_('Port'), defvalue='389', order=2, tooltip=_('Ldap port (usually 389 for non ssl and 636 for ssl)'), required=True) ssl = gui.CheckBoxField( label=_('Use SSL'), order=3, tooltip= _('If checked, the connection will be ssl, using port 636 instead of 389' )) username = gui.TextField( length=64, label=_('Ldap User'), order=4, tooltip=_('Username with read privileges on the base selected'), required=True, tab=gui.CREDENTIALS_TAB) password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the ldap user'), required=True, tab=gui.CREDENTIALS_TAB) timeout = gui.NumericField( length=3, label=_('Timeout'), defvalue='10', order=6, tooltip=_('Timeout in seconds of connection to LDAP'), required=True, minValue=1) ldapBase = gui.TextField( length=64, label=_('Base'), order=7, tooltip=_('Common search base (used for "users" and "groups")'), required=True, tab=_('Ldap info')) userClass = gui.TextField( length=64, label=_('User class'), defvalue='posixAccount', order=8, tooltip=_('Class for LDAP users (normally posixAccount)'), required=True, tab=_('Ldap info')) userIdAttr = gui.TextField( length=64, label=_('User Id Attr'), defvalue='uid', order=9, tooltip=_('Attribute that contains the user id'), required=True, tab=_('Ldap info')) userNameAttr = gui.TextField( length=64, label=_('User Name Attr'), defvalue='uid', order=10, tooltip= _('Attributes that contains the user name (list of comma separated values)' ), required=True, tab=_('Ldap info')) groupClass = gui.TextField( length=64, label=_('Group class'), defvalue='posixGroup', order=11, tooltip=_('Class for LDAP groups (normally poxisGroup)'), required=True, tab=_('Ldap info')) groupIdAttr = gui.TextField( length=64, label=_('Group Id Attr'), defvalue='cn', order=12, tooltip=_('Attribute that contains the group id'), required=True, tab=_('Ldap info')) memberAttr = gui.TextField( length=64, label=_('Group membership attr'), defvalue='memberUid', order=13, tooltip=_( 'Attribute of the group that contains the users belonging to it'), required=True, tab=_('Ldap info')) typeName = _('SimpleLDAP Authenticator') typeType = 'SimpleLdapAuthenticator' typeDescription = _('Simple LDAP authenticator') iconFile = 'auth.png' # If it has and external source where to get "new" users (groups must be declared inside UDS) isExternalSource = True # If we need to enter the password for this user needsPassword = False # Label for username field userNameLabel = _('Username') # Label for group field groupNameLabel = _("Group") # Label for password field passwordLabel = _("Password") def __init__(self, dbAuth, environment, values=None): super(SimpleLDAPAuthenticator, self).__init__(dbAuth, environment, values) if values is not None: self._host = values['host'] self._port = values['port'] self._ssl = gui.strToBool(values['ssl']) self._username = values['username'] self._password = values['password'] self._timeout = values['timeout'] self._ldapBase = values['ldapBase'] self._userClass = values['userClass'] self._groupClass = values['groupClass'] self._userIdAttr = values['userIdAttr'] self._groupIdAttr = values['groupIdAttr'] self._memberAttr = values['memberAttr'] self._userNameAttr = values['userNameAttr'].replace( ' ', '') # Removes white spaces else: self._host = None self._port = None self._ssl = None self._username = None self._password = None self._timeout = None self._ldapBase = None self._userClass = None self._groupClass = None self._userIdAttr = None self._groupIdAttr = None self._memberAttr = None self._userNameAttr = None self._connection = None def valuesDict(self): return { 'host': self._host, 'port': self._port, 'ssl': gui.boolToStr(self._ssl), 'username': self._username, 'password': self._password, 'timeout': self._timeout, 'ldapBase': self._ldapBase, 'userClass': self._userClass, 'groupClass': self._groupClass, 'userIdAttr': self._userIdAttr, 'groupIdAttr': self._groupIdAttr, 'memberAttr': self._memberAttr, 'userNameAttr': self._userNameAttr } def __str__(self): return "Ldap Auth: {0}:{1}@{2}:{3}, base = {4}, userClass = {5}, groupClass = {6}, userIdAttr = {7}, groupIdAttr = {8}, memberAttr = {9}, userName attr = {10}".format( self._username, self._password, self._host, self._port, self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr) def marshal(self): return '\t'.join([ 'v1', self._host, self._port, gui.boolToStr(self._ssl), self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr ]) def unmarshal(self, str_): data = str_.split('\t') if data[0] == 'v1': logger.debug("Data: {0}".format(data[1:])) self._host, self._port, self._ssl, self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr = data[ 1:] self._ssl = gui.strToBool(self._ssl) def __connection(self, username=None, password=None): if self._connection is None or username is not None: # We want this method also to check credentials if isinstance(username, six.text_type): username = username.encode('utf8') if isinstance(password, six.text_type): password = password.encode('utf8') l = None cache = False try: # ldap.set_option(ldap.OPT_DEBUG_LEVEL, 9) ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) schema = self._ssl and 'ldaps' or 'ldap' port = self._port != '389' and ':' + self._port or '' uri = "%s://%s%s" % (schema, self._host, port) logger.debug('Ldap uri: {0}'.format(uri)) l = ldap.initialize(uri=uri) l.network_timeout = l.timeout = int(self._timeout) l.protocol_version = ldap.VERSION3 if username is None: cache = True username = self._username password = self._password l.simple_bind_s(who=username, cred=password) except ldap.LDAPError as e: str_ = _('Ldap connection error: ') if isinstance(e.message, dict): str_ += ', '.join(e.message.get('info', ''), e.message.get('desc')) else: str_ += six.text_type(e) raise Exception(str_) if cache is True: self._connection = l else: return l # Do not cache nor overwrite "global" connection return self._connection def __getUser(self, username): try: con = self.__connection() filter_ = '(&(objectClass=%s)(%s=%s))' % ( self._userClass, self._userIdAttr, ldap.filter.escape_filter_chars(username, 0)) attrlist = [ i.encode('utf-8') for i in self._userNameAttr.split(',') + [self._userIdAttr] ] logger.debug('Getuser filter_: {0}, attr list: {1}'.format( filter_, attrlist)) res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr=filter_, attrlist=attrlist, sizelimit=LDAP_RESULT_LIMIT)[0] usr = dict((k, '') for k in attrlist) usr.update(res[1]) usr.update({'dn': res[0], '_id': username}) logger.debug('Usr: {0}'.format(usr)) return usr except Exception: logger.exception('Exception:') return None def __getGroup(self, groupName): try: con = self.__connection() filter_ = '(&(objectClass=%s)(%s=%s))' % ( self._groupClass, self._groupIdAttr, groupName) attrlist = [self._memberAttr.encode('utf-8')] logger.debug('Getgroup filter_: {0}, attr list {1}'.format( filter_, attrlist)) res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr=filter_, attrlist=attrlist, sizelimit=LDAP_RESULT_LIMIT * 10)[0] grp = dict((k, ['']) for k in attrlist) grp.update(res[1]) grp.update({'dn': res[0], '_id': groupName}) logger.debug('Group: {0}'.format(grp)) return grp except Exception: logger.exception('Exception:') return None def __getGroups(self, usr): try: con = self.__connection() filter_ = '(&(objectClass=%s)(|(%s=%s)(%s=%s)))' % ( self._groupClass, self._memberAttr, usr['_id'], self._memberAttr, usr['dn']) logger.debug('Filter: {0}'.format(filter_)) res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr=filter_, attrlist=[self._groupIdAttr.encode('utf8')], sizelimit=LDAP_RESULT_LIMIT * 10) groups = {} for g in res: v = g[1][self._groupIdAttr] if type(v) is not list: v = [v] for gg in v: groups[str(gg)] = g[0] logger.debug('Groups: {0}'.format(groups)) return groups except Exception: logger.exception('Exception at __getGroups') return {} def __getUserRealName(self, usr): ''' Tries to extract the real name for this user. Will return all atttributes (joint) specified in _userNameAttr (comma separated). ''' return ' '.join([(type(usr.get(id_, '')) is list and ' '.join( (str(k) for k in usr.get(id_, ''))) or str(usr.get(id_, ''))) for id_ in self._userNameAttr.split(',')]).strip() def authenticate(self, username, credentials, groupsManager): ''' Must authenticate the user. We can have to different situations here: 1.- The authenticator is external source, what means that users may be unknown to system before callig this 2.- The authenticator isn't external source, what means that users have been manually added to system and are known before this call We receive the username, the credentials used (normally password, but can be a public key or something related to pk) and a group manager. The group manager is responsible for letting know the authenticator which groups we currently has active. @see: uds.core.auths.GroupsManager ''' try: # Locate the user at LDAP usr = self.__getUser(username) if usr is None: return False # Let's see first if it credentials are fine self.__connection( usr['dn'], credentials) # Will raise an exception if it can't connect groupsManager.validate(self.__getGroups(usr).keys()) return True except Exception: return False def createUser(self, usrData): ''' Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to @param usrData: Contains data received from user directly, that is, a dictionary with at least: name, realName, comments, state & password @return: Raises an exception (AuthException) it things didn't went fine ''' res = self.__getUser(usrData['name']) if res is None: raise AuthenticatorException(_('Username not found')) # Fills back realName field usrData['real_name'] = self.__getUserRealName(res) def getRealName(self, username): ''' Tries to get the real name of an user ''' res = self.__getUser(username) if res is None: return username return self.__getUserRealName(res) def modifyUser(self, usrData): ''' We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..) Modify user has no reason on external sources, so it will never be used (probably) Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to @param usrData: Contains data received from user directly, that is, a dictionary with at least: name, realName, comments, state & password @return: Raises an exception it things don't goes fine ''' return self.createUser(usrData) def createGroup(self, groupData): ''' We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..) External sources already has its own groups and, at most, it can check if it exists on external source before accepting it Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to @params groupData: a dict that has, at least, name, comments and active @return: Raises an exception it things don't goes fine ''' res = self.__getGroup(groupData['name']) if res is None: raise AuthenticatorException(_('Group not found')) def getGroups(self, username, groupsManager): ''' Looks for the real groups to which the specified user belongs Updates groups manager with valid groups Remember to override it in derived authentication if needed (external auths will need this, for internal authenticators this is never used) ''' user = self.__getUser(username) if user is None: raise AuthenticatorException(_('Username not found')) groupsManager.validate(self.__getGroups(user).keys()) def searchUsers(self, pattern): try: con = self.__connection() res = [] for r in con.search_ext_s( base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(&(objectClass=%s)(%s=%s*))' % (self._userClass, self._userIdAttr, pattern), sizelimit=LDAP_RESULT_LIMIT): usrId = r[1].get(self._userIdAttr, '') usrId = type(usrId) == list and usrId[0] or usrId res.append({'id': usrId, 'name': self.__getUserRealName(r[1])}) return res except Exception: logger.exception("Exception: ") raise AuthenticatorException( _('Too many results, be more specific')) def searchGroups(self, pattern): try: con = self.__connection() res = [] for r in con.search_ext_s( base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(&(objectClass=%s)(%s=%s*))' % (self._groupClass, self._groupIdAttr, pattern), sizelimit=LDAP_RESULT_LIMIT): grpId = r[1].get(self._groupIdAttr, '') grpId = type(grpId) == list and grpId[0] or grpId res.append({'id': grpId, 'name': grpId}) return res except Exception: logger.exception("Exception: ") raise AuthenticatorException( _('Too many results, be more specific')) @staticmethod def test(env, data): try: auth = SimpleLDAPAuthenticator(None, env, data) return auth.testConnection() except Exception as e: logger.error( "Exception found testing Simple LDAP auth {0}: {1}".format( e.__class__, e)) return [False, "Error testing connection"] def testConnection(self): try: con = self.__connection() except Exception as e: return [False, str(e)] try: con.search_s(base=self._ldapBase, scope=ldap.SCOPE_BASE) except Exception: return [False, _('Ldap search base is incorrect')] try: if len( con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(objectClass=%s)' % self._userClass, sizelimit=1)) == 1: raise Exception() return [ False, _('Ldap user class seems to be incorrect (no user found by that class)' ) ] except Exception as e: # If found 1 or more, all right pass try: if len( con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(objectClass=%s)' % self._groupClass, sizelimit=1)) == 1: raise Exception() return [ False, _('Ldap group class seems to be incorrect (no group found by that class)' ) ] except Exception as e: # If found 1 or more, all right pass try: if len( con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(%s=*)' % self._userIdAttr, sizelimit=1)) == 1: raise Exception() return [ False, _('Ldap user id attribute seems to be incorrect (no user found by that attribute)' ) ] except Exception as e: # If found 1 or more, all right pass try: if len( con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(%s=*)' % self._groupIdAttr, sizelimit=1)) == 1: raise Exception() return [ False, _('Ldap group id attribute seems to be incorrect (no group found by that attribute)' ) ] except Exception as e: # If found 1 or more, all right pass # Now test objectclass and attribute of users try: if len( con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(&(objectClass=%s)(%s=*))' % (self._userClass, self._userIdAttr), sizelimit=1)) == 1: raise Exception() return [ False, _('Ldap user class or user id attr is probably wrong (can\'t find any user with both conditions)' ) ] except Exception as e: # If found 1 or more, all right pass # And group part, with membership try: res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(&(objectClass=%s)(%s=*))' % (self._groupClass, self._groupIdAttr), attrlist=[self._memberAttr.encode('utf-8')]) if len(res) == 0: raise Exception( _('Ldap group class or group id attr is probably wrong (can\'t find any group with both conditions)' )) ok = False for r in res: if self._memberAttr in r[1]: ok = True break if ok is False: raise Exception( _('Can\'t locate any group with the membership attribute specified' )) except Exception as e: return [False, six.text_type(e)] return [ True, _("Connection params seem correct, test was succesfully executed") ]
class LinuxRandomPassManager(LinuxOsManager): typeName = _('Linux Random Password OS Manager') typeType = 'LinRandomPasswordManager' typeDescription = _( 'Os Manager to control linux machines, with user password set randomly.' ) iconFile = 'losmanager.png' # Apart form data from linux 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) # Inherits base "onLogout" onLogout = LinuxOsManager.onLogout idle = LinuxOsManager.idle def __init__(self, environment, values): super(LinuxRandomPassManager, self).__init__(environment, values) if values is not None: if values['userAccount'] == '': raise osmanagers.OSManager.ValidationException( _('Must provide an user account!!!')) self._userAccount = values['userAccount'] else: self._userAccount = '' def release(self, service): super(LinuxRandomPassManager, self).release(service) def processUserPassword(self, service, username, password): if username == self._userAccount: return [username, service.recoverValue('linOsRandomPass')] return [username, password] def genPassword(self, service): import random import string randomPass = service.recoverValue('linOsRandomPass') if randomPass is None: randomPass = ''.join( random.choice(string.ascii_letters + string.digits) for _ in range(16)) service.storeValue('linOsRandomPass', 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\t{2}'.format(self.getName(service), self._userAccount, self.genPassword(service)) def infoValue(self, service): return 'rename\r{0}\t{1}\t\t{2}'.format(self.getName(service), self._userAccount, self.genPassword(service)) def marshal(self): base = super(LinuxRandomPassManager, self).marshal() ''' Serializes the os manager data so we can store it in database ''' return '\t'.join([ 'v1', self._userAccount, 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] super(LinuxRandomPassManager, self).unmarshal(encoders.decode(data[2], 'hex')) def valuesDict(self): dic = super(LinuxRandomPassManager, self).valuesDict() dic['userAccount'] = self._userAccount return dic
class BaseX2GOTransport(Transport): """ Provides access via X2GO 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 = 'x2go.png' protocol = protocols.X2GO supportedOss = (OsDetector.Linux, OsDetector.Windows) fixedName = gui.TextField( order=2, label=_('Username'), tooltip=_( 'If not empty, this username will be always used as credential'), tab=gui.CREDENTIALS_TAB) fullScreen = gui.CheckBoxField( order=10, label=_('Show fullscreen'), tooltip=_('If checked, viewer will be shown on fullscreen mode-'), tab=gui.PARAMETERS_TAB) desktopType = gui.ChoiceField( label=_('Desktop'), order=11, tooltip=_('Desktop session'), values=[ { 'id': 'XFCE', 'text': 'Xfce' }, { 'id': 'MATE', 'text': 'Mate' }, { 'id': 'LXDE', 'text': 'Lxde' }, { 'id': 'GNOME', 'text': 'Gnome (see docs)' }, { 'id': 'KDE', 'text': 'Kde (see docs)' }, # {'id': 'UNITY', 'text': 'Unity (see docs)'}, { 'id': 'gnome-session-cinnamon', 'text': 'Cinnamon 1.4 (see docs)' }, { 'id': 'gnome-session-cinnamon2d', 'text': 'Cinnamon 2.2 (see docs)' }, { 'id': 'UDSVAPP', 'text': 'UDS vAPP' }, ], tab=gui.PARAMETERS_TAB) customCmd = gui.TextField( order=12, label=_('vAPP'), tooltip= _('If UDS vAPP is selected as "Desktop", the FULL PATH of the app to be executed. If UDS vAPP is not selected, this field will be ignored.' ), tab=gui.PARAMETERS_TAB) sound = gui.CheckBoxField(order=13, label=_('Enable sound'), tooltip=_('If checked, sound will be available'), defvalue=gui.TRUE, tab=gui.PARAMETERS_TAB) exports = gui.CheckBoxField( order=14, label=_('Redirect home folder'), tooltip= _('If checked, user home folder will be redirected. (On linux, also redirects /media)' ), defvalue=gui.FALSE, tab=gui.PARAMETERS_TAB) speed = gui.ChoiceField(label=_('Speed'), order=15, tooltip=_('Connection speed'), defvalue='3', values=[ { 'id': '0', 'text': 'MODEM' }, { 'id': '1', 'text': 'ISDN' }, { 'id': '2', 'text': 'ADSL' }, { 'id': '3', 'text': 'WAN' }, { 'id': '4', 'text': 'LAN' }, ], tab=gui.PARAMETERS_TAB) soundType = gui.ChoiceField(label=_('Sound'), order=30, tooltip=_('Sound server'), defvalue='pulse', values=[ { 'id': 'pulse', 'text': 'Pulse' }, { 'id': 'esd', 'text': 'ESD' }, ], tab=gui.ADVANCED_TAB) keyboardLayout = gui.TextField( label=_('Keyboard'), order=31, tooltip=_( 'Keyboard layout (es, us, fr, ...). Empty value means autodetect.' ), defvalue='', tab=gui.ADVANCED_TAB) # 'nopack', '8', '64', '256', '512', '4k', '32k', '64k', '256k', '2m', '16m' # '256-rdp', '256-rdp-compressed', '32k-rdp', '32k-rdp-compressed', '64k-rdp' # '64k-rdp-compressed', '16m-rdp', '16m-rdp-compressed' # 'rfb-hextile', 'rfb-tight', 'rfb-tight-compressed' # '8-tight', '64-tight', '256-tight', '512-tight', '4k-tight', '32k-tight' # '64k-tight', '256k-tight', '2m-tight', '16m-tight' # '8-jpeg-%', '64-jpeg', '256-jpeg', '512-jpeg', '4k-jpeg', '32k-jpeg' # '64k-jpeg', '256k-jpeg', '2m-jpeg', '16m-jpeg-%' # '8-png-jpeg-%', '64-png-jpeg', '256-png-jpeg', '512-png-jpeg', '4k-png-jpeg' # '32k-png-jpeg', '64k-png-jpeg', '256k-png-jpeg', '2m-png-jpeg', '16m-png-jpeg-%' # '8-png-%', '64-png', '256-png', '512-png', '4k-png' # '32k-png', '64k-png', '256k-png', '2m-png', '16m-png-%' # '16m-rgb-%', '16m-rle-%' pack = gui.TextField(label=_('Pack'), order=32, tooltip=_('Pack format. Change with care!'), defvalue='16m-jpeg', tab=gui.ADVANCED_TAB) quality = gui.NumericField( label=_('Quality'), order=33, tooltip=_('Quality value used on some pack formats.'), length=1, defvalue='6', minValue=1, maxValue=9, required=True, tab=gui.ADVANCED_TAB) def isAvailableFor(self, userService, 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, '22') 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 != '': username = self.fixedName.value # Fix username/password acording to os manager username, password = service.processUserPassword(username, password) return { 'protocol': self.protocol, 'username': username, 'password': '' } def getConnectionInfo( self, service, user, password ): # Password is ignored in this transport, auth is done using SSH return self.processUserPassword(service, user, password) def genKeyPairForSsh(self): """ Generates a key pair for use with x2go The private part is used by client the public part must be "appended" to authorized_keys if it is not already added. If .ssh folder does not exists, it must be created if authorized_keys does not exists, it must be created On key adition, we can look for every key that has a "UDS@X2GOCLIENT" as comment, so we can remove them before adding new ones Windows (tested): C:\Program Files (x86)\\x2goclient>x2goclient.exe --session-conf=c:/temp/sessions --session=UDS/test-session --close-disconnect --hide --no-menu Linux (tested): HOME=[temporal folder, where we create a .x2goclient folder and a sessions inside] pyhoca-cli -P UDS/test-session """ key = paramiko.RSAKey.generate(SSH_KEY_LENGTH) privFile = six.StringIO() key.write_private_key(privFile) priv = privFile.getvalue() pub = key.get_base64( ) # 'ssh-rsa {} UDS@X2GOCLIENT'.format(key.get_base64()) return priv, pub def getAuthorizeScript(self, user, pubKey): return self.getScript('scripts/authorize.py').replace( '__USER__', user).replace('__KEY__', pubKey) def getAndPushKey(self, user, userService): priv, pub = self.genKeyPairForSsh() authScript = self.getAuthorizeScript(user, pub) userServiceManager().sendScript(userService, authScript) return priv, pub def getScript(self, script): with open(os.path.join(os.path.dirname(__file__), script)) as f: data = f.read() return data
class NXTransport(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 ''' typeName = _('NX Transport (direct)') typeType = 'NXTransport' typeDescription = _('NX Transport for direct connection') iconFile = 'nx.png' needsJava = True # If this transport needs java for rendering protocol = protocols.NX 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')) listenPort = gui.NumericField( label=_('Listening port'), length=5, order=4, tooltip=_('Listening port of NX (ssh) at client machine'), defvalue='22') connection = gui.ChoiceField( label=_('Connection'), order=6, tooltip=_('Connection speed for this transport (quality)'), values=[{ 'id': 'modem', 'text': 'modem' }, { 'id': 'isdn', 'text': 'isdn' }, { 'id': 'adsl', 'text': 'adsl' }, { 'id': 'wan', 'text': 'wan' }, { 'id': 'lan', 'text': 'lan' }]) session = gui.ChoiceField(label=_('Session'), order=7, tooltip=_('Desktop session'), values=[ { 'id': 'gnome', 'text': 'gnome' }, { 'id': 'kde', 'text': 'kde' }, { 'id': 'cde', 'text': 'cde' }, ]) cacheDisk = gui.ChoiceField(label=_('Disk Cache'), order=8, tooltip=_('Cache size en Mb stored at disk'), values=[ { 'id': '0', 'text': '0 Mb' }, { 'id': '32', 'text': '32 Mb' }, { 'id': '64', 'text': '64 Mb' }, { 'id': '128', 'text': '128 Mb' }, { 'id': '256', 'text': '256 Mb' }, { 'id': '512', 'text': '512 Mb' }, ]) cacheMem = gui.ChoiceField(label=_('Memory Cache'), order=9, tooltip=_('Cache size en Mb kept at memory'), values=[ { 'id': '4', 'text': '4 Mb' }, { 'id': '8', 'text': '8 Mb' }, { 'id': '16', 'text': '16 Mb' }, { 'id': '32', 'text': '32 Mb' }, { 'id': '64', 'text': '64 Mb' }, { 'id': '128', 'text': '128 Mb' }, ]) def __init__(self, environment, values=None): super(NXTransport, self).__init__(environment, values) if values is not None: self._useEmptyCreds = gui.strToBool(values['useEmptyCreds']) self._fixedName = values['fixedName'] self._fixedPassword = values['fixedPassword'] self._listenPort = values['listenPort'] self._connection = values['connection'] self._session = values['session'] self._cacheDisk = values['cacheDisk'] self._cacheMem = values['cacheMem'] else: self._useEmptyCreds = '' self._fixedName = '' self._fixedPassword = '' self._listenPort = '' self._connection = '' self._session = '' self._cacheDisk = '' self._cacheMem = '' def marshal(self): ''' Serializes the transport data so we can store it in database ''' return str.join('\t', [ 'v1', gui.boolToStr(self._useEmptyCreds), self._fixedName, self._fixedPassword, self._listenPort, self._connection, self._session, self._cacheDisk, self._cacheMem ]) def unmarshal(self, string): data = string.split('\t') if data[0] == 'v1': self._useEmptyCreds = gui.strToBool(data[1]) self._fixedName, self._fixedPassword, self._listenPort, self._connection, self._session, self._cacheDisk, self._cacheMem = data[ 2:] def valuesDict(self): return { 'useEmptyCreds': gui.boolToStr(self._useEmptyCreds), 'fixedName': self._fixedName, 'fixedPassword': self._fixedPassword, 'listenPort': self._listenPort, 'connection': self._connection, 'session': self._session, 'cacheDisk': self._cacheDisk, 'cacheMem': self._cacheMem } 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, self._listenPort) 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 renderForHtml(self, userService, transport, ip, os, user, password): prefs = user.prefs('nx') username = user.getUsernameForAuth() proc = username.split('@') username = proc[0] if self._fixedName is not '': username = self._fixedName if self._fixedPassword is not '': password = self._fixedPassword if self._useEmptyCreds is True: username, password = '', '' # We have the credentials right now, let os manager width, height = CommonPrefs.getWidthHeight(prefs) # Extra data extra = { 'width': width, 'height': height, 'port': self._listenPort, 'connection': self._connection, '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, userService.uuid, transport.uuid, ip, os, username, password, extra) def getHtmlComponent(self, theId, os, componentId): # We use helper to keep this clean return getHtmlComponent(self.__module__, componentId)
class WinDomainOsManager(WindowsOsManager): typeName = _('Windows Domain OS Manager') typeType = 'WinDomainManager' typeDescription = _('Os Manager to control windows machines with domain.') 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=64, 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')) serverHint = gui.TextField( length=64, label=_('Server Hint'), order=8, tooltip= _('In case of several AD servers, which one is prefered for first access' ), 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: if values['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!')) if values['account'].find('\\') != -1: raise osmanagers.OSManager.ValidationException( _('DOM\\USER form is not allowed!')) if values['password'] == '': raise osmanagers.OSManager.ValidationException( _('Must provide a password for the account!')) self._domain = values['domain'] self._ou = values['ou'].strip() self._account = values['account'] self._password = values['password'] self._group = values['grp'].strip() self._serverHint = values['serverHint'].strip() else: self._domain = "" self._ou = "" self._account = "" self._password = "" self._group = "" self._serverHint = "" # self._ou = self._ou.replace(' ', ''), do not remove spaces if self._domain != '' and self._ou != '': lpath = 'dc=' + ',dc='.join( (s.lower() for s in self._domain.split('.'))) if self._ou.lower().find(lpath) == -1: self._ou += ',' + lpath def __getLdapError(self, e): logger.debug('Ldap Error: {0} {1}'.format(e, e.message)) _str = '' if type(e.message) == dict: # _str += e.message.has_key('info') and e.message['info'] + ',' or '' _str += e.message.get('desc', '') else: _str += str(e) return _str def __getServerList(self): 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 (six.text_type(server.target)[:-1], server.port) def __connectLdap(self, servers=None): ''' Tries to connect to LDAP Raises an exception if not found: dns.resolver.NXDOMAIN ldap.LDAPError ''' if servers is None: servers = self.__getServerList() _str = "No servers found" for server in servers: _str = '' try: uri = "%s://%s:%d" % ('ldap', server[0], server[1]) logger.debug('URI: {0}'.format(uri)) ldap.set_option( ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) # Disable certificate check l = ldap.initialize(uri=uri) l.set_option(ldap.OPT_REFERRALS, 0) l.network_timeout = l.timeout = 5 l.protocol_version = ldap.VERSION3 account = self._account if account.find('@') == -1: account += '@' + self._domain logger.debug('Account data: {0}, {1}, {2}, {3}'.format( self._account, self._domain, account, self._password)) l.simple_bind_s(who=account, cred=self._password) return l except ldap.LDAPError as e: _str = self.__getLdapError(e) raise ldap.LDAPError(_str) def __getGroup(self, l): base = ','.join(['DC=' + i for i in self._domain.split('.')]) group = self._group.replace('\\', '\\\\').replace('(', '\\(').replace(')', '\\)') res = l.search_ext_s( base=base, scope=ldap.SCOPE_SUBTREE, filterstr="(&(objectClass=group)(|(cn={0})(sAMAccountName={0})))". format(group), attrlist=[b'dn']) if res[0] is None: return None return res[0][0] # Returns the DN def __getMachine(self, l, machineName): if self._ou: ou = self._ou else: ou = ','.join(['DC=' + i for i in self._domain.split('.')]) fltr = '(&(objectClass=computer)(sAMAccountName={}$))'.format( machineName) res = l.search_ext_s(base=ou, scope=ldap.SCOPE_SUBTREE, filterstr=fltr, attrlist=[b'dn']) if res[0] is None: return None return res[0][0] # Returns the DN def readyReceived(self, userService, data): # No group to add if self._group == '': return 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: try: l = self.__connectLdap(servers=(s, )) machine = self.__getMachine(l, userService.friendly_name) group = self.__getGroup(l) l.modify_s(group, ((ldap.MOD_ADD, 'member', machine), )) error = None break except dns.resolver.NXDOMAIN: # No domain found, log it and pass logger.warn('Could not find _ldap._tcp.' + 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: # Already added this machine to this group, pass error = None break except ldap.LDAPError: logger.exception('Ldap Exception caught') error = "Could not remove machine from domain (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) # logger.exception('Ldap Exception caught') if error is not None: 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) if not '.' in self._domain: logger.info('Releasing from a not FQDN domain is not supported') return try: l = self.__connectLdap() except dns.resolver.NXDOMAIN: # No domain found, log it and pass logger.warn('Could not find _ldap._tcp.' + self._domain) log.doLog( service, log.WARN, "Could not remove machine from domain (_ldap._tcp.{0} not found)" .format(self._domain), log.OSMANAGER) return except ldap.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) return except Exception: logger.exception('Exception caught') return try: res = self.__getMachine(l, service.friendly_name) if res is None: raise Exception( 'Machine {} not found on AD (permissions?)'.format( service.friendly_name)) l.delete_s(res) # Remove by DN, SYNC except IndexError: logger.error('Error deleting {} from BASE {}'.format( service.friendly_name, self._ou)) except Exception: logger.exception('Deleting from AD: ') def check(self): try: l = self.__connectLdap() except ldap.LDAPError as e: return _('Check error: {0}').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) ] except Exception as e: logger.exception('Exception ') return [False, str(e)] try: l.search_st(self._ou, ldap.SCOPE_BASE) except ldap.LDAPError as e: return _('Check error: {0}').format(self.__getLdapError(e)) # Group if self._group != '': if self.__getGroup(l) is None: return _( 'Check Error: group "{}" not found (using "cn" to locate it)' ).format(self._group) return _('Server check was successful') @staticmethod def test(env, data): logger.debug('Test invoked') try: wd = WinDomainOsManager(env, data) logger.debug(wd) try: l = wd.__connectLdap() except ldap.LDAPError as e: return [ False, _('Could not access AD using LDAP ({0})').format( wd.__getLdapError(e)) ] ou = wd._ou 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) logger.debug('Result of search: {0}'.format(r)) except ldap.LDAPError: if wd._ou == '': return [ False, _('The default path {0} for computers was not found!!!'). format(ou) ] else: return [ False, _('The ou path {0} was not found!!!').format(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: logger.exception('Exception ') return [False, str(e)] 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 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 marshal(self): base = super(WinDomainOsManager, self).marshal() ''' Serializes the os manager data so we can store it in database ''' return '\t'.join([ 'v3', self._domain, self._ou, self._account, CryptoManager.manager().encrypt(self._password), base.encode('hex'), self._group, self._serverHint ]) def unmarshal(self, s): data = s.split('\t') if data[0] in ('v1', 'v2', 'v3'): self._domain = data[1] self._ou = data[2] self._account = data[3] self._password = CryptoManager.manager().decrypt(data[4]) if data[0] in ('v2', 'v3'): self._group = data[6] else: self._group = '' if data[0] == 'v3': self._serverHint = data[7] else: self._serverHint = '' super(WinDomainOsManager, self).unmarshal(data[5].decode('hex')) def valuesDict(self): dct = super(WinDomainOsManager, self).valuesDict() dct['domain'] = self._domain dct['ou'] = self._ou dct['account'] = self._account dct['password'] = self._password dct['grp'] = self._group dct['serverHint'] = self._serverHint return dct
class HTML5RDPTransport(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 """ typeName = _('HTML5 RDP') typeType = 'HTML5RDPTransport' typeDescription = _('RDP protocol using HTML5 client') iconFile = 'html5.png' ownLink = True supportedOss = OsDetector.allOss protocol = protocols.RDP group = TUNNELED_GROUP guacamoleServer = gui.TextField( label=_('Tunnel Server'), order=1, tooltip= _('Host of the tunnel server (use http/https & port if needed) as accesible from users' ), defvalue='https://', length=64, required=True, tab=gui.TUNNEL_TAB) useEmptyCreds = gui.CheckBoxField( label=_('Empty creds'), order=2, tooltip=_('If checked, the credentials used to connect will be emtpy'), tab=gui.CREDENTIALS_TAB) fixedName = gui.TextField( label=_('Username'), order=3, tooltip=_( 'If not empty, this username will be always used as credential'), tab=gui.CREDENTIALS_TAB) fixedPassword = gui.PasswordField( label=_('Password'), order=4, tooltip=_( 'If not empty, this password will be always used as credential'), tab=gui.CREDENTIALS_TAB) withoutDomain = gui.CheckBoxField( label=_('Without Domain'), order=5, tooltip= _('If checked, the domain part will always be emptied (to connecto to xrdp for example is needed)' ), tab=gui.CREDENTIALS_TAB) fixedDomain = gui.TextField( label=_('Domain'), order=6, tooltip= _('If not empty, this domain will be always used as credential (used as DOMAIN\\user)' ), tab=gui.CREDENTIALS_TAB) wallpaper = gui.CheckBoxField( label=_('Show wallpaper'), order=20, tooltip= _('If checked, the wallpaper and themes will be shown on machine (better user experience, more bandwidth)' ), tab=gui.PARAMETERS_TAB) desktopComp = gui.CheckBoxField( label=_('Allow Desk.Comp.'), order=22, tooltip=_('If checked, desktop composition will be allowed'), tab=gui.PARAMETERS_TAB) smooth = gui.CheckBoxField( label=_('Font Smoothing'), order=23, tooltip=_( 'If checked, fonts smoothing will be allowed (windows clients only)' ), tab=gui.PARAMETERS_TAB) enableAudio = gui.CheckBoxField( label=_('Enable Audio'), order=24, tooltip= _('If checked, the audio will be redirected to client (if client browser supports it)' ), tab=gui.PARAMETERS_TAB) enablePrinting = gui.CheckBoxField( label=_('Enable Printing'), order=25, tooltip= _('If checked, the printing will be redirected to client (if client browser supports it)' ), tab=gui.PARAMETERS_TAB) enableFileSharing = gui.CheckBoxField( label=_('Enable File Sharing'), order=8, tooltip= _('If checked, the user will be able to upload/download files (if client browser supports it)' ), tab=gui.PARAMETERS_TAB) serverLayout = gui.ChoiceField( order=26, label=_('Layout'), tooltip=_('Keyboards Layout of server'), required=True, values=[ gui.choiceItem('-', 'default'), gui.choiceItem('en-us-qwerty', _('English (US) keyboard')), gui.choiceItem('de-de-qwertz', _('German keyboard (qwertz)')), gui.choiceItem('fr-fr-azerty', _('French keyboard (azerty)')), gui.choiceItem('it-it-qwerty', _('Italian keyboard')), gui.choiceItem('sv-se-qwerty', _('Swedish keyboard')), gui.choiceItem('failsafe', _('Failsafe')), ], defvalue='-', tab=gui.PARAMETERS_TAB) security = gui.ChoiceField( order=27, label=_('Security'), tooltip=_('Connection security mode for Guacamole RDP connection'), required=True, values=[ gui.choiceItem( 'any', _('Any (Allow the server to choose the type of auth)')), gui.choiceItem( 'rdp', _('RDP (Standard RDP encryption. Should be supported by all servers)' )), gui.choiceItem( 'nla', _('NLA (Network Layer authentication. Requires VALID username&password, or connection will fail)' )), gui.choiceItem('tls', _('TLS (Transport Security Layer encryption)')), ], defvalue='rdp', tab=gui.PARAMETERS_TAB) ticketValidity = gui.NumericField( length=3, label=_('Ticket Validity'), defvalue='60', order=90, tooltip= _('Allowed time, in seconds, for HTML5 client to reload data from UDS Broker. The default value of 60 is recommended.' ), required=True, minValue=60, tab=gui.ADVANCED_TAB) def initialize(self, values): if values is None: return self.guacamoleServer.value = self.guacamoleServer.value.strip() if self.guacamoleServer.value[0:4] != 'http': raise Transport.ValidationException( _('The server must be http or https')) if self.useEmptyCreds.isTrue() and self.security.value != 'rdp': raise Transport.ValidationException( _('Empty credentials (on Credentials tab) is only allowed with Security level (on Parameters tab) set to "RDP"' )) # Same check as normal RDP transport def isAvailableFor(self, userService, 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 self.testServer(userService, 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 != '': username = self.fixedName.value proc = username.split('@') if len(proc) > 1: domain = proc[1] else: domain = '' username = proc[0] if self.fixedPassword.value != '': password = self.fixedPassword.value if self.fixedDomain.value != '': 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 getLink(self, userService, transport, ip, os, user, password, request): ci = self.processUserPassword(userService, user, password) username, password, domain = ci['username'], ci['password'], ci[ 'domain'] if domain != '': username = domain + '\\' + username scrambler = cryptoManager().randomString(32) passwordCrypted = cryptoManager().symCrypt(password, scrambler) # Build params dict params = { 'protocol': 'rdp', 'hostname': ip, 'username': username, 'password': passwordCrypted, 'ignore-cert': 'true', 'security': self.security.value, 'drive-path': '/share/{}'.format(user.uuid), 'create-drive-path': 'true' } if self.enableFileSharing.isTrue(): params['enable-drive'] = 'true' if self.serverLayout.value != '-': params['server-layout'] = self.serverLayout.value if self.enableAudio.isTrue() is False: params['disable-audio'] = 'true' if self.enablePrinting.isTrue() is True: params['enable-printing'] = 'true' if self.wallpaper.isTrue() is True: params['enable-wallpaper'] = 'true' if self.desktopComp.isTrue() is True: params['enable-desktop-composition'] = 'true' if self.smooth.isTrue() is True: params['enable-font-smoothing'] = 'true' logger.debug('RDP Params: {0}'.format(params)) ticket = TicketStore.create(params, validity=self.ticketValidity.num()) return HttpResponseRedirect("{}/transport/?{}.{}&{}".format( self.guacamoleServer.value, ticket, scrambler, request.build_absolute_uri(reverse('Index'))))
class X2GOTransport(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 ''' typeName = _('X2GO Transport (direct)') typeType = 'X2GOTransport' typeDescription = _('X2GO Transport for direct connection') iconFile = 'x2go.png' protocol = protocols.NX 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')) listenPort = gui.NumericField(label=_('Listening port'), length=5, order=4, tooltip=_('Listening port of NX (ssh) at client machine'), defvalue='22') connection = gui.ChoiceField(label=_('Connection'), order=6, tooltip=_('Connection speed for this transport (quality)'), values=[ {'id': 'modem', 'text': 'modem'}, {'id': 'isdn', 'text': 'isdn'}, {'id': 'adsl', 'text': 'adsl'}, {'id': 'wan', 'text': 'wan'}, {'id': 'lan', 'text': 'lan'} ]) session = gui.ChoiceField(label=_('Session'), order=7, tooltip=_('Desktop session'), values=[ {'id': 'gnome', 'text': 'gnome'}, {'id': 'kde', 'text': 'kde'}, {'id': 'cde', 'text': 'cde'}, ]) cacheDisk = gui.ChoiceField(label=_('Disk Cache'), order=8, tooltip=_('Cache size en Mb stored at disk'), values=[ {'id': '0', 'text': '0 Mb'}, {'id': '32', 'text': '32 Mb'}, {'id': '64', 'text': '64 Mb'}, {'id': '128', 'text': '128 Mb'}, {'id': '256', 'text': '256 Mb'}, {'id': '512', 'text': '512 Mb'}, ]) cacheMem = gui.ChoiceField(label=_('Memory Cache'), order=9, tooltip=_('Cache size en Mb kept at memory'), values=[ {'id': '4', 'text': '4 Mb'}, {'id': '8', 'text': '8 Mb'}, {'id': '16', 'text': '16 Mb'}, {'id': '32', 'text': '32 Mb'}, {'id': '64', 'text': '64 Mb'}, {'id': '128', 'text': '128 Mb'}, ]) def initialize(self, values): if values is None: return # Just pass over in fact 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, self.listenPort.value) 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 getUDSTransportScript(self, userService, transport, ip, os, user, password, request): logger.debug('Getting X2Go Transport info') prefs = user.prefs('nx') username = user.getUsernameForAuth() proc = username.split('@') username = proc[0] if self.fixedName.value != '': username = self.fixedName.value if self.fixedPassword.value != '': password = self.fixedPassword.value if self.useEmptyCreds.isTrue(): username, password = '', '' # We have the credentials right now, let os manager width, height = CommonPrefs.getWidthHeight(prefs) # Fix username/password acording to os manager username, password = userService.processUserPassword(username, password) # data data = { 'username': username, 'password': password, 'width': width, 'height': height, 'port': self.listenPort.value, 'connection': self.connection.value, 'session': self.session.value, 'cacheDisk': self.cacheDisk.value, 'cacheMem': self.cacheMem.value } return ''' from PyQt4 import QtCore, QtGui import six from uds import osDetector data = {data} osname = {os} QtGui.QMessageBox.critical(parent, 'Notice ' + osDetector.getOs(), six.text_type(data), QtGui.QMessageBox.Ok) '''.format(data=data, os=os)
class RegexLdap(auths.Authenticator): host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('Ldap Server Host'), required=True) port = gui.NumericField( length=5, label=_('Port'), defvalue='389', order=2, tooltip=_('Ldap port (usually 389 for non ssl and 636 for ssl)'), required=True) ssl = gui.CheckBoxField( label=_('Use SSL'), order=3, tooltip= _('If checked, the connection will be ssl, using port 636 instead of 389' )) username = gui.TextField( length=64, label=_('User'), order=4, tooltip=_('Username with read privileges on the base selected'), required=True, tab=gui.CREDENTIALS_TAB) password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the ldap user'), required=True, tab=gui.CREDENTIALS_TAB) timeout = gui.NumericField( length=3, label=_('Timeout'), defvalue='10', order=6, tooltip=_('Timeout in seconds of connection to LDAP'), required=True, minValue=1) ldapBase = gui.TextField( length=64, label=_('Base'), order=7, tooltip=_('Common search base (used for "users" and "groups")'), required=True, tab=_('Ldap info')) userClass = gui.TextField( length=64, label=_('User class'), defvalue='posixAccount', order=8, tooltip=_('Class for LDAP users (normally posixAccount)'), required=True, tab=_('Ldap info')) userIdAttr = gui.TextField( length=64, label=_('User Id Attr'), defvalue='uid', order=9, tooltip=_('Attribute that contains the user id'), required=True, tab=_('Ldap info')) userNameAttr = gui.TextField( length=640, label=_('User Name Attr'), multiline=2, defvalue='uid', order=10, tooltip= _('Attributes that contains the user name (list of comma separated values)' ), required=True, tab=_('Ldap info')) groupNameAttr = gui.TextField( length=640, label=_('Group Name Attr'), multiline=2, defvalue='cn', order=11, tooltip=_('Attribute that contains the group name'), required=True, tab=_('Ldap info')) # regex = gui.TextField(length=64, label = _('Regular Exp. for groups'), defvalue = '^(.*)', order = 12, tooltip = _('Regular Expression to extract the group name'), required = True) altClass = gui.TextField( length=64, label=_('Alt. class'), defvalue='', order=20, tooltip= _('Class for LDAP objects that will be also checked for groups retrieval (normally empty)' ), required=False, tab=_('Advanced')) typeName = _('Regex LDAP Authenticator') typeType = 'RegexLdapAuthenticator' typeDescription = _('Regular Expressions LDAP authenticator') iconFile = 'auth.png' # If it has and external source where to get "new" users (groups must be declared inside UDS) isExternalSource = True # If we need to enter the password for this user needsPassword = False # Label for username field userNameLabel = _('Username') # Label for group field groupNameLabel = _("Group") # Label for password field passwordLabel = _("Password") def __init__(self, dbAuth, environment, values=None): super(RegexLdap, self).__init__(dbAuth, environment, values) if values is not None: self.__validateField(values['userNameAttr'], str(self.userNameAttr.label)) self.__validateField(values['userIdAttr'], str(self.userIdAttr.label)) self.__validateField(values['groupNameAttr'], str(self.groupNameAttr.label)) self._host = values['host'] self._port = values['port'] self._ssl = gui.strToBool(values['ssl']) self._username = values['username'] self._password = values['password'] self._timeout = values['timeout'] self._ldapBase = values['ldapBase'] self._userClass = values['userClass'] self._userIdAttr = values['userIdAttr'] self._groupNameAttr = values['groupNameAttr'] # self._regex = values['regex'] self._userNameAttr = values['userNameAttr'] self._altClass = values['altClass'] else: self._host = None self._port = None self._ssl = None self._username = None self._password = None self._timeout = None self._ldapBase = None self._userClass = None self._userIdAttr = None self._groupNameAttr = None # self._regex = None self._userNameAttr = None self._altClass = None self._connection = None def __validateField(self, field, fieldLabel): ''' Validates the multi line fields refering to attributes ''' for line in field.splitlines(): if line.find('=') != -1: _, pattern = line.split('=')[0:2] if pattern.find('(') == -1: pattern = '(' + pattern + ')' try: re.search(pattern, '') except: raise auths.Authenticator.ValidationException( 'Invalid pattern in {0}: {1}'.format(fieldLabel, line)) def __getAttrsFromField(self, field): res = [] for line in field.splitlines(): equalPos = line.find('=') if equalPos != -1: attr = line[:equalPos] else: attr = line res.append(attr.encode('utf-8')) return res def __processField(self, field, attributes): res = [] logger.debug('Attributes: {}'.format(attributes)) for line in field.splitlines(): equalPos = line.find('=') if equalPos == -1: line = line + '=(.*)' equalPos = line.find('=') attr, pattern = (line[:equalPos], line[equalPos + 1:]) attr = attr.lower() # if pattern do not have groups, define one with full re if pattern.find('(') == -1: pattern = '(' + pattern + ')' val = attributes.get(attr, []) if type(val) is not list: # May we have a single value val = [val] logger.debug('Pattern: {0}'.format(pattern)) for vv in val: try: v = vv.decode('utf-8') logger.debug('v, vv: {}, {}'.format(v, vv)) srch = re.search(pattern, v, re.IGNORECASE) logger.debug("Found against {0}: {1} ".format( v, srch.groups())) if srch is None: continue res.append(''.join(srch.groups())) except Exception: pass # Ignore exceptions here return res def valuesDict(self): return { 'host': self._host, 'port': self._port, 'ssl': gui.boolToStr(self._ssl), 'username': self._username, 'password': self._password, 'timeout': self._timeout, 'ldapBase': self._ldapBase, 'userClass': self._userClass, 'userIdAttr': self._userIdAttr, 'groupNameAttr': self._groupNameAttr, 'userNameAttr': self._userNameAttr, 'altClass': self._altClass, } def __str__(self): return "Ldap Auth: {}:{}@{}:{}, base = {}, userClass = {}, userIdAttr = {}, groupNameAttr = {}, userName attr = {}, altClass={}".format( self._username, self._password, self._host, self._port, self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, self._userNameAttr, self._altClass) def marshal(self): return '\t'.join([ 'v3', self._host, self._port, gui.boolToStr(self._ssl), self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, self._userNameAttr, self._altClass ]) def unmarshal(self, val): data = val.split('\t') if data[0] == 'v1': logger.debug("Data: {0}".format(data[1:])) self._host, self._port, self._ssl, self._username, self._password, \ self._timeout, self._ldapBase, self._userClass, self._userIdAttr, \ self._groupNameAttr, _regex, self._userNameAttr = data[1:] self._ssl = gui.strToBool(self._ssl) self._groupNameAttr = self._groupNameAttr + '=' + _regex self._userNameAttr = '\n'.join(self._userNameAttr.split(',')) elif data[0] == 'v2': logger.debug("Data v2: {0}".format(data[1:])) self._host, self._port, self._ssl, self._username, self._password, \ self._timeout, self._ldapBase, self._userClass, self._userIdAttr, \ self._groupNameAttr, self._userNameAttr = data[1:] self._ssl = gui.strToBool(self._ssl) elif data[0] == 'v3': logger.debug("Data v3: {0}".format(data[1:])) self._host, self._port, self._ssl, self._username, self._password, \ self._timeout, self._ldapBase, self._userClass, self._userIdAttr, \ self._groupNameAttr, self._userNameAttr, self._altClass = data[1:] self._ssl = gui.strToBool(self._ssl) def __connection(self, username=None, password=None): if self._connection is None or username is not None: # We want this method also to check credentials l = None cache = False try: if password is not None: password = password.encode('utf-8') # ldap.set_option(ldap.OPT_DEBUG_LEVEL, 9) ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) schema = self._ssl and 'ldaps' or 'ldap' port = self._port != '389' and ':' + self._port or '' uri = "%s://%s%s" % (schema, self._host, port) logger.debug('Ldap uri: {0}'.format(uri)) l = ldap.initialize(uri=uri) l.set_option(ldap.OPT_REFERRALS, 0) l.network_timeout = l.timeout = int(self._timeout) l.protocol_version = ldap.VERSION3 if username is None: cache = True username = self._username password = self._password l.simple_bind_s(who=username, cred=password) except ldap.LDAPError, e: str_ = _('Ldap connection error: ') if type(e.message) == dict: str_ += 'info' in e.message and e.message[ 'info'] + ',' or '' str_ += 'desc' in e.message and e.message['desc'] or '' else: str_ += str(e) raise Exception(str_) if cache is True: self._connection = l else: return l # Do not cache nor overwrite "global" connection return self._connection
class SimpleLDAPAuthenticator(Authenticator): host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('Ldap Server IP or Hostname'), required=True) port = gui.NumericField(length=5, label=_('Port'), defvalue='389', order=2, tooltip=_('Ldap port (usually 389 for non ssl and 636 for ssl)'), required=True) ssl = gui.CheckBoxField(label=_('Use SSL'), order=3, tooltip=_('If checked, the connection will be ssl, using port 636 instead of 389')) username = gui.TextField(length=64, label=_('Ldap User'), order=4, tooltip=_('Username with read privileges on the base selected'), required=True) password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the ldap user'), required=True) timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=6, tooltip=_('Timeout in seconds of connection to LDAP'), required=True, minValue=1) ldapBase = gui.TextField(length=64, label=_('Base'), order=7, tooltip=_('Common search base (used for "users" and "groups")'), required=True) userClass = gui.TextField(length=64, label=_('User class'), defvalue='posixAccount', order=8, tooltip=_('Class for LDAP users (normally posixAccount)'), required=True) userIdAttr = gui.TextField(length=64, label=_('User Id Attr'), defvalue='uid', order=9, tooltip=_('Attribute that contains the user id'), required=True) userNameAttr = gui.TextField(length=64, label=_('User Name Attr'), defvalue='uid', order=10, tooltip=_('Attributes that contains the user name (list of comma separated values)'), required=True) groupClass = gui.TextField(length=64, label=_('Group class'), defvalue='posixGroup', order=11, tooltip=_('Class for LDAP groups (normally poxisGroup)'), required=True) groupIdAttr = gui.TextField(length=64, label=_('Group Id Attr'), defvalue='cn', order=12, tooltip=_('Attribute that contains the group id'), required=True) memberAttr = gui.TextField(length=64, label=_('Group membership attr'), defvalue='memberUid', order=13, tooltip=_('Attribute of the group that contains the users belonging to it'), required=True) typeName = _('SimpleLDAP Authenticator') typeType = 'SimpleLdapAuthenticator' typeDescription = _('Simple LDAP authenticator') iconFile = 'auth.png' # If it has and external source where to get "new" users (groups must be declared inside UDS) isExternalSource = True # If we need to enter the password for this user needsPassword = False # Label for username field userNameLabel = _('Username') # Label for group field groupNameLabel = _("Group") # Label for password field passwordLabel = _("Password") def __init__(self, dbAuth, environment, values=None): super(SimpleLDAPAuthenticator, self).__init__(dbAuth, environment, values) if values is not None: self._host = values['host'] self._port = values['port'] self._ssl = gui.strToBool(values['ssl']) self._username = values['username'] self._password = values['password'] self._timeout = values['timeout'] self._ldapBase = values['ldapBase'] self._userClass = values['userClass'] self._groupClass = values['groupClass'] self._userIdAttr = values['userIdAttr'] self._groupIdAttr = values['groupIdAttr'] self._memberAttr = values['memberAttr'] self._userNameAttr = values['userNameAttr'].replace(' ', '') # Removes white spaces else: self._host = None self._port = None self._ssl = None self._username = None self._password = None self._timeout = None self._ldapBase = None self._userClass = None self._groupClass = None self._userIdAttr = None self._groupIdAttr = None self._memberAttr = None self._userNameAttr = None self._connection = None def valuesDict(self): return { 'host': self._host, 'port': self._port, 'ssl': gui.boolToStr(self._ssl), 'username': self._username, 'password': self._password, 'timeout': self._timeout, 'ldapBase': self._ldapBase, 'userClass': self._userClass, 'groupClass': self._groupClass, 'userIdAttr': self._userIdAttr, 'groupIdAttr': self._groupIdAttr, 'memberAttr': self._memberAttr, 'userNameAttr': self._userNameAttr } def __str__(self): return "Ldap Auth: {0}:{1}@{2}:{3}, base = {4}, userClass = {5}, groupClass = {6}, userIdAttr = {7}, groupIdAttr = {8}, memberAttr = {9}, userName attr = {10}".format( self._username, self._password, self._host, self._port, self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr) def marshal(self): return '\t'.join(['v1', self._host, self._port, gui.boolToStr(self._ssl), self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr]) def unmarshal(self, str_): data = str_.split('\t') if data[0] == 'v1': logger.debug("Data: {0}".format(data[1:])) self._host, self._port, self._ssl, self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr = data[1:] self._ssl = gui.strToBool(self._ssl) def __connection(self, username=None, password=None): if self._connection is None or username is not None: # We want this method also to check credentials if isinstance(username, six.text_type): username = username.encode('utf8') if isinstance(password, six.text_type): password = password.encode('utf8') l = None cache = False try: # ldap.set_option(ldap.OPT_DEBUG_LEVEL, 9) ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) schema = self._ssl and 'ldaps' or 'ldap' port = self._port != '389' and ':' + self._port or '' uri = "%s://%s%s" % (schema, self._host, port) logger.debug('Ldap uri: {0}'.format(uri)) l = ldap.initialize(uri=uri) l.network_timeout = l.timeout = int(self._timeout) l.protocol_version = ldap.VERSION3 if username is None: cache = True username = self._username password = self._password l.simple_bind_s(who=username, cred=password) except ldap.LDAPError, e: str_ = _('Ldap connection error: ') if type(e.message) == dict: str_ += 'info' in e.message and e.message['info'] + ',' or '' str_ += 'desc' in e.message and e.message['desc'] or '' else: str_ += str_(e) raise Exception(str_) if cache is True: self._connection = l else: return l # Do not cache nor overwrite "global" connection return self._connection
class TRDPTransport(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 ''' typeName = _('RDP') typeType = 'TSRDPTransport' typeDescription = _('RDP Protocol. Tunneled connection.') needsJava = True # If this transport needs java for rendering protocol = protocols.RDP group = TUNNELED_GROUP tunnelServer = gui.TextField(label=_('Tunnel server'), order=1, tooltip=_('IP or Hostname of tunnel server sent to client device ("public" ip) and port. (use HOST:PORT format)'), tab=gui.TUNNEL_TAB) # 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)'), tab=gui.TUNNEL_TAB) tunnelWait = gui.NumericField(length=3, label=_('Tunnel wait time'), defvalue='10', minValue=1, maxValue=65536, order=2, tooltip=_('Maximum time to wait before closing the tunnel listener'), required=True, tab=gui.TUNNEL_TAB) 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 allowClipboard = BaseRDPTransport.allowClipboard allowAudio = BaseRDPTransport.allowAudio wallpaper = BaseRDPTransport.wallpaper multimon = BaseRDPTransport.multimon aero = BaseRDPTransport.aero smooth = BaseRDPTransport.smooth credssp = BaseRDPTransport.credssp screenSize = BaseRDPTransport.screenSize colorDepth = BaseRDPTransport.colorDepth alsa = BaseRDPTransport.alsa multimedia = BaseRDPTransport.multimedia redirectHome = BaseRDPTransport.redirectHome printerString = BaseRDPTransport.printerString smartcardString = BaseRDPTransport.smartcardString customParameters = BaseRDPTransport.customParameters 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 getUDSTransportScript(self, userService, transport, ip, os, user, password, request): # We use helper to keep this clean # prefs = user.prefs('rdp') ci = self.getConnectionInfo(userService, user, password) username, password, domain = ci['username'], ci['password'], ci['domain'] # width, height = CommonPrefs.getWidthHeight(prefs) # depth = CommonPrefs.getDepth(prefs) width, height = self.screenSize.value.split('x') depth = self.colorDepth.value tunpass = ''.join(random.choice(string.letters + string.digits) for _i in range(12)) tunuser = TicketStore.create(tunpass) sshHost, sshPort = self.tunnelServer.value.split(':') logger.debug('Username generated: {0}, password: {1}'.format(tunuser, tunpass)) r = RDPFile(width == '-1' or height == '-1', width, height, depth, target=os['OS']) r.enablecredsspsupport = ci.get('sso', self.credssp.isTrue()) r.address = '{address}' r.username = username r.password = password r.domain = domain r.redirectPrinters = self.allowPrinters.isTrue() r.redirectSmartcards = self.allowSmartcards.isTrue() r.redirectDrives = self.allowDrives.value r.redirectHome = self.redirectHome.isTrue() r.redirectSerials = self.allowSerials.isTrue() r.enableClipboard = self.allowClipboard.isTrue() r.redirectAudio = self.allowAudio.isTrue() r.showWallpaper = self.wallpaper.isTrue() r.multimon = self.multimon.isTrue() r.desktopComposition = self.aero.isTrue() r.smoothFonts = self.smooth.isTrue() r.multimedia = self.multimedia.isTrue() r.alsa = self.alsa.isTrue() r.smartcardString = self.smartcardString.value r.printerString = self.printerString.value r.linuxCustomParameters = self.customParameters.value # data # data = { # 'os': os['OS'], # 'ip': ip, # 'tunUser': tunuser, # 'tunPass': tunpass, # 'tunHost': sshHost, # 'tunPort': sshPort, # 'tunWait': self.tunnelWait.num(), # 'username': username, # 'password': password, # 'hasCredentials': username != '' and password != '', # 'domain': domain, # 'width': width, # 'height': height, # 'depth': depth, # 'printers': self.allowPrinters.isTrue(), # 'smartcards': self.allowSmartcards.isTrue(), # 'drives': self.allowDrives.isTrue(), # 'serials': self.allowSerials.isTrue(), # 'compression': True, # 'wallpaper': self.wallpaper.isTrue(), # 'multimon': self.multimon.isTrue(), # 'fullScreen': width == -1 or height == -1, # 'this_server': request.build_absolute_uri('/'), # 'r': r, # } os = { OsDetector.Windows: 'windows', OsDetector.Linux: 'linux', OsDetector.Macintosh: 'macosx' }.get(os['OS']) if os is None: return super(self.__class__, self).getUDSTransportScript(userService, transport, ip, os, user, password, request) sp = { 'tunUser': tunuser, 'tunPass': tunpass, 'tunHost': sshHost, 'tunPort': sshPort, 'tunWait': self.tunnelWait.num(), 'ip': ip, 'password': password, 'this_server': request.build_absolute_uri('/'), } m = tools.DictAsObj(data) return self.getScript('scripts/{}/tunnel.py', os, sp)
class NXTransport(Transport): """ Provides access via NX 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 """ typeName = _('NX v3.5') typeType = 'NXTransport' typeDescription = _('NX Protocol v3.5. Direct connection.') iconFile = 'nx.png' protocol = protocols.NX useEmptyCreds = gui.CheckBoxField( label=_('Empty creds'), order=1, tooltip=_('If checked, the credentials used to connect will be emtpy'), tab=gui.CREDENTIALS_TAB) fixedName = gui.TextField( label=_('Username'), order=2, tooltip=_( 'If not empty, this username will be always used as credential'), tab=gui.CREDENTIALS_TAB) fixedPassword = gui.PasswordField( label=_('Password'), order=3, tooltip=_( 'If not empty, this password will be always used as credential'), tab=gui.CREDENTIALS_TAB) listenPort = gui.NumericField( label=_('Listening port'), length=5, order=4, tooltip=_('Listening port of NX (ssh) at client machine'), defvalue='22') connection = gui.ChoiceField( label=_('Connection'), order=6, tooltip=_('Connection speed for this transport (quality)'), values=[{ 'id': 'modem', 'text': 'modem' }, { 'id': 'isdn', 'text': 'isdn' }, { 'id': 'adsl', 'text': 'adsl' }, { 'id': 'wan', 'text': 'wan' }, { 'id': 'lan', 'text': 'lan' }], tab=gui.PARAMETERS_TAB) session = gui.ChoiceField(label=_('Session'), order=7, tooltip=_('Desktop session'), values=[ { 'id': 'gnome', 'text': 'gnome' }, { 'id': 'kde', 'text': 'kde' }, { 'id': 'cde', 'text': 'cde' }, ], tab=gui.PARAMETERS_TAB) cacheDisk = gui.ChoiceField(label=_('Disk Cache'), order=8, tooltip=_('Cache size en Mb stored at disk'), values=[ { 'id': '0', 'text': '0 Mb' }, { 'id': '32', 'text': '32 Mb' }, { 'id': '64', 'text': '64 Mb' }, { 'id': '128', 'text': '128 Mb' }, { 'id': '256', 'text': '256 Mb' }, { 'id': '512', 'text': '512 Mb' }, ], tab=gui.PARAMETERS_TAB) cacheMem = gui.ChoiceField(label=_('Memory Cache'), order=9, tooltip=_('Cache size en Mb kept at memory'), values=[ { 'id': '4', 'text': '4 Mb' }, { 'id': '8', 'text': '8 Mb' }, { 'id': '16', 'text': '16 Mb' }, { 'id': '32', 'text': '32 Mb' }, { 'id': '64', 'text': '64 Mb' }, { 'id': '128', 'text': '128 Mb' }, ], tab=gui.PARAMETERS_TAB) def __init__(self, environment, values=None): super(NXTransport, self).__init__(environment, values) if values is not None: self._useEmptyCreds = gui.strToBool(values['useEmptyCreds']) self._fixedName = values['fixedName'] self._fixedPassword = values['fixedPassword'] self._listenPort = values['listenPort'] self._connection = values['connection'] self._session = values['session'] self._cacheDisk = values['cacheDisk'] self._cacheMem = values['cacheMem'] else: self._useEmptyCreds = '' self._fixedName = '' self._fixedPassword = '' self._listenPort = '' self._connection = '' self._session = '' self._cacheDisk = '' self._cacheMem = '' def marshal(self): """ Serializes the transport data so we can store it in database """ return str.join('\t', [ 'v1', gui.boolToStr(self._useEmptyCreds), self._fixedName, self._fixedPassword, self._listenPort, self._connection, self._session, self._cacheDisk, self._cacheMem ]).encode('utf8') def unmarshal(self, val): data = val.decode('utf8').split('\t') if data[0] == 'v1': self._useEmptyCreds = gui.strToBool(data[1]) self._fixedName, self._fixedPassword, self._listenPort, self._connection, self._session, self._cacheDisk, self._cacheMem = data[ 2:] def valuesDict(self): return { 'useEmptyCreds': gui.boolToStr(self._useEmptyCreds), 'fixedName': self._fixedName, 'fixedPassword': self._fixedPassword, 'listenPort': self._listenPort, 'connection': self._connection, 'session': self._session, 'cacheDisk': self._cacheDisk, 'cacheMem': self._cacheMem } def isAvailableFor(self, userService, 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 self.testServer(userService, ip, self._listenPort) 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 getScript(self, script): with open(os.path.join(os.path.dirname(__file__), script)) as f: data = f.read() return data def getUDSTransportScript(self, userService, transport, ip, os, user, password, request): prefs = user.prefs('nx') username = user.getUsernameForAuth() proc = username.split('@') username = proc[0] if self._fixedName is not '': username = self._fixedName if self._fixedPassword is not '': password = self._fixedPassword if self._useEmptyCreds is True: username, password = '', '' # We have the credentials right now, let os manager width, height = CommonPrefs.getWidthHeight(prefs) # Fix username/password acording to os manager username, password = userService.processUserPassword( username, password) r = NXFile(username=username, password=password, width=width, height=height) r.host = ip r.port = self._listenPort r.connection = self._connection r.desktop = self._session r.cachedisk = self._cacheDisk r.cachemem = self._cacheMem os = { OsDetector.Windows: 'windows', OsDetector.Linux: 'linux', OsDetector.Macintosh: 'macosx' }.get(os['OS']) if os is None: return super(self.__class__, self).getUDSTransportScript(userService, transport, ip, os, user, password, request) return self.getScript('scripts/{}/direct.py'.format(os)).format(r=r)
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' 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)' )) multimon = gui.CheckBoxField( label=_('Multiple monitors'), order=10, tooltip= _('If checked, all client monitors will be used for displaying (only works on windows clients)' )) 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 getConnectionInfo(self, service, user, password): return self.processUserPassword(service, user, password) def getScript(self, script): with open(os.path.join(os.path.dirname(__file__), script)) as f: data = f.read() return data
class SimpleLDAPAuthenticator(Authenticator): host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('Ldap Server IP or Hostname'), required=True) port = gui.NumericField( length=5, label=_('Port'), defvalue='389', order=2, tooltip=_('Ldap port (usually 389 for non ssl and 636 for ssl)'), required=True) ssl = gui.CheckBoxField( label=_('Use SSL'), order=3, tooltip= _('If checked, the connection will be ssl, using port 636 instead of 389' )) username = gui.TextField( length=64, label=_('Ldap User'), order=4, tooltip=_('Username with read privileges on the base selected'), required=True, tab=gui.CREDENTIALS_TAB) password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the ldap user'), required=True, tab=gui.CREDENTIALS_TAB) timeout = gui.NumericField( length=3, label=_('Timeout'), defvalue='10', order=6, tooltip=_('Timeout in seconds of connection to LDAP'), required=True, minValue=1) ldapBase = gui.TextField( length=64, label=_('Base'), order=7, tooltip=_('Common search base (used for "users" and "groups")'), required=True, tab=_('Ldap info')) userClass = gui.TextField( length=64, label=_('User class'), defvalue='posixAccount', order=8, tooltip=_('Class for LDAP users (normally posixAccount)'), required=True, tab=_('Ldap info')) userIdAttr = gui.TextField( length=64, label=_('User Id Attr'), defvalue='uid', order=9, tooltip=_('Attribute that contains the user id'), required=True, tab=_('Ldap info')) userNameAttr = gui.TextField( length=64, label=_('User Name Attr'), defvalue='uid', order=10, tooltip= _('Attributes that contains the user name (list of comma separated values)' ), required=True, tab=_('Ldap info')) groupClass = gui.TextField( length=64, label=_('Group class'), defvalue='posixGroup', order=11, tooltip=_('Class for LDAP groups (normally poxisGroup)'), required=True, tab=_('Ldap info')) groupIdAttr = gui.TextField( length=64, label=_('Group Id Attr'), defvalue='cn', order=12, tooltip=_('Attribute that contains the group id'), required=True, tab=_('Ldap info')) memberAttr = gui.TextField( length=64, label=_('Group membership attr'), defvalue='memberUid', order=13, tooltip=_( 'Attribute of the group that contains the users belonging to it'), required=True, tab=_('Ldap info')) typeName = _('SimpleLDAP Authenticator') typeType = 'SimpleLdapAuthenticator' typeDescription = _('Simple LDAP authenticator') iconFile = 'auth.png' # If it has and external source where to get "new" users (groups must be declared inside UDS) isExternalSource = True # If we need to enter the password for this user needsPassword = False # Label for username field userNameLabel = _('Username') # Label for group field groupNameLabel = _("Group") # Label for password field passwordLabel = _("Password") def __init__(self, dbAuth, environment, values=None): super(SimpleLDAPAuthenticator, self).__init__(dbAuth, environment, values) if values is not None: self._host = values['host'] self._port = values['port'] self._ssl = gui.strToBool(values['ssl']) self._username = values['username'] self._password = values['password'] self._timeout = values['timeout'] self._ldapBase = values['ldapBase'] self._userClass = values['userClass'] self._groupClass = values['groupClass'] self._userIdAttr = values['userIdAttr'] self._groupIdAttr = values['groupIdAttr'] self._memberAttr = values['memberAttr'] self._userNameAttr = values['userNameAttr'].replace( ' ', '') # Removes white spaces else: self._host = None self._port = None self._ssl = None self._username = None self._password = None self._timeout = None self._ldapBase = None self._userClass = None self._groupClass = None self._userIdAttr = None self._groupIdAttr = None self._memberAttr = None self._userNameAttr = None self._connection = None def valuesDict(self): return { 'host': self._host, 'port': self._port, 'ssl': gui.boolToStr(self._ssl), 'username': self._username, 'password': self._password, 'timeout': self._timeout, 'ldapBase': self._ldapBase, 'userClass': self._userClass, 'groupClass': self._groupClass, 'userIdAttr': self._userIdAttr, 'groupIdAttr': self._groupIdAttr, 'memberAttr': self._memberAttr, 'userNameAttr': self._userNameAttr } def __str__(self): return "Ldap Auth: {0}:{1}@{2}:{3}, base = {4}, userClass = {5}, groupClass = {6}, userIdAttr = {7}, groupIdAttr = {8}, memberAttr = {9}, userName attr = {10}".format( self._username, self._password, self._host, self._port, self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr) def marshal(self): return '\t'.join([ 'v1', self._host, self._port, gui.boolToStr(self._ssl), self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr ]).encode('utf8') def unmarshal(self, str_): data = str_.decode('utf8').split('\t') if data[0] == 'v1': logger.debug("Data: {0}".format(data[1:])) self._host, self._port, self._ssl, self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._groupClass, self._userIdAttr, self._groupIdAttr, self._memberAttr, self._userNameAttr = data[ 1:] self._ssl = gui.strToBool(self._ssl) def __connection(self, username=None, password=None): """ Tries to connect to ldap. If username is None, it tries to connect using user provided credentials. @return: Connection established @raise exception: If connection could not be established """ if self._connection is None: # We want this method also to check credentials self._connection = ldaputil.connection(self._username, self._password, self._host, port=self._port, ssl=self._ssl, timeout=self._timeout, debug=False) return self._connection def __connectAs(self, username, password): return ldaputil.connection(username, password, self._host, ssl=self._ssl, timeout=self._timeout, debug=False) def __getUser(self, username): """ Searchs for the username and returns its LDAP entry @param username: username to search, using user provided parameters at configuration to map search entries. @return: None if username is not found, an dictionary of LDAP entry attributes if found. @note: Active directory users contains the groups it belongs to in "memberOf" attribute """ return ldaputil.getFirst( con=self.__connection(), base=self._ldapBase, objectClass=self._userClass, field=self._userIdAttr, value=username, attributes=[ i for i in self._userNameAttr.split(',') + [self._userIdAttr] ], sizeLimit=LDAP_RESULT_LIMIT) def __getGroup(self, groupName): """ Searchs for the groupName and returns its LDAP entry @param groupName: group name to search, using user provided parameters at configuration to map search entries. @return: None if group name is not found, an dictionary of LDAP entry attributes if found. """ return ldaputil.getFirst(con=self.__connection(), base=self.__getLdapBase(), objectClass=self._groupClass, field=self._groupIdAttr, value=groupName, attributes=[self._memberAttr], sizeLimit=LDAP_RESULT_LIMIT) def __getGroups(self, usr): try: groups = {} filter_ = '(&(objectClass=%s)(|(%s=%s)(%s=%s)))' % ( self._groupClass, self._memberAttr, usr['_id'], self._memberAttr, usr['dn']) fld = self._groupIdAttr for d in ldaputil.getAsDict(con=self.__connection(), base=self.__getLdapBase(), ldapFilter=_filter, attrList=[fld], sizeLimit=10 * LDAP_RESULT_LIMIT): if fld in d: for k in d[fld]: groups.add(k) logger.debug('Groups: {0}'.format(groups)) return groups except Exception: logger.exception('Exception at __getGroups') return {} def __getUserRealName(self, usr): ''' Tries to extract the real name for this user. Will return all atttributes (joint) specified in _userNameAttr (comma separated). ''' return ' '.join([(type(usr.get(id_, '')) is list and ' '.join( (str(k) for k in usr.get(id_, ''))) or str(usr.get(id_, ''))) for id_ in self._userNameAttr.split(',')]).strip() def authenticate(self, username, credentials, groupsManager): ''' Must authenticate the user. We can have to different situations here: 1.- The authenticator is external source, what means that users may be unknown to system before callig this 2.- The authenticator isn't external source, what means that users have been manually added to system and are known before this call We receive the username, the credentials used (normally password, but can be a public key or something related to pk) and a group manager. The group manager is responsible for letting know the authenticator which groups we currently has active. @see: uds.core.auths.GroupsManager ''' try: # Locate the user at LDAP usr = self.__getUser(username) if usr is None: return False # Let's see first if it credentials are fine self.__connectAs( usrAD['dn'], credentials) # Will raise an exception if it can't connect groupsManager.validate(self.__getGroups(usr)) return True except Exception: return False def createUser(self, usrData): ''' Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to @param usrData: Contains data received from user directly, that is, a dictionary with at least: name, realName, comments, state & password @return: Raises an exception (AuthException) it things didn't went fine ''' res = self.__getUser(usrData['name']) if res is None: raise AuthenticatorException(_('Username not found')) # Fills back realName field usrData['real_name'] = self.__getUserRealName(res) def getRealName(self, username): ''' Tries to get the real name of an user ''' res = self.__getUser(username) if res is None: return username return self.__getUserRealName(res) def modifyUser(self, usrData): ''' We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..) Modify user has no reason on external sources, so it will never be used (probably) Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to @param usrData: Contains data received from user directly, that is, a dictionary with at least: name, realName, comments, state & password @return: Raises an exception it things don't goes fine ''' return self.createUser(usrData) def createGroup(self, groupData): ''' We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..) External sources already has its own groups and, at most, it can check if it exists on external source before accepting it Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to @params groupData: a dict that has, at least, name, comments and active @return: Raises an exception it things don't goes fine ''' res = self.__getGroup(groupData['name']) if res is None: raise AuthenticatorException(_('Group not found')) def getGroups(self, username, groupsManager): ''' Looks for the real groups to which the specified user belongs Updates groups manager with valid groups Remember to override it in derived authentication if needed (external auths will need this, for internal authenticators this is never used) ''' user = self.__getUser(username) if user is None: raise AuthenticatorException(_('Username not found')) groupsManager.validate(self.__getGroups(user)) def searchUsers(self, pattern): try: fld = self._userIdAttr res = [] for r in ldaputil.getAsDict( con=self.__connection(), base=self.__getLdapBase(), ldapFilter='(&(objectClass=%s)(%s=%s*))' % (self._userClass, self._userIdAttr, pattern), attrList=[fld, self._userNameAttr], sizeLimit=LDAP_RESULT_LIMIT): res.append({ 'id': r[fld][0], # Ignore @... 'name': self.__getUserRealName(r) }) return res except Exception: logger.exception("Exception: ") raise AuthenticatorException( _('Too many results, be more specific')) def searchGroups(self, pattern): try: fld = self._groupIdAttr res = [] for r in ldaputil.getAsDict( con=self.__connection(), base=self.__getLdapBase(), ldapFilter='(&(objectClass=%s)(%s=%s*))' % (self._groupClass, self._groupIdAttr, pattern), attrList=[fld, 'memberOf', 'description'], sizeLimit=LDAP_RESULT_LIMIT): res.append({'id': r[fld][0], 'name': r['description'][0]}) return res except Exception: logger.exception("Exception: ") raise AuthenticatorException( _('Too many results, be more specific')) @staticmethod def test(env, data): try: auth = SimpleLDAPAuthenticator(None, env, data) return auth.testConnection() except Exception as e: logger.error( "Exception found testing Simple LDAP auth {0}: {1}".format( e.__class__, e)) return [False, "Error testing connection"] def testConnection(self): try: con = self.__connection() except Exception as e: return [False, str(e)] try: con.search_s(base=self._ldapBase, scope=ldap.SCOPE_BASE) except Exception: return [False, _('Ldap search base is incorrect')] try: if len( con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(objectClass=%s)' % self._userClass, sizelimit=1)) == 1: raise Exception() return [ False, _('Ldap user class seems to be incorrect (no user found by that class)' ) ] except Exception as e: # If found 1 or more, all right pass try: if len( con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(objectClass=%s)' % self._groupClass, sizelimit=1)) == 1: raise Exception() return [ False, _('Ldap group class seems to be incorrect (no group found by that class)' ) ] except Exception as e: # If found 1 or more, all right pass try: if len( con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(%s=*)' % self._userIdAttr, sizelimit=1)) == 1: raise Exception() return [ False, _('Ldap user id attribute seems to be incorrect (no user found by that attribute)' ) ] except Exception as e: # If found 1 or more, all right pass try: if len( con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(%s=*)' % self._groupIdAttr, sizelimit=1)) == 1: raise Exception() return [ False, _('Ldap group id attribute seems to be incorrect (no group found by that attribute)' ) ] except Exception as e: # If found 1 or more, all right pass # Now test objectclass and attribute of users try: if len( con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(&(objectClass=%s)(%s=*))' % (self._userClass, self._userIdAttr), sizelimit=1)) == 1: raise Exception() return [ False, _('Ldap user class or user id attr is probably wrong (can\'t find any user with both conditions)' ) ] except Exception as e: # If found 1 or more, all right pass # And group part, with membership try: res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(&(objectClass=%s)(%s=*))' % (self._groupClass, self._groupIdAttr), attrlist=[self._memberAttr.encode('utf-8')]) if len(res) == 0: raise Exception( _('Ldap group class or group id attr is probably wrong (can\'t find any group with both conditions)' )) ok = False for r in res: if self._memberAttr in r[1]: ok = True break if ok is False: raise Exception( _('Can\'t locate any group with the membership attribute specified' )) except Exception as e: return [False, str(e)] return [ True, _("Connection params seem correct, test was succesfully executed") ]
class WinDomainOsManager(WindowsOsManager): typeName = _('Windows Domain OS Manager') typeType = 'WinDomainManager' typeDescription = _('Os Manager to control windows machines with domain.') 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 allowed)' ), 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=64, 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' )) # 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: if values['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!')) if values['account'].find('\\') != -1: raise osmanagers.OSManager.ValidationException( _('DOM\\USER form is not allowed!')) if values['password'] == '': raise osmanagers.OSManager.ValidationException( _('Must provide a password for the account!')) self._domain = values['domain'] self._ou = values['ou'].strip() self._account = values['account'] self._password = values['password'] else: self._domain = "" self._ou = "" self._account = "" self._password = "" # self._ou = self._ou.replace(' ', ''), do not remove spaces if self._domain != '' and self._ou != '': lpath = 'dc=' + ',dc='.join( (s.lower() for s in self._domain.split('.'))) if self._ou.lower().find(lpath) == -1: self._ou += ',' + lpath def __getLdapError(self, e): logger.debug('Ldap Error: {0} {1}'.format(e, e.message)) _str = '' if type(e.message) == dict: # _str += e.message.has_key('info') and e.message['info'] + ',' or '' _str += e.message.get('desc', '') else: _str += str(e) return _str def __connectLdap(self): ''' Tries to connect to LDAP Raises an exception if not found: dns.resolver.NXDOMAIN ldap.LDAPError ''' servers = reversed( sorted(dns.resolver.query('_ldap._tcp.' + self._domain, 'SRV'), key=lambda i: i.priority * 10000 + i.weight)) for server in servers: _str = '' try: uri = "%s://%s:%d" % ('ldap', str( server.target)[:-1], server.port) logger.debug('URI: {0}'.format(uri)) ldap.set_option( ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) # Disable certificate check l = ldap.initialize(uri=uri) l.set_option(ldap.OPT_REFERRALS, 0) l.network_timeout = l.timeout = 5 l.protocol_version = ldap.VERSION3 account = self._account if account.find('@') == -1: account += '@' + self._domain logger.debug('Account data: {0}, {1}, {2}, {3}'.format( self._account, self._domain, account, self._password)) l.simple_bind_s(who=account, cred=self._password) return l except ldap.LDAPError as e: _str = self.__getLdapError(e) raise ldap.LDAPError(_str) def release(self, service): ''' service is a db user service object ''' super(WinDomainOsManager, self).release(service) if not '.' in self._domain: logger.info('Releasing from a not FQDN domain is not supported') return try: l = self.__connectLdap() except dns.resolver.NXDOMAIN: # No domain found, log it and pass logger.warn('Could not find _ldap._tcp.' + self._domain) log.doLog( service, log.WARN, "Could not remove machine from domain (_ldap._tcp.{0} not found)" .format(self._domain), log.OSMANAGER) except ldap.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) try: if self._ou: ou = self._ou else: ou = ','.join(['DC=' + i for i in self._domain.split('.')]) fltr = '(&(objectClass=computer)(sAMAccountName={}$))'.format( service.friendly_name) res = l.search_ext_s(base=ou, scope=ldap.SCOPE_SUBTREE, filterstr=fltr)[0] l.delete_s(res[0]) # Remove by DN, SYNC except IndexError: logger.error('Error deleting {} from BASE {}'.format( service.friendly_name, ou)) except Exception: logger.exception('Deleting from AD: ') def check(self): try: l = self.__connectLdap() except ldap.LDAPError as e: return _('Check error: {0}').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) ] except Exception as e: logger.exception('Exception ') return [False, str(e)] try: l.search_st(self._ou, ldap.SCOPE_BASE) except ldap.LDAPError as e: return _('Check error: {0}').format(self.__getLdapError(e)) return _('Server check was successful') @staticmethod def test(env, data): logger.debug('Test invoked') try: wd = WinDomainOsManager(env, data) logger.debug(wd) try: l = wd.__connectLdap() except ldap.LDAPError as e: return [ False, _('Could not access AD using LDAP ({0})').format( wd.__getLdapError(e)) ] ou = wd._ou 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) logger.debug('Result of search: {0}'.format(r)) except ldap.LDAPError: if wd._ou == '': return [ False, _('The default path {0} for computers was not found!!!'). format(ou) ] else: return [ False, _('The ou path {0} was not found!!!').format(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: logger.exception('Exception ') return [False, str(e)] 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 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 marshal(self): base = super(WinDomainOsManager, self).marshal() ''' Serializes the os manager data so we can store it in database ''' return '\t'.join([ 'v1', self._domain, self._ou, self._account, CryptoManager.manager().encrypt(self._password), base.encode('hex') ]) def unmarshal(self, s): data = s.split('\t') if data[0] == 'v1': self._domain = data[1] self._ou = data[2] self._account = data[3] self._password = CryptoManager.manager().decrypt(data[4]) super(WinDomainOsManager, self).unmarshal(data[5].decode('hex')) def valuesDict(self): dct = super(WinDomainOsManager, self).valuesDict() dct['domain'] = self._domain dct['ou'] = self._ou dct['account'] = self._account dct['password'] = self._password return dct
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 ''' typeName = _('RDP Transport (tunneled)') typeType = 'TSRDPTransport' typeDescription = _('RDP Transport for tunneled connection') iconFile = 'rdp.png' needsJava = True # If this transport needs java for rendering protocol = protocols.RDP tunnelServer = gui.TextField(label=_('Tunnel server'), order=1, tooltip=_('IP or Hostname of tunnel server sent 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 = 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 multimon = BaseRDPTransport.multimon 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 windowsScript(self, m): return self.getScript('scripts/windows/tunnel.py').format(m=m) def macOsXScript(self, m): return self.getScript('scripts/macosx/tunnel.py').format(m=m) def getUDSTransportScript(self, userService, transport, ip, os, user, password, request): # We use helper to keep this clean prefs = user.prefs('rdp') ci = self.getConnectionInfo(userService, user, password) username, password, domain = ci['username'], ci['password'], ci['domain'] width, height = CommonPrefs.getWidthHeight(prefs) depth = CommonPrefs.getDepth(prefs) tunpass = ''.join(random.choice(string.letters + string.digits) for _i in range(12)) tunuser = TicketStore.create(tunpass) sshHost, sshPort = self.tunnelServer.value.split(':') logger.debug('Username generated: {0}, password: {1}'.format(tunuser, tunpass)) r = RDPFile(width == -1 or height == -1, width, height, depth, target=os['OS']) r.address = '{address}' r.username = username r.password = password r.domain = domain r.redirectPrinters = self.allowPrinters.isTrue() r.redirectSmartcards = self.allowSmartcards.isTrue() r.redirectDrives = self.allowDrives.isTrue() r.redirectSerials = self.allowSerials.isTrue() r.showWallpaper = self.wallpaper.isTrue() r.multimon = self.multimon.isTrue() # data data = { 'os': os['OS'], 'ip': ip, 'tunUser': tunuser, 'tunPass': tunpass, 'tunHost': sshHost, 'tunPort': sshPort, 'username': username, 'password': password, 'hasCredentials': username != '' and password != '', 'domain': domain, 'width': width, 'height': height, 'depth': depth, 'printers': self.allowPrinters.isTrue(), 'smartcards': self.allowSmartcards.isTrue(), 'drives': self.allowDrives.isTrue(), 'serials': self.allowSerials.isTrue(), 'compression': True, 'wallpaper': self.wallpaper.isTrue(), 'multimon': self.multimon.isTrue(), 'fullScreen': width == -1 or height == -1, 'this_server': request.build_absolute_uri('/'), 'r': r, } m = tools.DictAsObj(data) if m.domain != '': m.usernameWithDomain = '{}\\\\{}'.format(m.domain, m.username) else: m.usernameWithDomain = m.username if m.os == OsDetector.Windows: r.password = '******' os = { OsDetector.Windows: 'windows', OsDetector.Linux: 'linux', OsDetector.Macintosh: 'macosx' }.get(m.os) if os is None: return super(TSRDPTransport, self).getUDSTransportScript(self, userService, transport, ip, os, user, password, request) return self.getScript('scripts/{}/tunnel.py'.format(os)).format(m=m)
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' protocol = protocols.RDP useEmptyCreds = gui.CheckBoxField( label=_('Empty creds'), order=11, tooltip=_('If checked, the credentials used to connect will be emtpy'), tab=gui.CREDENTIALS_TAB) fixedName = gui.TextField( label=_('Username'), order=12, tooltip=_( 'If not empty, this username will be always used as credential'), tab=gui.CREDENTIALS_TAB) fixedPassword = gui.PasswordField( label=_('Password'), order=13, tooltip=_( 'If not empty, this password will be always used as credential'), tab=gui.CREDENTIALS_TAB) withoutDomain = gui.CheckBoxField( label=_('Without Domain'), order=14, tooltip= _('If checked, the domain part will always be emptied (to connect to xrdp for example is needed)' ), tab=gui.CREDENTIALS_TAB) fixedDomain = gui.TextField( label=_('Domain'), order=15, tooltip= _('If not empty, this domain will be always used as credential (used as DOMAIN\\user)' ), tab=gui.CREDENTIALS_TAB) allowSmartcards = gui.CheckBoxField( label=_('Allow Smartcards'), order=20, tooltip=_( 'If checked, this transport will allow the use of smartcards'), tab=gui.PARAMETERS_TAB) allowPrinters = gui.CheckBoxField( label=_('Allow Printers'), order=21, tooltip=_( 'If checked, this transport will allow the use of user printers'), tab=gui.PARAMETERS_TAB) allowDrives = gui.ChoiceField( label=_('Allow Drives'), order=22, tooltip=_('Local drives redirection allowed'), defvalue='false', values=[ { 'id': 'false', 'text': 'None' }, { 'id': 'dynamic', 'text': 'Only PnP drives' }, { 'id': 'true', 'text': 'All drives' }, ], tab=gui.PARAMETERS_TAB) allowSerials = gui.CheckBoxField( label=_('Allow Serials'), order=23, tooltip=_( 'If checked, this transport will allow the use of user serial ports' ), tab=gui.PARAMETERS_TAB) allowClipboard = gui.CheckBoxField( label=_('Enable clipboard'), order=24, tooltip=_('If checked, copy-paste functions will be allowed'), tab=gui.PARAMETERS_TAB, defvalue=gui.TRUE) allowAudio = gui.CheckBoxField( label=_('Enable sound'), order=25, tooltip=_('If checked, sound will be redirected.'), tab=gui.PARAMETERS_TAB, defvalue=gui.TRUE) credssp = gui.CheckBoxField( label=_('Credssp Support'), order=26, tooltip=_('If checked, will enable Credentials Provider Support)'), tab=gui.PARAMETERS_TAB, defvalue=gui.TRUE) screenSize = gui.ChoiceField(label=_('Screen Size'), order=30, tooltip=_('Screen size for this transport'), defvalue='-1x-1', values=[ { 'id': '640x480', 'text': '640x480' }, { 'id': '800x600', 'text': '800x600' }, { 'id': '1024x768', 'text': '1024x768' }, { 'id': '1366x768', 'text': '1366x768' }, { 'id': '1920x1080', 'text': '1920x1080' }, { 'id': '-1x-1', 'text': 'Full screen' }, ], tab=gui.DISPLAY_TAB) colorDepth = gui.ChoiceField(label=_('Color depth'), order=31, tooltip=_('Color depth for this connection'), defvalue='24', values=[ { 'id': '8', 'text': '8' }, { 'id': '16', 'text': '16' }, { 'id': '24', 'text': '24' }, { 'id': '32', 'text': '32' }, ], tab=gui.DISPLAY_TAB) wallpaper = gui.CheckBoxField( label=_('Wallpaper/theme'), order=32, tooltip= _('If checked, the wallpaper and themes will be shown on machine (better user experience, more bandwidth)' ), tab=gui.DISPLAY_TAB) multimon = gui.CheckBoxField( label=_('Multiple monitors'), order=33, tooltip= _('If checked, all client monitors will be used for displaying (only works on windows clients)' ), tab=gui.DISPLAY_TAB) aero = gui.CheckBoxField( label=_('Allow Desk.Comp.'), order=34, tooltip=_('If checked, desktop composition will be allowed'), tab=gui.DISPLAY_TAB) smooth = gui.CheckBoxField( label=_('Font Smoothing'), order=35, tooltip=_('If checked, fonts smoothing will be allowed'), tab=gui.DISPLAY_TAB) showConnectionBar = gui.CheckBoxField( label=_('Connection Bar'), order=36, tooltip=_( 'If checked, connection bar will be shown (only on Windows clients)' ), tab=gui.DISPLAY_TAB, defvalue=gui.TRUE) multimedia = gui.CheckBoxField( label=_('Multimedia sync'), order=40, tooltip= _('If checked. Linux client will use multimedia parameter for xfreerdp' ), tab='Linux Client') alsa = gui.CheckBoxField( label=_('Use Alsa'), order=41, tooltip= _('If checked, Linux client will try to use ALSA, otherwise Pulse will be used' ), tab='Linux Client') redirectHome = gui.CheckBoxField( label=_('Redirect home folder'), order=42, tooltip=_( 'If checked, Linux client will try to redirect /home local folder' ), tab='Linux Client', defvalue=gui.FALSE) printerString = gui.TextField( label=_('Printer string'), order=43, tooltip= _('If printer is checked, the printer string used with xfreerdp client' ), tab='Linux Client') smartcardString = gui.TextField( label=_('Smartcard string'), order=44, tooltip= _('If smartcard is checked, the smartcard string used with xfreerdp client' ), tab='Linux Client') customParameters = gui.TextField( label=_('Custom parameters'), order=45, tooltip= _('If not empty, extra parameter to include for Linux Client (for example /usb:id,dev:054c:0268, or aything compatible with your xfreerdp client)' ), tab='Linux Client') def isAvailableFor(self, userService, 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 self.testServer(userService, 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 != '': username = self.fixedName.value proc = username.split('@') if len(proc) > 1: domain = proc[1] else: domain = '' username = proc[0] if self.fixedPassword.value != '': password = self.fixedPassword.value if self.fixedDomain.value != '': domain = self.fixedDomain.value if self.useEmptyCreds.isTrue(): username, password, domain = '', '', '' if self.withoutDomain.isTrue(): domain = '' if domain != '': # If has domain if '.' in domain: # Dotter domain form username = username + '@' + domain domain = '' else: # In case of a NETBIOS domain (not recomended), join it so processUserPassword can deal with it username = domain + '\\' + username domain = '' # Temporal "fix" to check if we do something on processUserPassword # Fix username/password acording to os manager username, password = service.processUserPassword(username, password) # Recover domain name if needed if '\\' in username: username, domain = username.split('\\') return { 'protocol': self.protocol, 'username': username, 'password': password, 'domain': domain } def getConnectionInfo(self, service, user, password): return self.processUserPassword(service, user, password) def getScript(self, scriptName, osName, params) -> Tuple[str, str, dict]: # Reads script scriptName = scriptName.format(osName) with open(os.path.join(os.path.dirname(__file__), scriptName)) as f: script = f.read() # Reads signature with open( os.path.join(os.path.dirname(__file__), scriptName + '.signature')) as f: signature = f.read() return script, signature, params
class RegexLdap(auths.Authenticator): host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('Ldap Server Host'), required=True) port = gui.NumericField(length=5, label=_('Port'), defvalue='389', order=2, tooltip=_('Ldap port (usually 389 for non ssl and 636 for ssl)'), required=True) ssl = gui.CheckBoxField(label=_('Use SSL'), order=3, tooltip=_('If checked, the connection will be ssl, using port 636 instead of 389')) username = gui.TextField(length=64, label=_('User'), order=4, tooltip=_('Username with read privileges on the base selected'), required=True, tab=gui.CREDENTIALS_TAB) password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the ldap user'), required=True, tab=gui.CREDENTIALS_TAB) timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=6, tooltip=_('Timeout in seconds of connection to LDAP'), required=True, minValue=1) ldapBase = gui.TextField(length=64, label=_('Base'), order=7, tooltip=_('Common search base (used for "users" and "groups")'), required=True, tab=_('Ldap info')) userClass = gui.TextField(length=64, label=_('User class'), defvalue='posixAccount', order=8, tooltip=_('Class for LDAP users (normally posixAccount)'), required=True, tab=_('Ldap info')) userIdAttr = gui.TextField(length=64, label=_('User Id Attr'), defvalue='uid', order=9, tooltip=_('Attribute that contains the user id'), required=True, tab=_('Ldap info')) userNameAttr = gui.TextField(length=640, label=_('User Name Attr'), multiline=2, defvalue='uid', order=10, tooltip=_('Attributes that contains the user name (list of comma separated values)'), required=True, tab=_('Ldap info')) groupNameAttr = gui.TextField(length=640, label=_('Group Name Attr'), multiline=2, defvalue='cn', order=11, tooltip=_('Attribute that contains the group name'), required=True, tab=_('Ldap info')) # regex = gui.TextField(length=64, label = _('Regular Exp. for groups'), defvalue = '^(.*)', order = 12, tooltip = _('Regular Expression to extract the group name'), required = True) altClass = gui.TextField(length=64, label=_('Alt. class'), defvalue='', order=20, tooltip=_('Class for LDAP objects that will be also checked for groups retrieval (normally empty)'), required=False, tab=_('Advanced')) typeName = _('Regex LDAP Authenticator') typeType = 'RegexLdapAuthenticator' typeDescription = _('Regular Expressions LDAP authenticator') iconFile = 'auth.png' # If it has and external source where to get "new" users (groups must be declared inside UDS) isExternalSource = True # If we need to enter the password for this user needsPassword = False # Label for username field userNameLabel = _('Username') # Label for group field groupNameLabel = _("Group") # Label for password field passwordLabel = _("Password") def __init__(self, dbAuth, environment, values=None): super(RegexLdap, self).__init__(dbAuth, environment, values) if values is not None: self.__validateField(values['userNameAttr'], str(self.userNameAttr.label)) self.__validateField(values['userIdAttr'], str(self.userIdAttr.label)) self.__validateField(values['groupNameAttr'], str(self.groupNameAttr.label)) self._host = values['host'] self._port = values['port'] self._ssl = gui.strToBool(values['ssl']) self._username = values['username'] self._password = values['password'] self._timeout = values['timeout'] self._ldapBase = values['ldapBase'] self._userClass = values['userClass'] self._userIdAttr = values['userIdAttr'] self._groupNameAttr = values['groupNameAttr'] # self._regex = values['regex'] self._userNameAttr = values['userNameAttr'] self._altClass = values['altClass'] else: self._host = None self._port = None self._ssl = None self._username = None self._password = None self._timeout = None self._ldapBase = None self._userClass = None self._userIdAttr = None self._groupNameAttr = None # self._regex = None self._userNameAttr = None self._altClass = None self._connection = None def __validateField(self, field, fieldLabel): """ Validates the multi line fields refering to attributes """ for line in field.splitlines(): if line.find('=') != -1: _, pattern = line.split('=')[0:2] if pattern.find('(') == -1: pattern = '(' + pattern + ')' try: re.search(pattern, '') except Exception: raise auths.Authenticator.ValidationException('Invalid pattern in {0}: {1}'.format(fieldLabel, line)) def __getAttrsFromField(self, field): res = [] for line in field.splitlines(): equalPos = line.find('=') if equalPos != -1: attr = line[:equalPos] else: attr = line res.append(attr) return res def __processField(self, field, attributes): res = [] logger.debug('Attributes: {}'.format(attributes)) for line in field.splitlines(): equalPos = line.find('=') if equalPos == -1: line += '=(.*)' equalPos = line.find('=') attr, pattern = (line[:equalPos], line[equalPos + 1:]) attr = attr.lower() # if pattern do not have groups, define one with full re if pattern.find('(') == -1: pattern = '(' + pattern + ')' val = attributes.get(attr, []) if type(val) is not list: # May we have a single value val = [val] logger.debug('Pattern: {0}'.format(pattern)) for v in val: try: srch = re.search(pattern, v, re.IGNORECASE) logger.debug("Found against {0}: {1} ".format(v, srch.groups())) if srch is None: continue res.append(''.join(srch.groups())) except Exception: pass # Ignore exceptions here logger.debug('Res: {}'.format(res)) return res def valuesDict(self): return { 'host': self._host, 'port': self._port, 'ssl': gui.boolToStr(self._ssl), 'username': self._username, 'password': self._password, 'timeout': self._timeout, 'ldapBase': self._ldapBase, 'userClass': self._userClass, 'userIdAttr': self._userIdAttr, 'groupNameAttr': self._groupNameAttr, 'userNameAttr': self._userNameAttr, 'altClass': self._altClass, } def __str__(self): return "Ldap Auth: {}:{}@{}:{}, base = {}, userClass = {}, userIdAttr = {}, groupNameAttr = {}, userName attr = {}, altClass={}".format( self._username, self._password, self._host, self._port, self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, self._userNameAttr, self._altClass) def marshal(self): return '\t'.join([ 'v3', self._host, self._port, gui.boolToStr(self._ssl), self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, self._userNameAttr, self._altClass ]).encode('utf8') def unmarshal(self, val): data = val.decode('utf8').split('\t') if data[0] == 'v1': logger.debug("Data: {0}".format(data[1:])) self._host, self._port, self._ssl, self._username, self._password, \ self._timeout, self._ldapBase, self._userClass, self._userIdAttr, \ self._groupNameAttr, _regex, self._userNameAttr = data[1:] self._ssl = gui.strToBool(self._ssl) self._groupNameAttr = self._groupNameAttr + '=' + _regex self._userNameAttr = '\n'.join(self._userNameAttr.split(',')) elif data[0] == 'v2': logger.debug("Data v2: {0}".format(data[1:])) self._host, self._port, self._ssl, self._username, self._password, \ self._timeout, self._ldapBase, self._userClass, self._userIdAttr, \ self._groupNameAttr, self._userNameAttr = data[1:] self._ssl = gui.strToBool(self._ssl) elif data[0] == 'v3': logger.debug("Data v3: {0}".format(data[1:])) self._host, self._port, self._ssl, self._username, self._password, \ self._timeout, self._ldapBase, self._userClass, self._userIdAttr, \ self._groupNameAttr, self._userNameAttr, self._altClass = data[1:] self._ssl = gui.strToBool(self._ssl) def __connection(self): """ Tries to connect to ldap. If username is None, it tries to connect using user provided credentials. @return: Connection established @raise exception: If connection could not be established """ if self._connection is None: # We want this method also to check credentials self._connection = ldaputil.connection(self._username, self._password, self._host, port=self._port, ssl=self._ssl, timeout=self._timeout, debug=False) return self._connection def __connectAs(self, username, password): return ldaputil.connection(username, password, self._host, ssl=self._ssl, timeout=self._timeout, debug=False) def __getUser(self, username): """ Searchs for the username and returns its LDAP entry @param username: username to search, using user provided parameters at configuration to map search entries. @return: None if username is not found, an dictionary of LDAP entry attributes if found. @note: Active directory users contains the groups it belongs to in "memberOf" attribute """ return ldaputil.getFirst( con=self.__connection(), base=self._ldapBase, objectClass=self._userClass, field=self._userIdAttr, value=username, attributes=[self._userIdAttr] + self.__getAttrsFromField(self._userNameAttr) + self.__getAttrsFromField(self._groupNameAttr), sizeLimit=LDAP_RESULT_LIMIT ) def __getGroups(self, usr): return self.__processField(self._groupNameAttr, usr) def __getUserRealName(self, usr): return ' '.join(self.__processField(self._userNameAttr, usr)) def authenticate(self, username, credentials, groupsManager): """ Must authenticate the user. We can have to different situations here: 1.- The authenticator is external source, what means that users may be unknown to system before callig this 2.- The authenticator isn't external source, what means that users have been manually added to system and are known before this call We receive the username, the credentials used (normally password, but can be a public key or something related to pk) and a group manager. The group manager is responsible for letting know the authenticator which groups we currently has active. @see: uds.core.auths.GroupsManager """ try: # Locate the user at LDAP usr = self.__getUser(username) if usr is None: return False # Let's see first if it credentials are fine self.__connectAs(usr['dn'], credentials) # Will raise an exception if it can't connect groupsManager.validate(self.__getGroups(usr)) return True except Exception: return False def createUser(self, usrData): """ We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..) External sources already has the user cause they are managed externally, so, it can at most test if the users exists on external source before accepting it. Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to @param usrData: Contains data received from user directly, that is, a dictionary with at least: name, real_name, comments, state & password @return: Raises an exception (AuthException) it things didn't went fine """ res = self.__getUser(usrData['name']) if res is None: raise AuthenticatorException(_('Username not found')) # Fills back realName field usrData['real_name'] = self.__getUserRealName(res) def getRealName(self, username): """ Tries to get the real name of an user """ res = self.__getUser(username) if res is None: return username return self.__getUserRealName(res) def modifyUser(self, usrData): """ We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..) Modify user has no reason on external sources, so it will never be used (probably) Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to @param usrData: Contains data received from user directly, that is, a dictionary with at least: name, realName, comments, state & password @return: Raises an exception it things doesn't go fine """ return self.createUser(usrData) def createGroup(self, groupData): """ We must override this method in authenticators not based on external sources (i.e. database users, text file users, etc..) External sources already has its own groups and, at most, it can check if it exists on external source before accepting it Groups are only used in case of internal users (non external sources) that must know to witch groups this user belongs to @params groupData: a dict that has, at least, name, comments and active @return: Raises an exception it things doesn't go fine """ pass def getGroups(self, username, groupsManager): """ Looks for the real groups to which the specified user belongs Updates groups manager with valid groups Remember to override it in derived authentication if needed (external auths will need this, for internal authenticators this is never used) """ user = self.__getUser(username) if user is None: raise AuthenticatorException(_('Username not found')) groups = self.__getGroups(user) groupsManager.validate(groups) def searchUsers(self, pattern): try: res = [] for r in ldaputil.getAsDict( con=self.__connection(), base=self._ldapBase, ldapFilter='(&(&(objectClass={})({}={}*)))'.format(self._userClass, self._userIdAttr, ldaputil.escape(pattern)), attrList=None, # All attrs sizeLimit=LDAP_RESULT_LIMIT ): logger.debug('R: {0}'.format(r)) res.append({ 'id': r.get(self._userIdAttr.lower(), '')[0], 'name': self.__getUserRealName(r) }) logger.debug(res) return res except Exception: logger.exception("Exception: ") raise AuthenticatorException(_('Too many results, be more specific')) @staticmethod def test(env, data): try: auth = RegexLdap(None, env, data) return auth.testConnection() except Exception as e: logger.error("Exception found testing Simple LDAP auth {0}: {1}".format(e.__class__, e)) return [False, "Error testing connection"] def testConnection(self): try: con = self.__connection() except Exception as e: return [False, str(e)] try: con.search_s(base=self._ldapBase, scope=ldap.SCOPE_BASE) except Exception: return [False, _('Ldap search base is incorrect')] try: if len(con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(objectClass=%s)' % self._userClass, sizelimit=1)) == 1: raise Exception() return [False, _('Ldap user class seems to be incorrect (no user found by that class)')] except Exception: # If found 1 or more, all right pass # Now test objectclass and attribute of users try: if len(con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(&(objectClass=%s)(%s=*))' % (self._userClass, self._userIdAttr), sizelimit=1)) == 1: raise Exception() return [False, _('Ldap user id attr is probably wrong (can\'t find any user with both conditions)')] except Exception: # If found 1 or more, all right pass for grpNameAttr in self._groupNameAttr.split('\n'): vals = grpNameAttr.split('=')[0] if vals == 'dn': continue try: if len(con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE, filterstr='(%s=*)' % vals, sizelimit=1)) == 1: continue except Exception: continue return [False, _('Ldap group id attribute seems to be incorrect (no group found by that attribute)')] # Now try to test regular expression to see if it matches anything ( try: # Check the existence of at least a () grouping # Check validity of regular expression (try to compile it) # this only right now pass except Exception: pass return [True, _("Connection params seem correct, test was succesfully executed")]
class TSPICETransport(BaseSpiceTransport): ''' Provides access via SPICE 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 ''' typeName = _('SPICE Transport (tunneled)') typeType = 'TSSPICETransport' typeDescription = _( 'SPICE Transport for tunneled connection (EXPERIMENTAL)') protocol = protocols.SPICE group = TUNNELED_GROUP tunnelServer = gui.TextField( label=_('Tunnel server'), order=1, tooltip= _('IP or Hostname of tunnel server sent to client device ("public" ip) and port. (use HOST:PORT format)' ), tab=gui.TUNNEL_TAB) serverCertificate = BaseSpiceTransport.serverCertificate fullScreen = BaseSpiceTransport.fullScreen usbShare = BaseSpiceTransport.usbShare autoNewUsbShare = BaseSpiceTransport.autoNewUsbShare smartCardRedirect = BaseSpiceTransport.smartCardRedirect 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 getUDSTransportScript(self, userService, transport, ip, os, user, password, request): userServiceInstance = userService.getInstance() # Spice connection con = userServiceInstance.getConsoleConnection() port, secure_port = con['port'], con['secure_port'] port = -1 if port is None else port secure_port = -1 if secure_port is None else secure_port # Ticket tunpass = ''.join( random.choice(string.letters + string.digits) for _i in range(12)) tunuser = TicketStore.create(tunpass) sshHost, sshPort = self.tunnelServer.value.split(':') r = RemoteViewerFile('127.0.0.1', '{port}', '{secure_port}', con['ticket']['value'], self.serverCertificate.value, con['cert_subject'], fullscreen=self.fullScreen.isTrue()) r.usb_auto_share = self.usbShare.isTrue() r.new_usb_auto_share = self.autoNewUsbShare.isTrue() r.smartcard = self.smartCardRedirect.isTrue() m = tools.DictAsObj({ 'r': r, 'tunUser': tunuser, 'tunPass': tunpass, 'tunHost': sshHost, 'tunPort': sshPort, 'ip': con['address'], 'port': port, 'secure_port': secure_port }) os = { OsDetector.Windows: 'windows', OsDetector.Linux: 'linux', OsDetector.Macintosh: 'macosx' }.get(os.OS) if os is None: return super(self.__class__, self).getUDSTransportScript(userService, transport, ip, os, user, password, request) return self.getScript('scripts/{}/tunnel.py'.format(os)).format(m=m)
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' protocol = protocols.RDP useEmptyCreds = gui.CheckBoxField(label=_('Empty creds'), order=11, tooltip=_('If checked, the credentials used to connect will be emtpy'), tab=gui.CREDENTIALS_TAB) fixedName = gui.TextField(label=_('Username'), order=12, tooltip=_('If not empty, this username will be always used as credential'), tab=gui.CREDENTIALS_TAB) fixedPassword = gui.PasswordField(label=_('Password'), order=13, tooltip=_('If not empty, this password will be always used as credential'), tab=gui.CREDENTIALS_TAB) withoutDomain = gui.CheckBoxField(label=_('Without Domain'), order=14, tooltip=_('If checked, the domain part will always be emptied (to connect to xrdp for example is needed)'), tab=gui.CREDENTIALS_TAB) fixedDomain = gui.TextField(label=_('Domain'), order=15, tooltip=_('If not empty, this domain will be always used as credential (used as DOMAIN\\user)'), tab=gui.CREDENTIALS_TAB) allowSmartcards = gui.CheckBoxField(label=_('Allow Smartcards'), order=16, tooltip=_('If checked, this transport will allow the use of smartcards'), tab=gui.PARAMETERS_TAB) allowPrinters = gui.CheckBoxField(label=_('Allow Printers'), order=17, tooltip=_('If checked, this transport will allow the use of user printers'), tab=gui.PARAMETERS_TAB) allowDrives = gui.CheckBoxField(label=_('Allow Drives'), order=18, tooltip=_('If checked, this transport will allow the use of user drives'), tab=gui.PARAMETERS_TAB) allowSerials = gui.CheckBoxField(label=_('Allow Serials'), order=19, tooltip=_('If checked, this transport will allow the use of user serial ports'), tab=gui.PARAMETERS_TAB) wallpaper = gui.CheckBoxField(label=_('Show wallpaper'), order=20, tooltip=_('If checked, the wallpaper and themes will be shown on machine (better user experience, more bandwidth)'), tab=gui.PARAMETERS_TAB) multimon = gui.CheckBoxField(label=_('Multiple monitors'), order=21, tooltip=_('If checked, all client monitors will be used for displaying (only works on windows clients)'), tab=gui.PARAMETERS_TAB) aero = gui.CheckBoxField(label=_('Allow Desk.Comp.'), order=22, tooltip=_('If checked, desktop composition will be allowed'), tab=gui.PARAMETERS_TAB) smooth = gui.CheckBoxField(label=_('Font Smoothing'), order=23, tooltip=_('If checked, fonts smoothing will be allowed (windows clients only)'), tab=gui.PARAMETERS_TAB) credssp = gui.CheckBoxField(label=_('Credssp Support'), order=24, tooltip=_('If checked, will enable Credentials Provider Support)'), tab=gui.PARAMETERS_TAB) multimedia = gui.CheckBoxField(label=_('Multimedia sync'), order=25, tooltip=_('If checked. Linux client will use multimedia parameter for xfreerdp'), tab='Linux Client') alsa = gui.CheckBoxField(label=_('Use Alsa'), order=26, tooltip=_('If checked, Linux client will try to use ALSA, otherwise Pulse will be used'), tab='Linux Client') printerString = gui.TextField(label=_('Printer string'), order=27, tooltip=_('If printer is checked, the printer string used with xfreerdp client'), tab='Linux Client') smartcardString = gui.TextField(label=_('Smartcard string'), order=28, tooltip=_('If smartcard is checked, the smartcard string used with xfreerdp client'), tab='Linux Client') customParameters = gui.TextField(label=_('Custom parameters'), order=29, tooltip=_('If not empty, extra parameter to include for Linux Client (for example /usb:id,dev:054c:0268, or aything compatible with your xfreerdp client)'), tab='Linux Client') def isAvailableFor(self, userService, 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 self.testServer(userService, 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 != '': username = self.fixedName.value proc = username.split('@') if len(proc) > 1: domain = proc[1] else: domain = '' username = proc[0] if self.fixedPassword.value != '': password = self.fixedPassword.value if self.fixedDomain.value != '': domain = self.fixedDomain.value if self.useEmptyCreds.isTrue(): username, password, domain = '', '', '' if self.withoutDomain.isTrue(): domain = '' if domain != '': # If has domain if '.' in domain: # Dotter domain form username = username + '@' + domain domain = '' else: # In case of a NETBIOS domain (not recomended), join it so processUserPassword can deal with it username = domain + '\\' + username domain = '' # Temporal "fix" to check if we do something on processUserPassword # Fix username/password acording to os manager username, password = service.processUserPassword(username, password) # Recover domain name if needed if '\\' in username: username, domain = username.split('\\') return {'protocol': self.protocol, 'username': username, 'password': password, 'domain': domain} def getConnectionInfo(self, service, user, password): return self.processUserPassword(service, user, password) def getScript(self, scriptName, osName, params): # Reads script scriptName = scriptName.format(osName) with open(os.path.join(os.path.dirname(__file__), scriptName)) as f: script = f.read() # Reads signature with open(os.path.join(os.path.dirname(__file__), scriptName + '.signature')) as f: signature = f.read() return script, signature, params