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 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 PoolPerformanceReport(StatsReport): filename = 'pools_performance.pdf' name = _('Pools performance by date') # Report name description = _('Pools performance report by date') # Report description uuid = '88932b48-1fd3-11e5-a776-10feed05884b' # Input fields pools = gui.MultiChoiceField(order=1, label=_('Pools'), tooltip=_('Pools for report'), required=True) startDate = gui.DateField(order=2, label=_('Starting date'), tooltip=_('starting date for report'), defvalue=datetime.date.min, required=True) endDate = gui.DateField(order=3, label=_('Finish date'), tooltip=_('finish date for report'), defvalue=datetime.date.max, required=True) samplingPoints = gui.NumericField( order=4, label=_('Number of intervals'), length=3, minValue=0, maxValue=32, tooltip=_('Number of sampling points used in charts'), defvalue='8') def initialize(self, values): pass def initGui(self): logger.debug('Initializing gui') vals = [ gui.choiceItem(v.uuid, v.name) for v in ServicePool.objects.all().order_by('name') ] self.pools.setValues(vals) def getPools(self): return [(v.id, v.name) for v in ServicePool.objects.filter(uuid__in=self.pools.value)] def getRangeData(self): start = self.startDate.stamp() end = self.endDate.stamp() if self.samplingPoints.num() < 2: self.samplingPoints.value = (self.endDate.date() - self.startDate.date()).days if self.samplingPoints.num() < 2: self.samplingPoints.value = 2 if self.samplingPoints.num() > 32: self.samplingPoints.value = 32 samplingPoints = self.samplingPoints.num() pools = self.getPools() if len(pools) == 0: raise Exception(_('Select at least a service pool for the report')) logger.debug('Pools: {}'.format(pools)) # x axis label format if end - start > 3600 * 24 * 2: xLabelFormat = 'SHORT_DATE_FORMAT' else: xLabelFormat = 'SHORT_DATETIME_FORMAT' # Generate samplings interval samplingIntervals = [] prevVal = None for val in range(start, end, int( (end - start) / (samplingPoints + 1))): if prevVal is None: prevVal = val continue samplingIntervals.append((prevVal, val)) prevVal = val # Store dataUsers for all pools poolsData = [] fld = events.statsManager().getEventFldFor('username') reportData = [] for p in pools: dataUsers = [] dataAccesses = [] for interval in samplingIntervals: key = (interval[0] + interval[1]) / 2 q = events.statsManager().getEvents( events.OT_DEPLOYED, events.ET_ACCESS, since=interval[0], to=interval[1], owner_id=p[0]).values(fld).annotate(cnt=Count(fld)) accesses = 0 for v in q: accesses += v['cnt'] dataUsers.append((key, len(q))) # @UndefinedVariable dataAccesses.append((key, accesses)) reportData.append({ 'name': p[1], 'date': tools.timestampAsStr(interval[0], xLabelFormat) + ' - ' + tools.timestampAsStr(interval[1], xLabelFormat), 'users': len(q), 'accesses': accesses }) poolsData.append({ 'pool': p[0], 'name': p[1], 'dataUsers': dataUsers, 'dataAccesses': dataAccesses, }) return xLabelFormat, poolsData, reportData def generate(self): # Generate the sampling intervals and get dataUsers from db xLabelFormat, poolsData, reportData = self.getRangeData() graph1 = io.BytesIO() graph2 = io.BytesIO() # surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT) # @UndefinedVariable # logger.debug('PoolsData: %s', poolsData) X = [v[0] for v in poolsData[0]['dataUsers']] data = { 'title': _('Distinct Users'), 'x': X, 'xtickFnc': lambda l: filters.date(datetime.datetime.fromtimestamp(X[int(l)]), xLabelFormat) if int(l) >= 0 else '', 'xlabel': _('Date'), 'y': [{ 'label': p['name'], 'data': [v[1] for v in p['dataUsers']] } for p in poolsData], 'ylabel': _('Users') } graphs.barChart(SIZE, data, graph1) X = [v[0] for v in poolsData[0]['dataAccesses']] data = { 'title': _('Accesses'), 'x': X, 'xtickFnc': lambda l: filters.date(datetime.datetime.fromtimestamp(X[int(l)]), xLabelFormat) if int(l) >= 0 else '', 'xlabel': _('Date'), 'y': [{ 'label': p['name'], 'data': [v[1] for v in p['dataAccesses']] } for p in poolsData], 'ylabel': _('Accesses') } graphs.barChart(SIZE, data, graph2) # Generate Data for pools, basically joining all pool data return self.templateAsPDF( 'uds/reports/stats/pools-performance.html', dct={ 'data': reportData, 'pools': [i[1] for i in self.getPools()], 'beginning': self.startDate.date(), 'ending': self.endDate.date(), 'intervals': self.samplingPoints.num(), }, header=ugettext('UDS Pools Performance Report'), water=ugettext('Pools Performance'), images={ 'graph1': graph1.getvalue(), 'graph2': graph2.getvalue() }, )
class StatsReportLogin(StatsReport): filename = 'access.pdf' name = _('Users access report by date') # Report name description = _( 'Report of user access to platform by date') # Report description uuid = '0f62f19a-f166-11e4-8f59-10feed05884b' # Input fields startDate = gui.DateField(order=1, label=_('Starting date'), tooltip=_('starting date for report'), defvalue=datetime.date.min, required=True) endDate = gui.DateField(order=2, label=_('Finish date'), tooltip=_('finish date for report'), defvalue=datetime.date.max, required=True) samplingPoints = gui.NumericField( order=3, label=_('Number of intervals'), length=3, minValue=0, maxValue=128, tooltip=_('Number of sampling points used in charts'), defvalue='64') def initialize(self, values): pass def initGui(self): pass def getRangeData(self): start = self.startDate.stamp() end = self.endDate.stamp() if self.samplingPoints.num() < 8: self.samplingPoints.value = (self.endDate.date() - self.startDate.date()).days if self.samplingPoints.num() < 2: self.samplingPoints.value = 2 if self.samplingPoints.num() > 128: self.samplingPoints.value = 128 samplingPoints = self.samplingPoints.num() # x axis label format if end - start > 3600 * 24 * 2: xLabelFormat = 'SHORT_DATE_FORMAT' else: xLabelFormat = 'SHORT_DATETIME_FORMAT' samplingIntervals = [] prevVal = None for val in range(start, end, int( (end - start) / (samplingPoints + 1))): if prevVal is None: prevVal = val continue samplingIntervals.append((prevVal, val)) prevVal = val data = [] reportData = [] for interval in samplingIntervals: key = (interval[0] + interval[1]) / 2 val = events.statsManager().getEvents(events.OT_AUTHENTICATOR, events.ET_LOGIN, since=interval[0], to=interval[1]).count() data.append((key, val)) # @UndefinedVariable reportData.append({ 'date': tools.timestampAsStr(interval[0], xLabelFormat) + ' - ' + tools.timestampAsStr(interval[1], xLabelFormat), 'users': val }) return xLabelFormat, data, reportData def getWeekHourlyData(self): start = self.startDate.stamp() end = self.endDate.stamp() dataWeek = [0] * 7 dataHour = [0] * 24 dataWeekHour = [[0] * 24 for _ in range(7)] for val in events.statsManager().getEvents(events.OT_AUTHENTICATOR, events.ET_LOGIN, since=start, to=end): s = datetime.datetime.fromtimestamp(val.stamp) dataWeek[s.weekday()] += 1 dataHour[s.hour] += 1 dataWeekHour[s.weekday()][s.hour] += 1 logger.debug('Data: {} {}'.format(s.weekday(), s.hour)) return dataWeek, dataHour, dataWeekHour def generate(self): # Sample query: # 'SELECT *, count(*) as number, CEIL(stamp/(3600))*3600 as block' # ' FROM {table}' # ' WHERE event_type = 0 and stamp >= {start} and stamp <= {end}' # ' GROUP BY CEIL(stamp/(3600))' # ' ORDER BY block' xLabelFormat, data, reportData = self.getRangeData() # # User access by date graph # graph1 = io.BytesIO() X = [v[0] for v in data] d = { 'title': _('Users Access (global)'), 'x': X, 'xtickFnc': lambda l: filters.date(datetime.datetime.fromtimestamp(l), xLabelFormat), 'xlabel': _('Date'), 'y': [{ 'label': 'Users', 'data': [v[1] for v in data] }], 'ylabel': 'Users', 'allTicks': False } graphs.lineChart(SIZE, d, graph1) graph2 = io.BytesIO() graph3 = io.BytesIO() graph4 = io.BytesIO() dataWeek, dataHour, dataWeekHour = self.getWeekHourlyData() X = list(range(7)) d = { 'title': _('Users Access (by week)'), 'x': X, 'xtickFnc': lambda l: [ _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday'), _('Sunday') ][l], 'xlabel': _('Day of week'), 'y': [{ 'label': 'Users', 'data': [v for v in dataWeek] }], 'ylabel': 'Users' } graphs.barChart(SIZE, d, graph2) X = list(range(24)) d = { 'title': _('Users Access (by hour)'), 'x': X, 'xlabel': _('Hour'), 'y': [{ 'label': 'Users', 'data': [v for v in dataHour] }], 'ylabel': 'Users' } graphs.barChart(SIZE, d, graph3) X = list(range(24)) Y = list(range(7)) d = { 'title': _('Users Access (by hour)'), 'x': X, 'xlabel': _('Hour'), 'xtickFnc': lambda l: l, 'y': Y, 'ylabel': _('Day of week'), 'ytickFnc': lambda l: [ _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday'), _('Sunday') ][l], 'z': dataWeekHour, 'zlabel': _('Users') } graphs.surfaceChart(SIZE, d, graph4) return self.templateAsPDF( 'uds/reports/stats/user-access.html', dct={ 'data': reportData, 'beginning': self.startDate.date(), 'ending': self.endDate.date(), 'intervals': self.samplingPoints.num(), }, header=ugettext('Users access to UDS'), water=ugettext('UDS Report for users access'), images={ 'graph1': graph1.getvalue(), 'graph2': graph2.getvalue(), 'graph3': graph3.getvalue(), 'graph4': graph4.getvalue() }, )
class WindowsOsManager(osmanagers.OSManager): typeName = _('Windows Basic OS Manager') typeType = 'WindowsManager' typeDescription = _( 'Os Manager to control windows machines without domain.') iconFile = 'wosmanager.png' servicesType = (serviceTypes.VDI, ) onLogout = gui.ChoiceField( label=_('Logout Action'), order=10, rdonly=True, tooltip=_('What to do when user logs out from service'), values=[ { 'id': 'keep', 'text': ugettext_lazy('Keep service assigned') }, { 'id': 'remove', 'text': ugettext_lazy('Remove service') }, { 'id': 'keep-always', 'text': ugettext_lazy('Keep service assigned even on new publication') }, ], defvalue='keep') idle = gui.NumericField( label=_("Max.Idle time"), length=4, defvalue=-1, rdonly=False, order=11, tooltip= _('Maximum idle time (in seconds) before session is automatically closed to the user (<= 0 means no max. idle time)' ), required=True) @staticmethod def validateLen(length): try: length = int(length) except Exception: raise osmanagers.OSManager.ValidationException( _('Length must be numeric!!')) if length > 6 or length < 1: raise osmanagers.OSManager.ValidationException( _('Length must be betwen 1 and 6')) return length def __setProcessUnusedMachines(self): self.processUnusedMachines = self._onLogout == 'remove' def __init__(self, environment, values): super(WindowsOsManager, self).__init__(environment, values) if values is not None: self._onLogout = values['onLogout'] self._idle = int(values['idle']) else: self._onLogout = '' self._idle = -1 self.__setProcessUnusedMachines() def isRemovableOnLogout(self, userService): ''' Says if a machine is removable on logout ''' if userService.in_use == False: if (self._onLogout == 'remove') or (not userService.isValidPublication() and self._onLogout == 'keep'): return True return False def release(self, service): pass def getName(self, service): """ gets name from deployed """ return service.getName() def infoVal(self, service): return 'rename:' + self.getName(service) def infoValue(self, service): return 'rename\r' + self.getName(service) def notifyIp(self, uid, service, data): si = service.getInstance() ip = '' # Notifies IP to deployed for p in data['ips']: if p[0].lower() == uid.lower(): si.setIp(p[1]) ip = p[1] break self.logKnownIp(service, ip) service.updateData(si) def doLog(self, service, data, origin=log.OSMANAGER): # Stores a log associated with this service try: msg, level = data.split('\t') try: level = int(level) except Exception: logger.debug('Do not understand level {}'.format(level)) level = log.INFO log.doLog(service, level, msg, origin) except Exception: logger.exception('WindowsOs Manager message log: ') log.doLog(service, log.ERROR, "do not understand {0}".format(data), origin) # default "ready received" does nothing def readyReceived(self, userService, data): pass def process(self, userService, msg, data, options=None): """ We understand this messages: * msg = info, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class) (old method) * msg = information, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class) (new method) * msg = logon, data = Username, Informs that the username has logged in inside the machine * msg = logoff, data = Username, Informs that the username has logged out of the machine * msg = ready, data = None, Informs machine ready to be used """ logger.info( "Invoked WindowsOsManager for {0} with params: {1},{2}".format( userService, msg, data)) if msg in ('ready', 'ip'): if not isinstance( data, dict ): # Old actors, previous to 2.5, convert it information.. data = { 'ips': [v.split('=') for v in data.split(',')], 'hostname': userService.friendly_name } # We get from storage the name for this userService. If no name, we try to assign a new one ret = "ok" notifyReady = False doRemove = False state = userService.os_state if msg == "info": ret = self.infoVal(userService) state = State.PREPARING elif msg == "information": ret = self.infoValue(userService) state = State.PREPARING elif msg == "log": self.doLog(userService, data, log.ACTOR) elif msg == "logon" or msg == 'login': if '\\' not in data: self.loggedIn(userService, data) userService.setInUse(True) # We get the userService logged hostname & ip and returns this ip, hostname = userService.getConnectionSource() deadLine = userService.deployed_service.getDeadline() if userService.getProperty('actor_version', '0.0.0') >= '2.0.0': ret = "{0}\t{1}\t{2}".format( ip, hostname, 0 if deadLine is None else deadLine) else: ret = "{0}\t{1}".format(ip, hostname) elif msg == "logoff" or msg == 'logout': self.loggedOut(userService, data) doRemove = self.isRemovableOnLogout(userService) elif msg == "ip": # This ocurss on main loop inside machine, so userService is usable state = State.USABLE self.notifyIp(userService.unique_id, userService, data) elif msg == "ready": self.toReady(userService) state = State.USABLE notifyReady = True self.notifyIp(userService.unique_id, userService, data) self.readyReceived(userService, data) userService.setOsState(state) # If notifyReady is not true, save state, let UserServiceManager do it for us else if doRemove is True: userService.release() else: if notifyReady is False: userService.save(update_fields=[ 'in_use', 'in_use_date', 'os_state', 'state', 'data' ]) else: logger.debug('Notifying ready') UserServiceManager.manager().notifyReadyFromOsManager( userService, '') logger.debug('Returning {} to {} message'.format(ret, msg)) if options is not None and options.get('scramble', True) is False: return ret return scrambleMsg(ret) def processUserPassword(self, service, username, password): if service.getProperty('sso_available') == '1': # Generate a ticket, store it and return username with no password domain = '' if '@' in username: username, domain = username.split('@') elif '\\' in username: username, domain = username.split('\\') creds = { 'username': username, 'password': password, 'domain': domain } ticket = TicketStore.create( creds, validator=None, validity=300) # , owner=SECURE_OWNER, secure=True) return ticket, '' else: return osmanagers.OSManager.processUserPassword( self, service, username, password) def processUnused(self, userService): """ This will be invoked for every assigned and unused user service that has been in this state at least 1/2 of Globalconfig.CHECK_UNUSED_TIME This function can update userService values. Normal operation will be remove machines if this state is not valid """ if self.isRemovableOnLogout(userService): userService.remove() def isPersistent(self): return self._onLogout == 'keep-always' def checkState(self, service): logger.debug('Checking state for service {0}'.format(service)) return State.RUNNING def maxIdle(self): """ On production environments, will return no idle for non removable machines """ if self._idle <= 0: # or (settings.DEBUG is False and self._onLogout != 'remove'): return None return self._idle def marshal(self): """ Serializes the os manager data so we can store it in database """ return '\t'.join(['v2', self._onLogout, str(self._idle)]).encode('utf8') def unmarshal(self, s): data = s.decode('utf8').split('\t') try: if data[0] == 'v1': self._onLogout = data[1] self._idle = -1 elif data[0] == 'v2': self._onLogout, self._idle = data[1], int(data[2]) except Exception: logger.exception( 'Exception unmarshalling. Some values left as default ones') self.__setProcessUnusedMachines() def valuesDict(self): return {'onLogout': self._onLogout, 'idle': self._idle}
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 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 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 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 PoolPerformanceReport(StatsReport): filename = 'pools_performance.pdf' name = _('Pools performance by date') # Report name description = _('Pools performance report by date') # Report description uuid = '88932b48-1fd3-11e5-a776-10feed05884b' # Input fields pools = gui.MultiChoiceField(order=1, label=_('Pools'), tooltip=_('Pools for report'), required=True) startDate = gui.DateField(order=2, label=_('Starting date'), tooltip=_('starting date for report'), defvalue=datetime.date.min, required=True) endDate = gui.DateField(order=3, label=_('Finish date'), tooltip=_('finish date for report'), defvalue=datetime.date.max, required=True) samplingPoints = gui.NumericField( order=4, label=_('Number of intervals'), length=3, minValue=0, maxValue=32, tooltip=_('Number of sampling points used in charts'), defvalue='8') def initialize(self, values): pass def initGui(self): logger.debug('Initializing gui') vals = [ gui.choiceItem(v.uuid, v.name) for v in ServicePool.objects.all() ] self.pools.setValues(vals) def getRangeData(self): start = self.startDate.stamp() end = self.endDate.stamp() if self.samplingPoints.num() < 2: self.samplingPoints.value = (self.endDate.date() - self.startDate.date()).days if self.samplingPoints.num() < 2: self.samplingPoints.value = 2 if self.samplingPoints.num() > 32: self.samplingPoints.value = 32 samplingPoints = self.samplingPoints.num() pools = [(v.id, v.name) for v in ServicePool.objects.filter(uuid__in=self.pools.value) ] if len(pools) == 0: raise Exception(_('Select at least a service pool for the report')) logger.debug('Pools: {}'.format(pools)) # x axis label format if end - start > 3600 * 24 * 2: xLabelFormat = 'SHORT_DATE_FORMAT' else: xLabelFormat = 'SHORT_DATETIME_FORMAT' # Generate samplings interval samplingIntervals = [] prevVal = None for val in range(start, end, (end - start) / (samplingPoints + 1)): if prevVal is None: prevVal = val continue samplingIntervals.append((prevVal, val)) prevVal = val # Store dataUsers for all pools poolsData = [] fld = events.statsManager().getEventFldFor('username') reportData = [] for p in pools: dataUsers = [] dataAccesses = [] for interval in samplingIntervals: key = (interval[0] + interval[1]) / 2 q = events.statsManager().getEvents( events.OT_DEPLOYED, events.ET_ACCESS, since=interval[0], to=interval[1], owner_id=p[0]).values(fld).annotate(cnt=Count(fld)) accesses = 0 for v in q: accesses += v['cnt'] dataUsers.append((key, len(q))) # @UndefinedVariable dataAccesses.append((key, accesses)) reportData.append({ 'name': p[1], 'date': tools.timestampAsStr(interval[0], xLabelFormat) + ' - ' + tools.timestampAsStr(interval[1], xLabelFormat), 'users': len(q), 'accesses': accesses }) poolsData.append({ 'pool': p[0], 'name': p[1], 'dataUsers': dataUsers, 'dataAccesses': dataAccesses, }) return (xLabelFormat, poolsData, reportData) def generate(self): # Generate the sampling intervals and get dataUsers from db start = self.startDate.stamp() end = self.endDate.stamp() xLabelFormat, poolsData, reportData = self.getRangeData() surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT) # @UndefinedVariable options = { 'encoding': 'utf-8', 'axis': { 'x': { 'ticks': [ dict(v=i, label=filters.date( datetime.datetime.fromtimestamp(l), xLabelFormat)) for i, l in enumerate( range(start, end, (end - start) / self.samplingPoints.num())) ], 'range': (0, self.samplingPoints.num()), 'showLines': True, }, 'y': { 'tickCount': 10, 'showLines': True, }, 'tickFontSize': 16, }, 'background': { 'chartColor': '#f0f0f0', 'baseColor': '#f0f0f0', 'lineColor': '#187FF2' }, 'colorScheme': { 'name': 'rainbow', 'args': { 'initialColor': 'blue', }, }, 'legend': { 'hide': False, 'legendFontSize': 16, 'position': { 'left': 96, 'top': 40, } }, 'padding': { 'left': 48, 'top': 16, 'right': 48, 'bottom': 48, }, 'title': _('Users by pool'), } # chart = pycha.line.LineChart(surface, options) # chart = pycha.bar.VerticalBarChart(surface, options) chart = pycha.stackedbar.StackedVerticalBarChart(surface, options) dataset = [] for pool in poolsData: logger.debug(pool['dataUsers']) ds = list((i, l[1]) for i, l in enumerate(pool['dataUsers'])) logger.debug(ds) dataset.append((ugettext('Users for {}').format(pool['name']), ds)) logger.debug('Dataset: {}'.format(dataset)) chart.addDataset(dataset) chart.render() img = PILImage.frombuffer("RGBA", (surface.get_width(), surface.get_height()), surface.get_data(), "raw", "BGRA", 0, 1) # Accesses chart = pycha.stackedbar.StackedVerticalBarChart(surface, options) dataset = [] for pool in poolsData: logger.debug(pool['dataAccesses']) ds = list((i, l[1]) for i, l in enumerate(pool['dataAccesses'])) logger.debug(ds) dataset.append( (ugettext('Accesses for {}').format(pool['name']), ds)) logger.debug('Dataset: {}'.format(dataset)) chart.addDataset(dataset) chart.render() img2 = PILImage.frombuffer("RGBA", (surface.get_width(), surface.get_height()), surface.get_data(), "raw", "BGRA", 0, 1) # Generate Data for pools, basically joining all pool data queryset = [{'image': img, 'image2': img2, 'data': reportData}] logger.debug(queryset) output = six.StringIO() try: report = AccessReport(queryset=queryset) report.title = ugettext('UDS Pools Performance Report') report.generate_by(PDFGenerator, filename=output) return output.getvalue() except Exception: logger.exception('Errool') return None
class WindowsOsManager(osmanagers.OSManager): typeName = _('Windows Basic OS Manager') typeType = 'WindowsManager' typeDescription = _( 'Os Manager to control windows machines without domain. (Basically renames machine)' ) iconFile = 'wosmanager.png' onLogout = gui.ChoiceField( label=_('On Logout'), order=10, rdonly=True, tooltip=_('What to do when user logs out from service'), values=[{ 'id': 'keep', 'text': _('Keep service assigned') }, { 'id': 'remove', 'text': _('Remove service') }], defvalue='keep') idle = gui.NumericField( label=_("Max.Idle time"), length=4, defvalue=-1, rdonly=False, order=11, tooltip= _('Maximum idle time (in seconds) before session is automaticatlly closed to the user (<= 0 means no max. idle time)' ), required=True) @staticmethod def validateLen(length): try: length = int(length) except Exception: raise osmanagers.OSManager.ValidationException( _('Length must be numeric!!')) if length > 6 or length < 1: raise osmanagers.OSManager.ValidationException( _('Length must be betwen 1 and 6')) return length def __setProcessUnusedMachines(self): self.processUnusedMachines = self._onLogout == 'remove' def __init__(self, environment, values): super(WindowsOsManager, self).__init__(environment, values) if values is not None: self._onLogout = values['onLogout'] self._idle = int(values['idle']) else: self._onLogout = '' self._idle = -1 self.__setProcessUnusedMachines() def release(self, service): pass def getName(self, service): ''' gets name from deployed ''' return service.getName() def infoVal(self, service): return 'rename:' + self.getName(service) def infoValue(self, service): return 'rename\r' + self.getName(service) def notifyIp(self, uid, service, data): si = service.getInstance() ip = '' # Notifies IP to deployed pairs = data.split(',') for p in pairs: key, val = p.split('=') if key.lower() == uid.lower(): si.setIp(val) ip = val break self.logKnownIp(service, ip) service.updateData(si) def doLog(self, service, data, origin=log.OSMANAGER): # Stores a log associated with this service try: msg, level = data.split('\t') try: level = int(level) except Exception: logger.debug('Do not understand level {}'.format(level)) level = log.INFO log.doLog(service, level, msg, origin) except Exception: logger.exception('LinuxOs Manager message log: ') log.doLog(service, log.ERROR, "do not understand {0}".format(data), origin) def process(self, service, msg, data, options): ''' We understand this messages: * msg = info, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class) (old method) * msg = information, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class) (new method) * msg = logon, data = Username, Informs that the username has logged in inside the machine * msg = logoff, data = Username, Informs that the username has logged out of the machine * msg = ready, data = None, Informs machine ready to be used ''' logger.info( "Invoked WindowsOsManager for {0} with params: {1},{2}".format( service, msg, data)) # We get from storage the name for this service. If no name, we try to assign a new one ret = "ok" notifyReady = False doRemove = False state = service.os_state if msg == "info": ret = self.infoVal(service) state = State.PREPARING elif msg == "information": ret = self.infoValue(service) state = State.PREPARING elif msg == "log": self.doLog(service, data, log.ACTOR) elif msg == "logon" or msg == 'login': if '\\' not in data: self.loggedIn(service, data, False) service.setInUse(True) # We get the service logged hostname & ip and returns this ip, hostname = service.getConnectionSource() ret = "{0}\t{1}".format(ip, hostname) elif msg == "logoff" or msg == 'logout': self.loggedOut(service, data, False) if self._onLogout == 'remove': doRemove = True elif msg == "ip": # This ocurss on main loop inside machine, so service is usable state = State.USABLE self.notifyIp(service.unique_id, service, data) elif msg == "ready": state = State.USABLE notifyReady = True self.notifyIp(service.unique_id, service, data) service.setOsState(state) # If notifyReady is not true, save state, let UserServiceManager do it for us else if doRemove is True: service.remove() else: if notifyReady is False: service.save() else: logger.debug('Notifying ready') UserServiceManager.manager().notifyReadyFromOsManager( service, '') logger.debug('Returning {} to {} message'.format(ret, msg)) if options is not None and options.get('scramble', True) is False: return ret return scrambleMsg(ret) def processUnused(self, userService): ''' This will be invoked for every assigned and unused user service that has been in this state at least 1/2 of Globalconfig.CHECK_UNUSED_TIME This function can update userService values. Normal operation will be remove machines if this state is not valid ''' if self._onLogout == 'remove': userService.remove() def checkState(self, service): logger.debug('Checking state for service {0}'.format(service)) return State.RUNNING def maxIdle(self): ''' On production environments, will return no idle for non removable machines ''' if self._idle <= 0 or (settings.DEBUG is False and self._onLogout != 'remove'): return None return self._idle def marshal(self): ''' Serializes the os manager data so we can store it in database ''' return '\t'.join(['v2', self._onLogout, six.text_type(self._idle)]) def unmarshal(self, s): data = s.split('\t') try: if data[0] == 'v1': self._onLogout = data[1] self._idle = -1 elif data[0] == 'v2': self._onLogout, self._idle = data[1], int(data[2]) except Exception: logger.exception( 'Exception unmarshalling. Some values left as default ones') self.__setProcessUnusedMachines() def valuesDict(self): return {'onLogout': self._onLogout, 'idle': self._idle}
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 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 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, 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 LinuxOsManager(osmanagers.OSManager): typeName = _('Linux OS Manager') typeType = 'LinuxManager' typeDescription = _('Os Manager to control Linux virtual machines') iconFile = 'losmanager.png' servicesType = (serviceTypes.VDI, ) onLogout = gui.ChoiceField( label=_('Logout Action'), order=10, rdonly=True, tooltip=_('What to do when user logs out from service'), values=[ { 'id': 'keep', 'text': ugettext_lazy('Keep service assigned') }, { 'id': 'remove', 'text': ugettext_lazy('Remove service') }, { 'id': 'keep-always', 'text': ugettext_lazy('Keep service assigned even on new publication') }, ], defvalue='keep') idle = gui.NumericField( label=_("Max.Idle time"), length=4, defvalue=-1, rdonly=False, order=11, tooltip= _('Maximum idle time (in seconds) before session is automatically closed to the user (<= 0 means no max idle time).' ), required=True) def __setProcessUnusedMachines(self): self.processUnusedMachines = self._onLogout == 'remove' def __init__(self, environment, values): super(LinuxOsManager, self).__init__(environment, values) if values is not None: self._onLogout = values['onLogout'] self._idle = int(values['idle']) else: self._onLogout = '' self._idle = -1 self.__setProcessUnusedMachines() def release(self, service): pass def isRemovableOnLogout(self, userService): ''' Says if a machine is removable on logout ''' if userService.in_use == False: if (self._onLogout == 'remove') or (not userService.isValidPublication() and self._onLogout == 'keep'): return True return False def getName(self, service): """ gets name from deployed """ return service.getName() def infoVal(self, service): return 'rename:' + self.getName(service) def infoValue(self, service): return 'rename\r' + self.getName(service) def notifyIp(self, uid, service, data): si = service.getInstance() ip = '' # Notifies IP to deployed for p in data['ips']: if p[0].lower() == uid.lower(): si.setIp(p[1]) ip = p[1] break self.logKnownIp(service, ip) service.updateData(si) def doLog(self, service, data, origin=log.OSMANAGER): # Stores a log associated with this service try: msg, level = data.split('\t') try: level = int(level) except Exception: logger.debug('Do not understand level {}'.format(level)) level = log.INFO log.doLog(service, level, msg, origin) except Exception: log.doLog(service, log.ERROR, "do not understand {0}".format(data), origin) def process(self, userService, msg, data, options=None): """ We understand this messages: * msg = info, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class), old method * msg = information, data = None. Get information about name of machine (or domain, in derived WinDomainOsManager class), new method * msg = logon, data = Username, Informs that the username has logged in inside the machine * msg = logoff, data = Username, Informs that the username has logged out of the machine * msg = ready, data = None, Informs machine ready to be used """ logger.info( "Invoked LinuxOsManager for {0} with params: {1},{2}".format( userService, msg, data)) # We get from storage the name for this userService. If no name, we try to assign a new one ret = "ok" notifyReady = False doRemove = False state = userService.os_state if msg in ('ready', 'ip'): if not isinstance( data, dict ): # Old actors, previous to 2.5, convert it information.. data = { 'ips': [v.split('=') for v in data.split(',')], 'hostname': userService.friendly_name } # Old "info" state, will be removed in a near future if msg == "info": ret = self.infoVal(userService) state = State.PREPARING elif msg == "information": ret = self.infoValue(userService) state = State.PREPARING elif msg == "log": self.doLog(userService, data, log.ACTOR) elif msg == "login": self.loggedIn(userService, data, False) ip, hostname = userService.getConnectionSource() deadLine = userService.deployed_service.getDeadline() ret = "{0}\t{1}\t{2}".format(ip, hostname, 0 if deadLine is None else deadLine) elif msg == "logout": self.loggedOut(userService, data, False) doRemove = self.isRemovableOnLogout(userService) elif msg == "ip": # This ocurss on main loop inside machine, so userService is usable state = State.USABLE self.notifyIp(userService.unique_id, userService, data) elif msg == "ready": self.toReady(userService) state = State.USABLE notifyReady = True self.notifyIp(userService.unique_id, userService, data) userService.setOsState(state) # If notifyReady is not true, save state, let UserServiceManager do it for us else if doRemove is True: userService.release() else: if notifyReady is False: userService.save() else: UserServiceManager.manager().notifyReadyFromOsManager( userService, '') logger.debug('Returning {0}'.format(ret)) return ret def processUnused(self, userService): """ This will be invoked for every assigned and unused user service that has been in this state at least 1/2 of Globalconfig.CHECK_UNUSED_TIME This function can update userService values. Normal operation will be remove machines if this state is not valid """ if self.isRemovableOnLogout(userService): userService.remove() def isPersistent(self): return self._onLogout == 'keep-always' def checkState(self, service): logger.debug('Checking state for service {0}'.format(service)) return State.RUNNING def maxIdle(self): """ On production environments, will return no idle for non removable machines """ if self._idle <= 0: # or (settings.DEBUG is False and self._onLogout != 'remove'): return None return self._idle def marshal(self): """ Serializes the os manager data so we can store it in database """ return '\t'.join(['v2', self._onLogout, six.text_type(self._idle)]).encode('utf8') def unmarshal(self, s): data = s.decode('utf8').split('\t') if data[0] == 'v1': self._onLogout = data[1] self._idle = -1 elif data[0] == 'v2': self._onLogout, self._idle = data[1], int(data[2]) self.__setProcessUnusedMachines() def valuesDict(self): return {'onLogout': self._onLogout, 'idle': self._idle}
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 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 StatsReportLogin(StatsReport): filename = 'access.pdf' name = _('Users access report by date') # Report name description = _( 'Report of user access to platform by date') # Report description uuid = '0f62f19a-f166-11e4-8f59-10feed05884b' # Input fields startDate = gui.DateField(order=1, label=_('Starting date'), tooltip=_('starting date for report'), defvalue=datetime.date.min, required=True) endDate = gui.DateField(order=2, label=_('Finish date'), tooltip=_('finish date for report'), defvalue=datetime.date.max, required=True) samplingPoints = gui.NumericField( order=3, label=_('Number of intervals'), length=3, minValue=0, maxValue=128, tooltip=_('Number of sampling points used in charts'), defvalue='64') def initialize(self, values): pass def initGui(self): pass def getRangeData(self): start = self.startDate.stamp() end = self.endDate.stamp() if self.samplingPoints.num() < 8: self.samplingPoints.value = (self.endDate.date() - self.startDate.date()).days if self.samplingPoints.num() < 2: self.samplingPoints.value = 2 if self.samplingPoints.num() > 128: self.samplingPoints.value = 128 samplingPoints = self.samplingPoints.num() # x axis label format if end - start > 3600 * 24 * 2: xLabelFormat = 'SHORT_DATE_FORMAT' else: xLabelFormat = 'SHORT_DATETIME_FORMAT' samplingIntervals = [] prevVal = None for val in range(start, end, (end - start) / (samplingPoints + 1)): if prevVal is None: prevVal = val continue samplingIntervals.append((prevVal, val)) prevVal = val data = [] reportData = [] for interval in samplingIntervals: key = (interval[0] + interval[1]) / 2 val = events.statsManager().getEvents(events.OT_AUTHENTICATOR, events.ET_LOGIN, since=interval[0], to=interval[1]).count() data.append((key, val)) # @UndefinedVariable reportData.append({ 'date': tools.timestampAsStr(interval[0], xLabelFormat) + ' - ' + tools.timestampAsStr(interval[1], xLabelFormat), 'users': val }) return (xLabelFormat, data, reportData) def getWeekHourlyData(self): start = self.startDate.stamp() end = self.endDate.stamp() dataWeek = [0] * 7 dataHour = [0] * 24 for val in events.statsManager().getEvents(events.OT_AUTHENTICATOR, events.ET_LOGIN, since=start, to=end): s = datetime.datetime.fromtimestamp(val.stamp) dataWeek[s.weekday()] += 1 dataHour[s.hour] += 1 return (dataWeek, dataHour) def generate(self): # Sample query: # 'SELECT *, count(*) as number, CEIL(stamp/(3600))*3600 as block' # ' FROM {table}' # ' WHERE event_type = 0 and stamp >= {start} and stamp <= {end}' # ' GROUP BY CEIL(stamp/(3600))' # ' ORDER BY block' # Generate the sampling intervals and get data from db start = self.startDate.stamp() end = self.endDate.stamp() xLabelFormat, data, reportData = self.getRangeData() # # User access by date graph # surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT) # @UndefinedVariable dataset = ((ugettext('Users access to UDS'), data), ) options = { 'encoding': 'utf-8', 'axis': { 'x': { 'ticks': [ dict(v=i, label=filters.date( datetime.datetime.fromtimestamp(i), xLabelFormat)) for i in range(start, end, (end - start) / 11) ], 'range': (start, end), 'showLines': True, }, 'y': { 'tickCount': 10, 'showLines': True, }, 'tickFontSize': 16, }, 'background': { 'chartColor': '#f0f0f0', 'baseColor': '#f0f0f0', 'lineColor': '#187FF2' }, 'colorScheme': { 'name': 'gradient', 'args': { 'initialColor': '#B8CA16', }, }, 'legend': { 'hide': False, 'legendFontSize': 16, 'position': { 'left': 48, 'bottom': 8, } }, 'padding': { 'left': 48, 'top': 16, 'right': 48, 'bottom': 48, }, 'title': _('Users access to UDS') } chart = pycha.line.LineChart(surface, options) chart.addDataset(dataset) chart.render() img = PILImage.frombuffer("RGBA", (surface.get_width(), surface.get_height()), surface.get_data(), "raw", "BGRA", 0, 1) # # User access by day of week # dataWeek, dataHour = self.getWeekHourlyData() dataset = ((ugettext('Users access to UDS'), [(i, dataWeek[i]) for i in range(0, 7)]), ) options['axis'] = { 'x': { 'ticks': [dict(v=i, label='Day {}'.format(i)) for i in range(0, 7)], 'range': (0, 6), 'showLines': True, }, 'y': { 'tickCount': 10, 'showLines': True, }, 'tickFontSize': 16, } chart = pycha.bar.VerticalBarChart(surface, options) chart.addDataset(dataset) chart.render() img2 = PILImage.frombuffer("RGBA", (surface.get_width(), surface.get_height()), surface.get_data(), "raw", "BGRA", 0, 1) # Hourly chart dataset = ((ugettext('Users access to UDS'), [(i, dataHour[i]) for i in range(0, 24)]), ) options['axis'] = { 'x': { 'ticks': [dict(v=i, label='{}:00'.format(i)) for i in range(0, 24)], 'range': (0, 24), 'showLines': True, }, 'y': { 'tickCount': 10, 'showLines': True, }, 'tickFontSize': 16, } chart = pycha.bar.VerticalBarChart(surface, options) chart.addDataset(dataset) chart.render() img3 = PILImage.frombuffer("RGBA", (surface.get_width(), surface.get_height()), surface.get_data(), "raw", "BGRA", 0, 1) output = six.StringIO() queryset = [{ 'image': img, 'image2': img2, 'image3': img3, 'data': reportData }] logger.debug(queryset) try: report = AccessReport(queryset=queryset) report.title = ugettext('Users access to UDS') # report = UsersReport(queryset=users) report.generate_by(PDFGenerator, filename=output) return output.getvalue() except Exception: logger.exception('Errool') return None
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 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)