Ejemplo n.º 1
0
class BaseRDPTransport(transports.Transport):
    """
    Provides access via RDP to service.
    This transport can use an domain. If username processed by authenticator contains '@', it will split it and left-@-part will be username, and right password
    """
    iconFile = 'rdp.png'
    protocol = transports.protocols.RDP

    useEmptyCreds = gui.CheckBoxField(label=_('Empty creds'), order=11, tooltip=_('If checked, the credentials used to connect will be emtpy'), tab=gui.CREDENTIALS_TAB)
    fixedName = gui.TextField(label=_('Username'), order=12, tooltip=_('If not empty, this username will be always used as credential'), tab=gui.CREDENTIALS_TAB)
    fixedPassword = gui.PasswordField(label=_('Password'), order=13, tooltip=_('If not empty, this password will be always used as credential'), tab=gui.CREDENTIALS_TAB)
    withoutDomain = gui.CheckBoxField(label=_('Without Domain'), order=14, tooltip=_('If checked, the domain part will always be emptied (to connect to xrdp for example is needed)'), tab=gui.CREDENTIALS_TAB)
    fixedDomain = gui.TextField(label=_('Domain'), order=15, tooltip=_('If not empty, this domain will be always used as credential (used as DOMAIN\\user)'), tab=gui.CREDENTIALS_TAB)

    allowSmartcards = gui.CheckBoxField(label=_('Allow Smartcards'), order=20, tooltip=_('If checked, this transport will allow the use of smartcards'), tab=gui.PARAMETERS_TAB)
    allowPrinters = gui.CheckBoxField(label=_('Allow Printers'), order=21, tooltip=_('If checked, this transport will allow the use of user printers'), tab=gui.PARAMETERS_TAB)
    allowDrives = gui.ChoiceField(
        label=_('Local drives policy'),
        order=22,
        tooltip=_('Local drives redirection policy'),
        defvalue='false',
        values=[
            {'id': 'false', 'text': 'Allow none'},
            {'id': 'dynamic', 'text': 'Allow PnP drives'},
            {'id': 'true', 'text': 'Allow any drive'},
        ],
        tab=gui.PARAMETERS_TAB
    )
    enforceDrives = gui.TextField(
        label=_('Force drives'),
        order=23,
        tooltip=_('Use comma separated values, for example "C:,D:". If drives policy is disallowed, this will be ignored'),
        tab=gui.PARAMETERS_TAB
    )

    allowSerials = gui.CheckBoxField(label=_('Allow Serials'), order=24, tooltip=_('If checked, this transport will allow the use of user serial ports'), tab=gui.PARAMETERS_TAB)
    allowClipboard = gui.CheckBoxField(label=_('Enable clipboard'), order=25, tooltip=_('If checked, copy-paste functions will be allowed'), tab=gui.PARAMETERS_TAB, defvalue=gui.TRUE)
    allowAudio = gui.CheckBoxField(label=_('Enable sound'), order=26, tooltip=_('If checked, sound will be redirected.'), tab=gui.PARAMETERS_TAB, defvalue=gui.TRUE)
    credssp = gui.CheckBoxField(label=_('Credssp Support'), order=27, tooltip=_('If checked, will enable Credentials Provider Support)'), tab=gui.PARAMETERS_TAB, defvalue=gui.TRUE)

    screenSize = gui.ChoiceField(
        label=_('Screen Size'),
        order=30,
        tooltip=_('Screen size for this transport'),
        defvalue='-1x-1',
        values=[
            {'id': '640x480', 'text': '640x480'},
            {'id': '800x600', 'text': '800x600'},
            {'id': '1024x768', 'text': '1024x768'},
            {'id': '1366x768', 'text': '1366x768'},
            {'id': '1920x1080', 'text': '1920x1080'},
            {'id': '-1x-1', 'text': 'Full screen'},
        ],
        tab=gui.DISPLAY_TAB
    )

    colorDepth = gui.ChoiceField(
        label=_('Color depth'),
        order=31,
        tooltip=_('Color depth for this connection'),
        defvalue='24',
        values=[
            {'id': '8', 'text': '8'},
            {'id': '16', 'text': '16'},
            {'id': '24', 'text': '24'},
            {'id': '32', 'text': '32'},
        ],
        tab=gui.DISPLAY_TAB
    )

    wallpaper = gui.CheckBoxField(label=_('Wallpaper/theme'), order=32, tooltip=_('If checked, the wallpaper and themes will be shown on machine (better user experience, more bandwidth)'), tab=gui.DISPLAY_TAB)
    multimon = gui.CheckBoxField(label=_('Multiple monitors'), order=33, tooltip=_('If checked, all client monitors will be used for displaying (only works on windows clients)'), tab=gui.DISPLAY_TAB)
    aero = gui.CheckBoxField(label=_('Allow Desk.Comp.'), order=34, tooltip=_('If checked, desktop composition will be allowed'), tab=gui.DISPLAY_TAB)
    smooth = gui.CheckBoxField(label=_('Font Smoothing'), order=35, tooltip=_('If checked, fonts smoothing will be allowed'), tab=gui.DISPLAY_TAB)
    showConnectionBar = gui.CheckBoxField(label=_('Connection Bar'), order=36, tooltip=_('If checked, connection bar will be shown (only on Windows clients)'), tab=gui.DISPLAY_TAB, defvalue=gui.TRUE)

    multimedia = gui.CheckBoxField(label=_('Multimedia sync'), order=40, tooltip=_('If checked. Linux client will use multimedia parameter for xfreerdp'), tab='Linux Client')
    alsa = gui.CheckBoxField(label=_('Use Alsa'), order=41, tooltip=_('If checked, Linux client will try to use ALSA, otherwise Pulse will be used'), tab='Linux Client')
    redirectHome = gui.CheckBoxField(label=_('Redirect home folder'), order=42, tooltip=_('If checked, Linux client will try to redirect /home local folder'), tab='Linux Client', defvalue=gui.FALSE)
    printerString = gui.TextField(label=_('Printer string'), order=43, tooltip=_('If printer is checked, the printer string used with xfreerdp client'), tab='Linux Client')
    smartcardString = gui.TextField(label=_('Smartcard string'), order=44, tooltip=_('If smartcard is checked, the smartcard string used with xfreerdp client'), tab='Linux Client')
    customParameters = gui.TextField(label=_('Custom parameters'), order=45, tooltip=_('If not empty, extra parameter to include for Linux Client (for example /usb:id,dev:054c:0268, or aything compatible with your xfreerdp client)'), tab='Linux Client')

    def isAvailableFor(self, userService: 'models.UserService', ip: str) -> bool:
        """
        Checks if the transport is available for the requested destination ip
        Override this in yours transports
        """
        logger.debug('Checking availability for %s', ip)
        ready = self.cache.get(ip)
        if ready is None:
            # Check again for ready
            if self.testServer(userService, ip, '3389') is True:
                self.cache.put(ip, 'Y', READY_CACHE_TIMEOUT)
                return True
            else:
                self.cache.put(ip, 'N', READY_CACHE_TIMEOUT)
        return ready == 'Y'

    def processedUser(self, userService: 'models.UserService', user: '******') -> str:
        v = self.processUserPassword(userService, user, '')
        return v['username']

    def processUserPassword(self, userService: 'models.UserService', user: '******', password: '******') -> typing.Dict[str, typing.Any]:
        username = user.getUsernameForAuth()

        if self.fixedName.value != '':
            username = self.fixedName.value

        proc = username.split('@')
        if len(proc) > 1:
            domain = proc[1]
        else:
            domain = ''
        username = proc[0]

        if self.fixedPassword.value != '':
            password = self.fixedPassword.value
        if self.fixedDomain.value != '':
            domain = self.fixedDomain.value
        if self.useEmptyCreds.isTrue():
            username, password, domain = '', '', ''

        if self.withoutDomain.isTrue():
            domain = ''

        if domain != '':  # If has domain
            if '.' in domain:  # Dotter domain form
                username = username + '@' + domain
                domain = ''
            else:  # In case of a NETBIOS domain (not recomended), join it so processUserPassword can deal with it
                username = domain + '\\' + username
                domain = ''

        # Temporal "fix" to check if we do something on processUserPassword

        # Fix username/password acording to os manager
        username, password = userService.processUserPassword(username, password)

        # Recover domain name if needed
        if '\\' in username:
            username, domain = username.split('\\')

        return {'protocol': self.protocol, 'username': username, 'password': password, 'domain': domain}

    def getConnectionInfo(
            self,
            userService: typing.Union['models.UserService', 'models.ServicePool'],
            user: '******',
            password: str
        ) -> typing.Dict[str, str]:
        return self.processUserPassword(userService, user, password)

    def getScript(self, scriptNameTemplate: str, osName: str, params: typing.Dict[str, typing.Any]) -> typing.Tuple[str, str, typing.Dict[str, typing.Any]]:
        # Reads script
        scriptNameTemplate = scriptNameTemplate.format(osName)
        with open(os.path.join(os.path.dirname(__file__), scriptNameTemplate)) as f:
            script = f.read()
        # Reads signature
        with open(os.path.join(os.path.dirname(__file__), scriptNameTemplate + '.signature')) as f:
            signature = f.read()
        return script, signature, params
Ejemplo n.º 2
0
class NXTransport(BaseNXTransport):
    """
    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 (DEPRECATED)')
    typeType = 'NXTransport'
    typeDescription = _('NX Protocol v3.5. Direct connection.')
    iconFile = 'nx.png'
    protocol = transports.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)
    screenSize = gui.ChoiceField(label=_('Screen size'),
                                 order=10,
                                 tooltip=_('Screen size'),
                                 defvalue=CommonPrefs.SZ_FULLSCREEN,
                                 values=[{
                                     'id': CommonPrefs.SZ_640x480,
                                     'text': '640x480'
                                 }, {
                                     'id': CommonPrefs.SZ_800x600,
                                     'text': '800x600'
                                 }, {
                                     'id': CommonPrefs.SZ_1024x768,
                                     'text': '1024x768'
                                 }, {
                                     'id': CommonPrefs.SZ_1366x768,
                                     'text': '1366x768'
                                 }, {
                                     'id': CommonPrefs.SZ_1920x1080,
                                     'text': '1920x1080'
                                 }, {
                                     'id': CommonPrefs.SZ_FULLSCREEN,
                                     'text': ugettext_lazy('Full Screen')
                                 }],
                                 tab=gui.PARAMETERS_TAB)

    _useEmptyCreds: bool = False
    _fixedName: str = ''
    _fixedPassword: str = ''
    _listenPort: str = ''
    _connection: str = ''
    _session: str = ''
    _cacheDisk: str = ''
    _cacheMem: str = ''
    _screenSize: str = ''

    def initialize(self, values: 'Module.ValuesType'):
        if values:
            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']
            self._screenSize = values['screenSize']

    def marshal(self) -> bytes:
        """
        Serializes the transport data so we can store it in database
        """
        return str.join('\t', [
            'v2',
            gui.boolToStr(self._useEmptyCreds), self._fixedName,
            self._fixedPassword, self._listenPort, self._connection,
            self._session, self._cacheDisk, self._cacheMem, self._screenSize
        ]).encode('utf8')

    def unmarshal(self, data: bytes) -> None:
        values = data.decode('utf8').split('\t')
        if values[0] in ('v1', 'v2'):
            self._useEmptyCreds = gui.strToBool(values[1])
            self._fixedName, self._fixedPassword, self._listenPort, self._connection, self._session, self._cacheDisk, self._cacheMem = values[
                2:9]
            self._screenSize = values[9] if values[
                0] == 'v2' else CommonPrefs.SZ_FULLSCREEN

    def valuesDict(self) -> gui.ValuesDictType:
        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 getUDSTransportScript(  # pylint: disable=too-many-locals
        self, userService: 'models.UserService', transport: 'models.Transport',
        ip: str, os: typing.Dict[str, str], user: '******', password: str,
        request: 'HttpRequest'
    ) -> typing.Tuple[str, str, typing.Dict[str, typing.Any]]:
        username = user.getUsernameForAuth()
        proc = username.split('@')
        username = proc[0]

        if self._fixedName:
            username = self._fixedName
        if self._fixedPassword:
            password = self._fixedPassword
        if self._useEmptyCreds:
            username, password = '', ''

        # We have the credentials right now, let os manager

        width, height = CommonPrefs.getWidthHeight(self._screenSize)

        # 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.linkSpeed = self._connection
        r.desktop = self._session
        r.cachedisk = self._cacheDisk
        r.cachemem = self._cacheMem

        osName = {
            OsDetector.Windows: 'windows',
            OsDetector.Linux: 'linux',
            OsDetector.Macintosh: 'macosx'
        }.get(os['OS'])

        if osName is None:
            return super().getUDSTransportScript(userService, transport, ip,
                                                 os, user, password, request)

        sp = {
            'as_file': r.as_file,
        }

        return self.getScript('scripts/{}/direct.py', osName, sp)
Ejemplo n.º 3
0
class SimpleLDAPAuthenticator(auths.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")

    _connection: typing.Any = None
    _host: str = ''
    _port: str = ''
    _ssl: bool = False
    _username: str = ''
    _password: str = ''
    _timeout: str = ''
    _ldapBase: str = ''
    _userClass: str = ''
    _groupClass: str = ''
    _userIdAttr: str = ''
    _groupIdAttr: str = ''
    _memberAttr: str = ''
    _userNameAttr: str = ''

    def initialize(
            self, values: typing.Optional[typing.Dict[str,
                                                      typing.Any]]) -> None:
        if values:
            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

    def valuesDict(self) -> gui.ValuesDictType:
        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 marshal(self) -> bytes:
        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, data: bytes):
        vals = data.decode('utf8').split('\t')
        if vals[0] == 'v1':
            logger.debug("Data: %s", vals[1:])
            (self._host, self._port, ssl, self._username, self._password,
             self._timeout, self._ldapBase, self._userClass, self._groupClass,
             self._userIdAttr, self._groupIdAttr, self._memberAttr,
             self._userNameAttr) = vals[1:]
            self._ssl = gui.strToBool(ssl)

    def __connection(self,
                     username: typing.Optional[str] = None,
                     password: typing.Optional[str] = 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=int(self._port),
                                                   ssl=self._ssl,
                                                   timeout=int(self._timeout),
                                                   debug=False)

        return self._connection

    def __connectAs(self, username: str, password: str) -> typing.Any:
        return ldaputil.connection(username,
                                   password,
                                   self._host,
                                   ssl=self._ssl,
                                   timeout=int(self._timeout),
                                   debug=False)

    def __getUser(self,
                  username: str) -> typing.Optional[ldaputil.LDAPResultType]:
        """
        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: str) -> typing.Optional[ldaputil.LDAPResultType]:
        """
        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._ldapBase,
                                 objectClass=self._groupClass,
                                 field=self._groupIdAttr,
                                 value=groupName,
                                 attributes=[self._memberAttr],
                                 sizeLimit=LDAP_RESULT_LIMIT)

    def __getGroups(self, user: ldaputil.LDAPResultType):
        try:
            groups: typing.List[str] = []

            filter_ = '(&(objectClass=%s)(|(%s=%s)(%s=%s)))' % (
                self._groupClass, self._memberAttr, user['_id'],
                self._memberAttr, user['dn'])
            for d in ldaputil.getAsDict(con=self.__connection(),
                                        base=self._ldapBase,
                                        ldapFilter=filter_,
                                        attrList=[self._groupIdAttr],
                                        sizeLimit=10 * LDAP_RESULT_LIMIT):
                if self._groupIdAttr in d:
                    for k in d[self._groupIdAttr]:
                        groups.append(k)

            logger.debug('Groups: %s', groups)
            return groups

        except Exception:
            logger.exception('Exception at __getGroups')
            return []

    def __getUserRealName(self, usr: ldaputil.LDAPResultType) -> str:
        '''
        Tries to extract the real name for this user. Will return all atttributes (joint)
        specified in _userNameAttr (comma separated).
        '''
        return ' '.join([
            ' '.join((str(k) for k in usr.get(id_, ''))) if isinstance(
                usr.get(id_), list) else str(usr.get(id_, ''))
            for id_ in self._userNameAttr.split(',')
        ]).strip()

    def authenticate(self, username: str, credentials: str,
                     groupsManager: 'auths.GroupsManager') -> bool:
        '''
        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.groups_manager
        '''
        try:
            # Locate the user at LDAP
            user = self.__getUser(username)

            if user is None:
                return False

            # Let's see first if it credentials are fine
            self.__connectAs(
                user['dn'],
                credentials)  # Will raise an exception if it can't connect

            groupsManager.validate(self.__getGroups(user))

            return True

        except Exception:
            return False

    def createUser(self, usrData: typing.Dict[str, str]) -> None:
        '''
        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 auths.AuthenticatorException(_('Username not found'))
        # Fills back realName field
        usrData['real_name'] = self.__getUserRealName(res)

    def getRealName(self, username: str) -> str:
        '''
        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: typing.Dict[str, str]) -> None:
        '''
        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: typing.Dict[str, str]) -> None:
        '''
        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 auths.AuthenticatorException(_('Group not found'))

    def getGroups(self, username: str, groupsManager: 'auths.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 auths.AuthenticatorException(_('Username not found'))
        groupsManager.validate(self.__getGroups(user))

    def searchUsers(self,
                    pattern: str) -> typing.Iterable[typing.Dict[str, str]]:
        try:
            res = []
            for r in ldaputil.getAsDict(
                    con=self.__connection(),
                    base=self._ldapBase,
                    ldapFilter='(&(objectClass=%s)(%s=%s*))' %
                (self._userClass, self._userIdAttr, pattern),
                    attrList=[self._userIdAttr, self._userNameAttr],
                    sizeLimit=LDAP_RESULT_LIMIT):
                res.append({
                    'id': r[self._userIdAttr][0],  # Ignore @...
                    'name': self.__getUserRealName(r)
                })

            return res
        except Exception:
            logger.exception("Exception: ")
            raise auths.AuthenticatorException(
                _('Too many results, be more specific'))

    def searchGroups(self,
                     pattern: str) -> typing.Iterable[typing.Dict[str, str]]:
        try:
            res = []
            for r in ldaputil.getAsDict(
                    con=self.__connection(),
                    base=self._ldapBase,
                    ldapFilter='(&(objectClass=%s)(%s=%s*))' %
                (self._groupClass, self._groupIdAttr, pattern),
                    attrList=[self._groupIdAttr, 'memberOf', 'description'],
                    sizeLimit=LDAP_RESULT_LIMIT):
                res.append({
                    'id': r[self._groupIdAttr][0],
                    'name': r['description'][0]
                })

            return res
        except Exception:
            logger.exception("Exception: ")
            raise auths.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: %s", e)
            return [False, "Error testing connection"]

    def testConnection(self):  # pylint: disable=too-many-return-statements,too-many-branches
        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 not res:
                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")
        ]

    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)
Ejemplo n.º 4
0
class Provider(ServiceProvider):
    """
    This class represents the sample services provider

    In this class we provide:
       * The Provider functionality
       * The basic configuration parameters for the provider
       * The form fields needed by administrators to configure this provider

       :note: At class level, the translation must be simply marked as so
       using ugettext_noop. This is so cause we will translate the string when
       sent to the administration client.

    For this class to get visible at administration client as a provider type,
    we MUST register it at package __init__.

    """
    # : What kind of services we offer, this are classes inherited from Service
    offers = [ServiceOne, ServiceTwo]
    # : Name to show the administrator. This string will be translated BEFORE
    # : sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    typeName = _('Sample Provider')
    # : Type used internally to identify this provider
    typeType = 'SampleProvider'
    # : Description shown at administration interface for this provider
    typeDescription = _('Sample (and dummy) service provider')
    # : Icon file used as icon for this provider. This string will be translated
    # : BEFORE sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    iconFile = 'provider.png'

    # now comes the form fields
    # There is always two fields that are requested to the admin, that are:
    # Service Name, that is a name that the admin uses to name this provider
    # Description, that is a short description that the admin gives to this provider
    # Now we are going to add a few fields that we need to use this provider
    # Remember that these are "dummy" fields, that in fact are not required
    # but used for sample purposes
    # If we don't indicate an order, the output order of fields will be
    # "random"

    # : Remote host. Here core will translate label and tooltip, remember to
    # : mark them as _ using ugettext_noop.
    remoteHost = gui.TextField(
        oder=1,
        length=64,
        label=_('Remote host'),
        tooltip=_('This fields contains a remote host'),
        required=True,
    )
    # : Name of your pet (sample, not really needed :-) )
    petName = gui.TextField(
        order=2,
        length=32,
        label=_('Your pet\'s name'),
        tooltip=_('If you like, write the name of your pet'),
        requred=False,
        defvalue='Tux'  # : This will not get translated
    )
    # : Age of Methuselah (matusalén in spanish)
    # : in Spain there is a well-known to say that something is very old,
    # : "Tiene mas años que matusalén"(is older than Methuselah)
    methAge = gui.NumericField(
        order=3,
        length=4,  # That is, max allowed value is 9999
        label=_('Age of Methuselah'),
        tooltip=_('If you know it, please, tell me!!!'),
        required=
        True,  # : Numeric fields have always a value, so this not really needed
        defvalue='4500')

    # : Is Methuselah istill alive?
    methAlive = gui.CheckBoxField(
        order=4,
        label=_('Is Methuselah still alive?'),
        tooltip=_('If you fail, this will not get saved :-)'),
        required=True,  # : Also means nothing. Check boxes has always a value
        defvalue=gui.TRUE  # : By default, at new item, check this
    )

    methText = gui.TextField(
        order=5,
        length=512,
        multiline=5,
        label=_('Text area'),
        tooltip=_('This is a text area'),
        requred=False,
        defvalue='Write\nsomething'  # : This will not get translated
    )

    # There is more fields type, but not here the best place to cover it
    def initialize(self, values=None):
        """
        We will use the "autosave" feature for form fields, that is more than
        enought for most providers. (We simply need to store data provided by user
        and, maybe, initialize some kind of connection with this values).

        Normally provider values are rally used at sevice level, cause we never
        instantiate nothing except a service from a provider.
        """

        # If you say meth is alive, you are wrong!!! (i guess..)
        # values are only passed from administration client. Internals
        # instantiations are always empty.
        if values is not None and self.methAlive.isTrue():
            raise ServiceProvider.ValidationException(
                _('Methuselah is not alive!!! :-)'))

    # Marshal and unmarshal are defaults ones, also enought

    # As we use "autosave" fields feature, dictValues is also provided by
    # base class so we don't have to mess with all those things...

    @staticmethod
    def test(env, data):
        """
        Create your test method here so the admin can push the "check" button
        and this gets executed.
        Args:
            env: environment passed for testing (temporal environment passed)

            data: data passed for testing (data obtained from the form
            definition)

        Returns:
            Array of two elements, first is True of False, depending on test
            (True is all right, false is error),
            second is an String with error, preferably internacionalizated..

        In this case, wi well do nothing more that use the provider params

        Note also that this is an static method, that will be invoked using
        the admin user provided data via administration client, and a temporary
        environment that will be erased after invoking this method
        """
        try:
            # We instantiate the provider, but this may fail...
            instance = Provider(env, data)
            logger.debug('Methuselah has {0} years and is {1} :-)'.format(
                instance.methAge.value, instance.methAlive.value))
        except ServiceProvider.ValidationException as e:
            # If we say that meth is alive, instantiation will
            return [False, str(e)]
        except Exception as e:
            logger.exception("Exception caugth!!!")
            return [False, str(e)]
        return [True, _('Nothing tested, but all went fine..')]

    # Congratulations!!!, the needed part of your first simple provider is done!
    # Now you can go to administration panel, and check it
    #
    # From now onwards, we implement our own methods, that will be used by,
    # for example, services derived from this provider
    def host(self):
        """
        Sample method, in fact in this we just return
        the value of host field, that is an string
        """
        return self.remoteHost.value

    def methYears(self):
        """
        Another sample return, it will in fact return the Methuselah years
        """
        return self.methAge.value()
Ejemplo n.º 5
0
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.')
    group = transports.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,
    )

    tunnelWait = gui.NumericField(
        length=3,
        label=_('Tunnel wait time'),
        defvalue='60',
        minValue=5,
        maxValue=65536,
        order=2,
        tooltip=_('Maximum time to wait before closing the tunnel listener'),
        required=True,
        tab=gui.TUNNEL_TAB,
    )

    verifyCertificate = gui.CheckBoxField(
        label=_('Force SSL certificate verification'),
        order=23,
        tooltip=
        _('If enabled, the certificate of tunnel server will be verified (recommended).'
          ),
        defvalue=gui.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
    enforceDrives = BaseRDPTransport.enforceDrives
    allowSerials = BaseRDPTransport.allowSerials
    allowClipboard = BaseRDPTransport.allowClipboard
    allowAudio = BaseRDPTransport.allowAudio
    allowWebcam = BaseRDPTransport.allowWebcam

    wallpaper = BaseRDPTransport.wallpaper
    multimon = BaseRDPTransport.multimon
    aero = BaseRDPTransport.aero
    smooth = BaseRDPTransport.smooth
    showConnectionBar = BaseRDPTransport.showConnectionBar
    credssp = BaseRDPTransport.credssp

    screenSize = BaseRDPTransport.screenSize
    colorDepth = BaseRDPTransport.colorDepth

    alsa = BaseRDPTransport.alsa
    multimedia = BaseRDPTransport.multimedia
    printerString = BaseRDPTransport.printerString
    smartcardString = BaseRDPTransport.smartcardString
    customParameters = BaseRDPTransport.customParameters
    allowMacMSRDC = BaseRDPTransport.allowMacMSRDC
    customParametersMAC = BaseRDPTransport.customParametersMAC

    def initialize(self, values: 'Module.ValuesType'):
        if values:
            if values['tunnelServer'].count(':') != 1:
                raise transports.Transport.ValidationException(
                    _('Must use HOST:PORT in Tunnel Server Field'))

    def getUDSTransportScript(  # pylint: disable=too-many-locals
        self,
        userService: 'models.UserService',
        transport: 'models.Transport',
        ip: str,
        os: typing.Dict[str, str],
        user: '******',
        password: str,
        request: 'HttpRequest',
    ) -> typing.Tuple[str, str, typing.Mapping[str, typing.Any]]:
        # 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']

        # escape conflicting chars : Note, on 3.0 this should not be neccesary. Kept until more tests
        # password = password.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")

        # width, height = CommonPrefs.getWidthHeight(prefs)
        # depth = CommonPrefs.getDepth(prefs)
        width, height = self.screenSize.value.split('x')
        depth = self.colorDepth.value

        ticket = TicketStore.create_for_tunnel(
            userService=userService,
            port=3389,
            validity=self.tunnelWait.num() + 60,  # Ticket overtime
        )

        tunHost, tunPort = self.tunnelServer.value.split(':')

        r = RDPFile(width == '-1' or height == '-1',
                    width,
                    height,
                    depth,
                    target=os['OS'])
        r.enablecredsspsupport = ci.get(
            'sso') == 'True' or 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.redirectSerials = self.allowSerials.isTrue()
        r.enableClipboard = self.allowClipboard.isTrue()
        r.redirectAudio = self.allowAudio.isTrue()
        r.redirectWebcam = self.allowWebcam.isTrue()
        r.showWallpaper = self.wallpaper.isTrue()
        r.multimon = self.multimon.isTrue()
        r.desktopComposition = self.aero.isTrue()
        r.smoothFonts = self.smooth.isTrue()
        r.enablecredsspsupport = self.credssp.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
        r.enforcedShares = self.enforceDrives.value

        osName = {
            OsDetector.Windows: 'windows',
            OsDetector.Linux: 'linux',
            OsDetector.Macintosh: 'macosx',
        }.get(os['OS'])

        if osName is None:
            return super().getUDSTransportScript(userService, transport, ip,
                                                 os, user, password, request)

        sp: typing.MutableMapping[str, typing.Any] = {
            'tunHost': tunHost,
            'tunPort': tunPort,
            'tunWait': self.tunnelWait.num(),
            'tunChk': self.verifyCertificate.isTrue(),
            'ticket': ticket,
            'password': password,
            'this_server': request.build_absolute_uri('/'),
        }

        if osName == 'windows':
            if password != '':
                r.password = '******'
            sp.update({
                'as_file': r.as_file,
            })
        elif osName == 'linux':
            sp.update({
                'as_new_xfreerdp_params': r.as_new_xfreerdp_params,
            })
        else:  # Mac
            r.linuxCustomParameters = self.customParametersMAC.value
            sp.update({
                'as_new_xfreerdp_params':
                r.as_new_xfreerdp_params,
                'as_file':
                r.as_file if self.allowMacMSRDC.isTrue() else '',
                'as_rdp_url':
                r.as_rdp_url if self.allowMacMSRDC.isTrue() else '',
            })

        return self.getScript('scripts/{}/tunnel.py', osName, sp)
Ejemplo n.º 6
0
class TSNXTransport(BaseNXTransport):
    """
    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 (DEPRECATED)')
    typeType = 'TSNXTransport'
    typeDescription = _('NX protocol v3.5. Tunneled connection.')
    iconFile = 'nx.png'
    protocol = transports.protocols.NX
    group = transports.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)

    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)
    screenSize = gui.ChoiceField(label=_('Screen size'),
                                 order=10,
                                 tooltip=_('Screen size'),
                                 defvalue=CommonPrefs.SZ_FULLSCREEN,
                                 values=[{
                                     'id': CommonPrefs.SZ_640x480,
                                     'text': '640x480'
                                 }, {
                                     'id': CommonPrefs.SZ_800x600,
                                     'text': '800x600'
                                 }, {
                                     'id': CommonPrefs.SZ_1024x768,
                                     'text': '1024x768'
                                 }, {
                                     'id': CommonPrefs.SZ_1366x768,
                                     'text': '1366x768'
                                 }, {
                                     'id': CommonPrefs.SZ_1920x1080,
                                     'text': '1920x1080'
                                 }, {
                                     'id': CommonPrefs.SZ_FULLSCREEN,
                                     'text': ugettext_lazy('Full Screen')
                                 }],
                                 tab=gui.PARAMETERS_TAB)

    _tunnelServer: str = ''
    _tunnelCheckServer: str = ''
    _useEmptyCreds: bool = False
    _fixedName: str = ''
    _fixedPassword: str = ''
    _listenPort: str = ''
    _connection: str = ''
    _session: str = ''
    _cacheDisk: str = ''
    _cacheMem: str = ''
    _screenSize: str = ''

    def initialize(self, values: 'Module.ValuesType'):
        if values:
            if values['tunnelServer'].find(':') == -1:
                raise transports.Transport.ValidationException(
                    _('Must use HOST:PORT in Tunnel Server Field'))
            self._tunnelServer = values['tunnelServer']
            self._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']
            self._screenSize = values['screenSize']

    def marshal(self) -> bytes:
        """
        Serializes the transport data so we can store it in database
        """
        return str.join('\t', [
            'v2',
            gui.boolToStr(self._useEmptyCreds), self._fixedName,
            self._fixedPassword, self._listenPort, self._connection,
            self._session, self._cacheDisk, self._cacheMem, self._tunnelServer,
            self._tunnelCheckServer, self._screenSize
        ]).encode('utf8')

    def unmarshal(self, data: bytes) -> None:
        values = data.decode('utf8').split('\t')
        if values[0] in ('v1', 'v2'):
            self._useEmptyCreds = gui.strToBool(values[1])
            (self._fixedName, self._fixedPassword, self._listenPort,
             self._connection, self._session, self._cacheDisk, self._cacheMem,
             self._tunnelServer, self._tunnelCheckServer) = values[2:11]
            self._screenSize = values[11] if values[
                0] == 'v2' else CommonPrefs.SZ_FULLSCREEN

    def valuesDict(self) -> gui.ValuesDictType:
        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
        }

    def getUDSTransportScript(  # pylint: disable=too-many-locals
        self, userService: 'models.UserService', transport: 'models.Transport',
        ip: str, os: typing.Dict[str, str], user: '******', password: str,
        request: 'HttpRequest'
    ) -> typing.Tuple[str, str, typing.Dict[str, typing.Any]]:
        prefs = self.screenSize.value

        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:
            usernamsizerd = '', ''

        tunpass = ''.join(random.SystemRandom().choice(string.ascii_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: %s, password: %s', tunuser, tunpass)

        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 = '{address}'
        r.port = '{port}'
        r.linkSpeed = self._connection
        r.desktop = self._session
        r.cachedisk = self._cacheDisk
        r.cachemem = self._cacheMem

        osName = {
            OsDetector.Windows: 'windows',
            OsDetector.Linux: 'linux',
            OsDetector.Macintosh: 'macosx'
        }.get(os['OS'])

        if osName is None:
            return super().getUDSTransportScript(userService, transport, ip,
                                                 os, user, password, request)

        sp = {
            'ip': ip,
            'tunUser': tunuser,
            'tunPass': tunpass,
            'tunHost': sshHost,
            'tunPort': sshPort,
            'port': self._listenPort,
            'as_file_for_format': r.as_file_for_format
        }

        return self.getScript('scripts/{}/direct.py', osName, sp)
Ejemplo n.º 7
0
class Provider(ServiceProvider):
    '''
    This class represents the sample services provider

    In this class we provide:
       * The Provider functionality
       * The basic configuration parameters for the provider
       * The form fields needed by administrators to configure this provider

       :note: At class level, the translation must be simply marked as so
       using ugettext_noop. This is so cause we will translate the string when
       sent to the administration client.

    For this class to get visible at administration client as a provider type,
    we MUST register it at package __init__.

    '''
    # : What kind of services we offer, this are classes inherited from Service
    offers = [XenLinkedService]
    # : Name to show the administrator. This string will be translated BEFORE
    # : sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    typeName = _('Xenserver Platform Provider')
    # : Type used internally to identify this provider
    typeType = 'XenPlatform'
    # : Description shown at administration interface for this provider
    typeDescription = _('XenServer platform service provider')
    # : Icon file used as icon for this provider. This string will be translated
    # : BEFORE sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    iconFile = 'provider.png'

    # now comes the form fields
    # There is always two fields that are requested to the admin, that are:
    # Service Name, that is a name that the admin uses to name this provider
    # Description, that is a short description that the admin gives to this provider
    # Now we are going to add a few fields that we need to use this provider
    # Remember that these are "dummy" fields, that in fact are not required
    # but used for sample purposes
    # If we don't indicate an order, the output order of fields will be
    # "random"
    host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('XenServer Server IP or Hostname'), required=True)
    username = gui.TextField(length=32, label=_('Username'), order=2, tooltip=_('User with valid privileges on XenServer'), required=True, defvalue='root')
    password = gui.PasswordField(lenth=32, label=_('Password'), order=3, tooltip=_('Password of the user of XenServer'), required=True)

    maxPreparingServices = gui.NumericField(length=3, label=_('Creation concurrency'), defvalue='10', minValue=1, maxValue=65536, order=50, tooltip=_('Maximum number of concurrently creating VMs'), required=True, tab=gui.ADVANCED_TAB)
    maxRemovingServices = gui.NumericField(length=3, label=_('Removal concurrency'), defvalue='5', minValue=1, maxValue=65536, order=51, tooltip=_('Maximum number of concurrently removing VMs'), required=True, tab=gui.ADVANCED_TAB)

    macsRange = gui.TextField(length=36, label=_('Macs range'), defvalue='02:46:00:00:00:00-02:46:00:FF:FF:FF', order=90, rdonly=True,
                              tooltip=_('Range of valid macs for created machines'), required=True, tab=gui.ADVANCED_TAB)
    verifySSL = gui.CheckBoxField(label=_('Verify Certificate'), order=91,
                                  tooltip=_('If selected, certificate will be checked against system valid certificate providers'), required=True, tab=gui.ADVANCED_TAB)

    # XenServer engine, right now, only permits a connection to one server and only one per instance
    # If we want to connect to more than one server, we need keep locked access to api, change api server, etc..
    # We have implemented an "exclusive access" client that will only connect to one server at a time (using locks)
    # and this way all will be fine
    def __getApi(self, force=False):
        '''
        Returns the connection API object for XenServer (using XenServersdk)
        '''
        logger.debug('API verifySSL: {} {}'.format(self.verifySSL.value, self.verifySSL.isTrue()))
        if self._api is None or force:
            self._api = XenServer(self.host.value, '443', self.username.value, self.password.value, True, self.verifySSL.isTrue())
        return self._api

    # There is more fields type, but not here the best place to cover it
    def initialize(self, values=None):
        '''
        We will use the "autosave" feature for form fields
        '''

        # Just reset _api connection variable
        self._api = None

    def testConnection(self):
        '''
        Test that conection to XenServer server is fine

        Returns

            True if all went fine, false if id didn't
        '''
        self.__getApi().test()

    def checkTaskFinished(self, task):
        '''
        Checks a task state.
        Returns None if task is Finished
        Returns a number indicating % of completion if running
        Raises an exception with status else ('cancelled', 'unknown', 'failure')
        '''
        if task is None or task == '':
            return (True, '')
        ts = self.__getApi().getTaskInfo(task)
        logger.debug('Task status: {0}'.format(ts))
        if ts['status'] == 'running':
            return (False, ts['progress'])
        if ts['status'] == 'success':
            return (True, ts['result'])

        # Any other state, raises an exception
        raise Exception(six.text_type(ts['result']))  # Should be error message

    def getMachines(self, force=False):
        '''
        Obtains the list of machines inside XenServer.
        Machines starting with UDS are filtered out

        Args:
            force: If true, force to update the cache, if false, tries to first
            get data from cache and, if valid, return this.

        Returns
            An array of dictionaries, containing:
                'name'
                'id'
                'cluster_id'
        '''

        for m in self.__getApi().getVMs():
            if m['name'][:3] == 'UDS':
                continue
            yield m

    def getStorages(self, force=False):
        '''
        Obtains the list of storages inside XenServer.

        Args:
            force: If true, force to update the cache, if false, tries to first
            get data from cache and, if valid, return this.

        Returns
            An array of dictionaries, containing:
                'name'
                'id'
                'size'
                'used'
        '''
        return self.__getApi().getSRs()

    def getStorageInfo(self, storageId, force=False):
        '''
        Obtains the storage info

        Args:
            storageId: Id of the storage to get information about it
            force: If true, force to update the cache, if false, tries to first
            get data from cache and, if valid, return this.

        Returns

            A dictionary with following values
               'id' -> Storage id
               'name' -> Storage name
               'type' -> Storage type ('data', 'iso')
               'available' -> Space available, in bytes
               'used' -> Space used, in bytes
               # 'active' -> True or False --> This is not provided by api?? (api.storagedomains.get)

        '''
        return self.__getApi().getSRInfo(storageId)

    def getNetworks(self, force=False):
        return self.__getApi().getNetworks()

    def cloneForTemplate(self, name, comments, machineId, sr):
        task = self.__getApi().cloneVM(machineId, name, sr)
        logger.debug('Task for cloneForTemplate: {0}'.format(task))
        return task

    def convertToTemplate(self, machineId, shadowMultiplier=4):
        '''
        Publish the machine (makes a template from it so we can create COWs) and returns the template id of
        the creating machine

        Args:
            name: Name of the machine (care, only ascii characters and no spaces!!!)
            machineId: id of the machine to be published
            clusterId: id of the cluster that will hold the machine
            storageId: id of the storage tuat will contain the publication AND linked clones
            displayType: type of display (for XenServer admin interface only)

        Returns
            Raises an exception if operation could not be acomplished, or returns the id of the template being created.
        '''
        self.__getApi().convertToTemplate(machineId, shadowMultiplier)

    def getMachineState(self, machineId):
        '''
        Returns the state of the machine
        This method do not uses cache at all (it always tries to get machine state from XenServer server)

        Args:
            machineId: Id of the machine to get state

        Returns:
            one of this values:
             unassigned, down, up, powering_up, powered_down,
             paused, migrating_from, migrating_to, unknown, not_responding,
             wait_for_launch, reboot_in_progress, saving_state, restoring_state,
             suspended, image_illegal, image_locked or powering_down
             Also can return'unknown' if Machine is not known
        '''
        return self.__getApi().getMachineState(machineId)

        return State.INACTIVE

    def removeTemplate(self, templateId):
        '''
        Removes a template from XenServer server

        Returns nothing, and raises an Exception if it fails
        '''
        return self.__getApi().removeTemplate(templateId)

    def startDeployFromTemplate(self, name, comments, templateId):
        '''
        Deploys a virtual machine on selected cluster from selected template

        Args:
            name: Name (sanitized) of the machine
            comments: Comments for machine
            templateId: Id of the template to deploy from
            clusterId: Id of the cluster to deploy to
            displayType: 'vnc' or 'spice'. Display to use ad XenServer admin interface
            memoryMB: Memory requested for machine, in MB
            guaranteedMB: Minimum memory guaranteed for this machine

        Returns:
            Id of the machine being created form template
        '''
        return self.__getApi().cloneTemplate(templateId, name)

    def getVMPowerState(self, machineId):
        '''
        Returns current machine power state
        '''
        return self.__getApi().getVMPowerState(machineId)

    def startVM(self, machineId, async=True):
        '''
        Tries to start a machine. No check is done, it is simply requested to XenServer.

        This start also "resume" suspended/paused machines

        Args:
            machineId: Id of the machine

        Returns:
        '''
        return self.__getApi().startVM(machineId, async)
Ejemplo n.º 8
0
class OpenStackProvider(ServiceProvider):
    """
    This class represents the sample services provider

    In this class we provide:
       * The Provider functionality
       * The basic configuration parameters for the provider
       * The form fields needed by administrators to configure this provider

       :note: At class level, the translation must be simply marked as so
       using ugettext_noop. This is so cause we will translate the string when
       sent to the administration client.

    For this class to get visible at administration client as a provider type,
    we MUST register it at package __init__.

    """

    # : What kind of services we offer, this are classes inherited from Service
    offers = [LiveService]
    # : Name to show the administrator. This string will be translated BEFORE
    # : sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    typeName = _('OpenStack Platform Provider')
    # : Type used internally to identify this provider
    typeType = 'openStackPlatformNew'
    # : Description shown at administration interface for this provider
    typeDescription = _('OpenStack platform service provider')
    # : Icon file used as icon for this provider. This string will be translated
    # : BEFORE sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    iconFile = 'openstack.png'

    # now comes the form fields
    # There is always two fields that are requested to the admin, that are:
    # Service Name, that is a name that the admin uses to name this provider
    # Description, that is a short description that the admin gives to this provider
    # Now we are going to add a few fields that we need to use this provider
    # Remember that these are "dummy" fields, that in fact are not required
    # but used for sample purposes
    # If we don't indicate an order, the output order of fields will be
    # "random"
    endpoint = gui.TextField(
        length=128,
        label=_('Identity endpoint'),
        order=1,
        tooltip=_(
            'OpenStack identity endpoint API Access (for example, https://10.0.0.1/identity). Do not include /v3.'
        ),
        required=True,
    )

    access = gui.ChoiceField(
        label=_('Access interface'),
        order=5,
        tooltip=_('Access interface to be used'),
        values=INTERFACE_VALUES,
        defvalue='public',
    )

    domain = gui.TextField(
        length=64,
        label=_('Domain'),
        order=8,
        tooltip=_('Domain name (default is Default)'),
        required=True,
        defvalue='Default',
    )
    username = gui.TextField(
        length=64,
        label=_('Username'),
        order=9,
        tooltip=_('User with valid privileges on OpenStack'),
        required=True,
        defvalue='admin',
    )
    password = gui.PasswordField(
        lenth=32,
        label=_('Password'),
        order=10,
        tooltip=_('Password of the user of OpenStack'),
        required=True,
    )

    maxPreparingServices = gui.NumericField(
        length=3,
        label=_('Creation concurrency'),
        defvalue='10',
        minValue=1,
        maxValue=65536,
        order=50,
        tooltip=_('Maximum number of concurrently creating VMs'),
        required=True,
        tab=gui.ADVANCED_TAB,
    )
    maxRemovingServices = gui.NumericField(
        length=3,
        label=_('Removal concurrency'),
        defvalue='5',
        minValue=1,
        maxValue=65536,
        order=51,
        tooltip=_('Maximum number of concurrently removing VMs'),
        required=True,
        tab=gui.ADVANCED_TAB,
    )

    timeout = gui.NumericField(
        length=3,
        label=_('Timeout'),
        defvalue='10',
        minValue=1,
        maxValue=128,
        order=99,
        tooltip=_('Timeout in seconds of connection to OpenStack'),
        required=True,
        tab=gui.ADVANCED_TAB,
    )

    tenant = gui.TextField(
        length=64,
        label=_('Project Id'),
        order=6,
        tooltip=_(
            'Project (tenant) for this provider. Set only if required by server.'
        ),
        required=False,
        defvalue='',
        tab=gui.ADVANCED_TAB,
    )
    region = gui.TextField(
        length=64,
        label=_('Region'),
        order=7,
        tooltip=_('Region for this provider. Set only if required by server.'),
        required=False,
        defvalue='',
        tab=gui.ADVANCED_TAB,
    )

    useSubnetsName = gui.CheckBoxField(
        label=_('Subnets names'),
        order=8,
        tooltip=_(
            'If checked, the name of the subnets will be used instead of the names of networks'
        ),
        defvalue=gui.FALSE,
        tab=gui.ADVANCED_TAB,
    )

    httpsProxy = gui.TextField(
        length=96,
        label=_('Proxy'),
        order=91,
        tooltip=_('Proxy used for connection to azure for HTTPS connections (use PROTOCOL://host:port, i.e. http://10.10.0.1:8080)'),
        required=False,
        tab=gui.ADVANCED_TAB,
    )

    legacy = False

    # Own variables
    _api: typing.Optional[openstack.Client] = None

    def initialize(self, values: 'Module.ValuesType' = None):
        """
        We will use the "autosave" feature for form fields
        """
        self._api = None

        if values is not None:
            self.timeout.value = validators.validateTimeout(self.timeout.value)

    def api(self, projectId=None, region=None) -> openstack.Client:
        projectId = projectId or self.tenant.value or None
        region = region or self.region.value or None
        if self._api is None:
            proxies = None
            if self.httpsProxy.value.strip():
                proxies = {
                    'https': self.httpsProxy.value
                }
            self._api = openstack.Client(
                self.endpoint.value,
                -1,
                self.domain.value,
                self.username.value,
                self.password.value,
                legacyVersion=False,
                useSSL=False,
                projectId=projectId,
                region=region,
                access=self.access.value,
                proxies=proxies
            )
        return self._api

    def sanitizeVmName(self, name: str) -> str:
        return openstack.sanitizeName(name)

    def testConnection(self):
        """
        Test that conection to OpenStack server is fine

        Returns

            True if all went fine, false if id didn't
        """
        logger.debug('Testing connection to OpenStack')
        try:
            if self.api().testConnection() is False:
                raise Exception('Check connection credentials, server, etc.')
        except Exception as e:
            logger.exception('Error')
            return [False, '{}'.format(e)]

        return [True, _('OpenStack test connection passed')]

    @staticmethod
    def test(env, data):
        """
        Test ovirt Connectivity

        Args:
            env: environment passed for testing (temporal environment passed)

            data: data passed for testing (data obtained from the form
            definition)

        Returns:
            Array of two elements, first is True of False, depending on test
            (True is all right, false is error),
            second is an String with error, preferably internacionalizated..

        """
        return OpenStackProvider(env, data).testConnection()
Ejemplo n.º 9
0
class IPAuth(auths.Authenticator):
    acceptProxy = gui.CheckBoxField(
        label=_('Accept proxy'),
        order=3,
        tooltip=_(
            'If checked, requests via proxy will get FORWARDED ip address'
            ' (take care with this bein checked, can take internal IP addresses from internet)'
        ),
        tab=gui.ADVANCED_TAB)

    typeName = _('IP Authenticator')
    typeType = 'IPAuth'
    typeDescription = _('IP Authenticator')
    iconFile = 'auth.png'

    needsPassword = False
    userNameLabel = _('IP')
    groupNameLabel = _('IP Range')
    isExternalSource = True

    blockUserOnLoginFailures = False

    def getIp(self) -> str:
        ip = getRequest().ip_proxy if self.acceptProxy.isTrue(
        ) else getRequest().ip  # pylint: disable=maybe-no-member
        logger.debug('Client IP: %s', ip)
        return ip

    def getGroups(self, username: str, groupsManager: 'auths.GroupsManager'):
        # these groups are a bit special. They are in fact ip-ranges, and we must check that the ip is in betwen
        # The ranges are stored in group names
        for g in groupsManager.getGroupsNames():
            try:
                if net.ipInNetwork(username, g):
                    groupsManager.validate(g)
            except Exception as e:
                logger.error('Invalid network for IP auth: %s', e)

    def authenticate(self, username: str, credentials: str,
                     groupsManager: 'auths.GroupsManager') -> bool:
        # If credentials is a dict, that can't be sent directly from web interface, we allow entering
        # We use this "trick" so authenticators
        if username == self.getIp():
            self.getGroups(username, groupsManager)
            return True
        return False

    def internalAuthenticate(self, username: str, credentials: str,
                             groupsManager: 'auths.GroupsManager') -> bool:
        # In fact, username does not matter, will get IP from request
        username = self.getIp()  # Override provided username and use source IP
        self.getGroups(username, groupsManager)
        if groupsManager.hasValidGroups() and self.dbAuthenticator(
        ).isValidUser(username, True):
            return True
        return False

    @staticmethod
    def test(env, data):
        return _("All seems to be fine.")

    def check(self):
        return _("All seems to be fine.")

    def getJavascript(self, request: 'HttpRequest') -> typing.Optional[str]:
        # We will authenticate ip here, from request.ip
        # If valid, it will simply submit form with ip submited and a cached generated random password
        ip = self.getIp()
        gm = auths.GroupsManager(self.dbAuthenticator())
        self.getGroups(ip, gm)

        if gm.hasValidGroups() and self.dbAuthenticator().isValidUser(
                ip, True):
            return '''function setVal(element, value) {{
                        document.getElementById(element).value = value;
                    }}
                    setVal("id_user", "{ip}");
                    setVal("id_password", "{passwd}");
                    document.getElementById("loginform").submit();'''.format(
                ip=ip, passwd='')

        return 'alert("invalid authhenticator"); window.location.reload();'

    def __str__(self):
        return "IP Authenticator"
Ejemplo n.º 10
0
class TSPICETransport(BaseSpiceTransport):
    """
    Provides access via SPICE to service.
    """

    typeName = _('SPICE')
    typeType = 'TSSPICETransport'
    typeDescription = _('SPICE Protocol. Tunneled connection.')
    protocol = transports.protocols.SPICE
    group: typing.ClassVar[str] = transports.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,
    )

    tunnelWait = gui.NumericField(
        length=3,
        label=_('Tunnel wait time'),
        defvalue='30',
        minValue=5,
        maxValue=65536,
        order=2,
        tooltip=_('Maximum time to wait before closing the tunnel listener'),
        required=True,
        tab=gui.TUNNEL_TAB,
    )

    verifyCertificate = gui.CheckBoxField(
        label=_('Force SSL certificate verification'),
        order=23,
        tooltip=
        _('If enabled, the certificate of tunnel server will be verified (recommended).'
          ),
        defvalue=gui.TRUE,
        tab=gui.TUNNEL_TAB,
    )

    serverCertificate = BaseSpiceTransport.serverCertificate
    fullScreen = BaseSpiceTransport.fullScreen
    usbShare = BaseSpiceTransport.usbShare
    autoNewUsbShare = BaseSpiceTransport.autoNewUsbShare
    smartCardRedirect = BaseSpiceTransport.smartCardRedirect

    def initialize(self, values: 'Module.ValuesType'):
        if values:
            if values['tunnelServer'].count(':') != 1:
                raise transports.Transport.ValidationException(
                    _('Must use HOST:PORT in Tunnel Server Field'))

    def getUDSTransportScript(  # pylint: disable=too-many-locals
        self,
        userService: 'models.UserService',
        transport: 'models.Transport',
        ip: str,
        os: typing.Dict[str, str],
        user: '******',
        password: str,
        request: 'HttpRequest',
    ) -> typing.Tuple[str, str, typing.Mapping[str, typing.Any]]:
        userServiceInstance: typing.Any = userService.getInstance()

        # Spice connection
        con = userServiceInstance.getConsoleConnection()

        # We MAY need two tickets, one for 'insecure' port an one for secure
        ticket = ''
        if con['port']:
            ticket = TicketStore.create_for_tunnel(
                userService=userService,
                port=int(con['port']),
                validity=self.tunnelWait.num() + 60,  # Ticket overtime
            )

        ticket_secure = ''
        if con['secure_port']:
            ticket_secure = TicketStore.create_for_tunnel(
                userService=userService,
                port=int(con['secure_port']),
                validity=self.tunnelWait.num() + 60,  # Ticket overtime
            )

        tunHost, tunPort = self.tunnelServer.value.split(':')

        r = RemoteViewerFile(
            '127.0.0.1',
            '{port}',
            '{secure_port}',
            con['ticket']
            ['value'],  # This is secure ticket from kvm, not UDS ticket
            self.serverCertificate.value.strip(),
            con['cert_subject'],
            fullscreen=self.fullScreen.isTrue(),
        )
        r.usb_auto_share = self.usbShare.isTrue()
        r.new_usb_auto_share = self.autoNewUsbShare.isTrue()
        r.smartcard = self.smartCardRedirect.isTrue()

        osName = {
            OsDetector.Windows: 'windows',
            OsDetector.Linux: 'linux',
            OsDetector.Macintosh: 'macosx',
        }.get(os['OS'])

        if osName is None:
            return super().getUDSTransportScript(userService, transport, ip,
                                                 os, user, password, request)

        # if sso:  # If SSO requested, and when supported by platform
        #     userServiceInstance.desktopLogin(user, password, '')

        sp = {
            'as_file': r.as_file,
            'as_file_ns': r.as_file_ns,
            'tunHost': tunHost,
            'tunPort': tunPort,
            'tunWait': self.tunnelWait.num(),
            'tunChk': self.verifyCertificate.isTrue(),
            'ticket': ticket,
            'ticket_secure': ticket_secure,
        }

        return self.getScript('scripts/{}/tunnel.py', osName, sp)
Ejemplo n.º 11
0
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 attributes or attribute patterns (one for each line)'), 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 attributes or attribute patterns (one for each line)'), 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")

    _connection: typing.Any = None
    _host: str = ''
    _port: str = ''
    _ssl: bool = False
    _username: str = ''
    _password: str = ''
    _timeout: str = ''
    _ldapBase: str = ''
    _userClass: str = ''
    _userIdAttr: str = ''
    _groupNameAttr: str = ''
    _userNameAttr: str = ''
    _altClass: str = ''

    def __init__(self, dbAuth: 'models.Authenticator', environment: 'Environment', values: typing.Optional[typing.Dict[str, str]]):
        super().__init__(dbAuth, environment, values)
        if values:
            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']

    def __validateField(self, field: str, fieldLabel: str) -> None:
        """
        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: str) -> typing.List[str]:
        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: str, attributes: typing.MutableMapping[str, typing.Any]) -> typing.List[str]:
        res: typing.List[str] = []
        logger.debug('Attributes: %s', attributes)
        for line in field.splitlines():
            equalPos = line.find('=')
            if equalPos == -1:  # if no attr=pattern form, convert to it (only "attr", so it will be "attr=(.*)")
                line += '=(.*)'
                equalPos = line.find('=')
            attr, pattern = (line[:equalPos].lower(), line[equalPos + 1:])
            # if pattern do not have groups, define one with complete pattern (i.e. id=.* --> id=(.*))
            if pattern.find('(') == -1:
                pattern = '(' + pattern + ')'
            val = attributes.get(attr, [])

            if not isinstance(val, list):  # May we have a single value
                val = [val]

            logger.debug('Pattern: %s', pattern)

            for v in val:
                try:
                    searchResult = re.search(pattern, v, re.IGNORECASE)  # @UndefinedVariable
                    if searchResult is None:
                        continue
                    logger.debug("Found against %s: %s ", v, searchResult.groups())
                    res.append(''.join(searchResult.groups()))
                except Exception:
                    pass  # Ignore exceptions here
        logger.debug('Res: %s', res)
        return res

    def valuesDict(self) -> gui.ValuesDictType:
        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 marshal(self) -> bytes:
        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, data: bytes) -> None:
        vals = data.decode('utf8').split('\t')
        if vals[0] == 'v1':
            logger.debug("Data: %s", vals[1:])
            self._host, self._port, ssl, self._username, self._password, \
                self._timeout, self._ldapBase, self._userClass, self._userIdAttr, \
                self._groupNameAttr, _regex, self._userNameAttr = vals[1:]
            self._ssl = gui.strToBool(ssl)
            self._groupNameAttr = self._groupNameAttr + '=' + _regex
            self._userNameAttr = '\n'.join(self._userNameAttr.split(','))
        elif vals[0] == 'v2':
            logger.debug("Data v2: %s", vals[1:])
            self._host, self._port, ssl, self._username, self._password, \
                self._timeout, self._ldapBase, self._userClass, self._userIdAttr, \
                self._groupNameAttr, self._userNameAttr = vals[1:]
            self._ssl = gui.strToBool(ssl)
        elif vals[0] == 'v3':
            logger.debug("Data v3: %s", vals[1:])
            self._host, self._port, ssl, self._username, self._password, \
                self._timeout, self._ldapBase, self._userClass, self._userIdAttr, \
                self._groupNameAttr, self._userNameAttr, self._altClass = vals[1:]
            self._ssl = gui.strToBool(ssl)

    def __connection(self) -> typing.Any:
        """
        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=int(self._port),
                ssl=self._ssl,
                timeout=int(self._timeout),
                debug=False
            )

        return self._connection

    def __connectAs(self, username: str, password: str) -> typing.Any:
        return ldaputil.connection(username, password, self._host, port=int(self._port), ssl=self._ssl, timeout=int(self._timeout), debug=False)

    def __getUser(self, username: str) -> typing.Optional[ldaputil.LDAPResultType]:
        """
        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
        """
        attributes = [self._userIdAttr] + self.__getAttrsFromField(self._userNameAttr) + self.__getAttrsFromField(self._groupNameAttr)
        user = ldaputil.getFirst(
            con=self.__connection(),
            base=self._ldapBase,
            objectClass=self._userClass,
            field=self._userIdAttr,
            value=username,
            attributes=attributes,
            sizeLimit=LDAP_RESULT_LIMIT
        )

        # If user attributes is split, that is, it has more than one "ldap entry", get a second entry filtering by a new attribute
        # and add result attributes to "main" search.
        # For example, you can have authentication in an "user" object class and attributes in an "user_attributes" object class.
        # Note: This is very rare situation, but it ocurrs :)
        if user and self._altClass:
            for usr in  ldaputil.getAsDict(
                con=self.__connection(),
                base=self._ldapBase,
                ldapFilter='(&(objectClass={})({}={}))'.format(self._altClass, self._userIdAttr, ldaputil.escape(username)),
                attrList=attributes,
                sizeLimit=LDAP_RESULT_LIMIT
            ):
                for attr in self.__getAttrsFromField(self._groupNameAttr):
                    v = usr.get(attr)
                    if not v:
                        continue
                    kl = attr.lower()
                    # If already exists the field, check if it is a list to add new elements...
                    if kl in usr:
                        # Convert existing to list, so we can add a new value
                        if not isinstance(user[kl], (list, tuple)):
                            user[kl] = [user[kl]]

                        # Convert values to list, if not list
                        if not isinstance(v, (list, tuple)):
                            v = [v]

                        # Now append to existing values
                        for x in v:
                            user[kl].append(x)
                    else:
                        user[kl] = v


        return user


    def __getGroups(self, user: ldaputil.LDAPResultType):
        grps = self.__processField(self._groupNameAttr, user)
        if extra:
            grps += extra.getGroups(self, user)
        return grps

    def __getUserRealName(self, user: ldaputil.LDAPResultType):
        return ' '.join(self.__processField(self._userNameAttr, user))

    def authenticate(self, username: str, credentials: str, groupsManager: 'auths.GroupsManager') -> bool:
        """
        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.groups_manager
        """
        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: typing.Dict[str, str]) -> None:
        """
        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 auths.exceptions.AuthenticatorException(_('Username not found'))
        # Fills back realName field
        usrData['real_name'] = self.__getUserRealName(res)

    def getRealName(self, username: str) -> str:
        """
        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: typing.Dict[str, str]) -> None:
        """
        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 getGroups(self, username: str, groupsManager: 'auths.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 auths.exceptions.AuthenticatorException(_('Username not found'))
        groups = self.__getGroups(user)
        groupsManager.validate(groups)

    def searchUsers(self, pattern: str) -> typing.Iterable[typing.Dict[str, str]]:
        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('Result: %s', 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 auths.exceptions.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 %s: %s', 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")]

    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
        )
Ejemplo n.º 12
0
class OGService(Service):
    """
    OpenGnsys Service
    """

    # : Name to show the administrator. This string will be translated BEFORE
    # : sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    typeName = _('OpenGnsys Machines Service')
    # : Type used internally to identify this provider
    typeType = 'openGnsysMachine'
    # : Description shown at administration interface for this provider
    typeDescription = _('OpenGnsys physical machines')
    # : Icon file used as icon for this provider. This string will be translated
    # : BEFORE sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    iconFile = 'provider.png'

    # Functional related data

    # : If the service provides more than 1 "deployed user" (-1 = no limit,
    # : 0 = ???? (do not use it!!!), N = max number to deploy
    maxDeployed = -1
    # : If we need to generate "cache" for this service, so users can access the
    # : provided services faster. Is usesCache is True, you will need also
    # : set publicationType, do take care about that!
    usesCache = True
    # : Tooltip shown to user when this item is pointed at admin interface, none
    # : because we don't use it
    cacheTooltip = _(
        'Number of desired machines to keep running waiting for an user')

    # : If the service needs a s.o. manager (managers are related to agents
    # : provided by services itselfs, i.e. virtual machines with actors)
    needsManager = False
    # : If true, the system can't do an automatic assignation of a deployed user
    # : service from this service
    mustAssignManually = False

    # : Types of publications (preparated data for deploys)
    # : In our case, we do no need a publication, so this is None
    publicationType = OGPublication
    # : Types of deploys (services in cache and/or assigned to users)
    deployedType = OGDeployment

    allowedProtocols = protocols.GENERIC
    servicesTypeProvided = (serviceTypes.VDI, )

    # Now the form part
    ou = gui.ChoiceField(
        label=_("OU"),
        order=100,
        fills={
            'callbackName': 'osFillData',
            'function': helpers.getResources,
            'parameters': ['ov', 'ev', 'ou'],
        },
        tooltip=_('Organizational Unit'),
        required=True,
    )

    # Lab is not required, but maybe used as filter
    lab = gui.ChoiceField(label=_("lab"),
                          order=101,
                          tooltip=_('Laboratory'),
                          required=False)

    # Required, this is the base image
    image = gui.ChoiceField(
        label=_("OS Image"),
        order=102,
        tooltip=_('OpenGnsys Operating System Image'),
        required=True,
    )

    maxReservationTime = gui.NumericField(
        length=3,
        label=_("Max. reservation time"),
        order=110,
        tooltip=
        _('Security parameter for OpenGnsys to keep reservations at most this hours. Handle with care!'
          ),
        defvalue='2400',  # 1 hundred days
        minValue='24',
        tab=_('Advanced'),
        required=False,
    )

    startIfUnavailable = gui.CheckBoxField(
        label=_('Start if unavailable'),
        defvalue=gui.TRUE,
        order=111,
        tooltip=
        _('If active, machines that are not available on user connect (on some OS) will try to power on through OpenGnsys.'
          ),
    )

    ov = gui.HiddenField(value=None)
    ev = gui.HiddenField(
        value=None
    )  # We need to keep the env so we can instantiate the Provider

    def initGui(self) -> None:
        """
        Loads required values inside
        """
        ous = [
            gui.choiceItem(r['id'], r['name'])
            for r in self.parent().api.getOus()
        ]
        self.ou.setValues(ous)

        self.ov.setDefValue(self.parent().serialize())
        self.ev.setDefValue(self.parent().env.key)

    def parent(self) -> 'OGProvider':
        return typing.cast('OGProvider', super().parent())

    def status(self, machineId: str) -> typing.Any:
        return self.parent().status(machineId)

    def reserve(self) -> typing.Any:
        return self.parent().reserve(
            self.ou.value,
            self.image.value,
            self.lab.value,
            self.maxReservationTime.num(),
        )

    def unreserve(self, machineId: str) -> typing.Any:
        return self.parent().unreserve(machineId)

    def notifyEvents(self, machineId: str, token: str,
                     uuid: str) -> typing.Any:
        return self.parent().notifyEvents(machineId,
                                          self.getLoginNotifyURL(uuid, token),
                                          self.getLogoutNotifyURL(uuid, token),
                                          self.getReleaseURL(uuid, token))

    def notifyDeadline(self, machineId: str,
                       deadLine: typing.Optional[int]) -> typing.Any:
        return self.parent().notifyDeadline(machineId, deadLine)

    def powerOn(self, machineId: str) -> typing.Any:
        return self.parent().powerOn(machineId, self.image.value)

    def _notifyURL(self, uuid: str, token: str, message: str) -> str:
        # The URL is "GET messages URL".
        return '{accessURL}uds/ognotify/{message}/{token}/{uuid}'.format(
            accessURL=self.parent().getUDSServerAccessUrl(),
            uuid=uuid,
            token=token,
            message=message)

    def getLoginNotifyURL(self, uuid: str, token: str) -> str:
        return self._notifyURL(uuid, token, 'login')

    def getLogoutNotifyURL(self, uuid: str, token: str) -> str:
        return self._notifyURL(uuid, token, 'logout')

    def getReleaseURL(self, uuid: str, token: str) -> str:
        return self._notifyURL(uuid, token, 'release')

    def isRemovableIfUnavailable(self):
        return self.startIfUnavailable.isTrue()
Ejemplo n.º 13
0
class HTML5VNCTransport(transports.Transport):
    """
    Provides access via VNC 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 VNC Experimental')
    typeType = 'HTML5VNCTransport'
    typeDescription = _('VNC protocol using HTML5 client (EXPERIMENTAL)')
    iconFile = 'html5vnc.png'

    ownLink = True
    supportedOss = OsDetector.allOss
    protocol = transports.protocols.VNC
    group = transports.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)

    username = gui.TextField(
        label=_('Username'),
        order=20,
        tooltip=_('Username for VNC connection authentication.'),
        tab=gui.PARAMETERS_TAB)
    password = gui.PasswordField(
        label=_('Password'),
        order=21,
        tooltip=_('Password for VNC connection authentication'),
        tab=gui.PARAMETERS_TAB)

    vncPort = gui.NumericField(length=22,
                               label=_('VNC Server port'),
                               defvalue='5900',
                               order=2,
                               tooltip=_('Port of the VNC server.'),
                               required=True,
                               tab=gui.PARAMETERS_TAB)

    colorDepth = gui.ChoiceField(
        order=26,
        label=_('Color depth'),
        tooltip=_(
            'Color depth for VNC connection. Use this to control bandwidth.'),
        required=True,
        values=[
            gui.choiceItem('-', 'default'),
            gui.choiceItem('8', '8 bits'),
            gui.choiceItem('16', '16 bits'),
            gui.choiceItem('24', '24 bits'),
            gui.choiceItem('32', '33 bits'),
        ],
        defvalue='-',
        tab=gui.PARAMETERS_TAB)
    swapRedBlue = gui.CheckBoxField(
        label=_('Swap red/blue'),
        order=27,
        tooltip=
        _('Use this if your colours seems incorrect (blue appears red, ..) to swap them.'
          ),
        tab=gui.PARAMETERS_TAB)
    cursor = gui.CheckBoxField(
        label=_('Remote cursor'),
        order=28,
        tooltip=_('If set, force to show remote cursor'),
        tab=gui.PARAMETERS_TAB)
    readOnly = gui.CheckBoxField(
        label=_('Read only'),
        order=29,
        tooltip=_('If set, the connection will be read only'),
        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)
    forceNewWindow = gui.CheckBoxField(
        label=_('Force new HTML Window'),
        order=91,
        tooltip=
        _('If checked, every connection will try to open its own window instead of reusing the "global" one.'
          ),
        defvalue=gui.FALSE,
        tab=gui.ADVANCED_TAB)

    def initialize(self, values: 'Module.ValuesType'):
        if not values:
            return
        # Strip spaces
        self.guacamoleServer.value = self.guacamoleServer.value.strip()
        if self.guacamoleServer.value[0:4] != 'http':
            raise transports.Transport.ValidationException(
                _('The server must be http or https'))

    def isAvailableFor(self, userService: 'models.UserService',
                       ip: str) -> bool:
        """
        Checks if the transport is available for the requested destination ip
        Override this in yours transports
        """
        logger.debug('Checking availability for %s', ip)
        ready = self.cache.get(ip)
        if not ready:
            # Check again for readyness
            if self.testServer(userService, ip, self.vncPort.value) is True:
                self.cache.put(ip, 'Y', READY_CACHE_TIMEOUT)
                return True
            self.cache.put(ip, 'N', READY_CACHE_TIMEOUT)
        return ready == 'Y'

    def getLink(  # pylint: disable=too-many-locals
            self, userService: 'models.UserService',
            transport: 'models.Transport', ip: str, os: typing.Dict[str, str],
            user: '******', password: str, request: 'HttpRequest') -> str:
        # Build params dict
        params = {
            'protocol': 'vnc',
            'hostname': ip,
            'port': str(self.vncPort.num()),
        }

        if self.username.value.strip():
            params['username'] = self.username.value.strip()

        if self.password.value.strip():
            params['password'] = self.password.value.strip()

        if self.colorDepth.value != '-':
            params['color-depth'] = self.colorDepth.value

        if self.swapRedBlue.isTrue():
            params['swap-red-blue'] = 'true'

        if self.cursor.isTrue():
            params['cursor'] = 'remote'

        if self.readOnly.isTrue():
            params['read-only'] = 'true'

        logger.debug('VNC Params: %s', params)

        scrambler = cryptoManager().randomString(32)
        ticket = models.TicketStore.create(params,
                                           validity=self.ticketValidity.num())

        onw = 'o_n_w={};'.format(hash(
            transport.name)) if self.forceNewWindow.isTrue() else ''
        return str("{}/transport/?{}.{}&{}".format(self.guacamoleServer.value,
                                                   ticket, scrambler, onw))
Ejemplo n.º 14
0
class URLCustomTransport(transports.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 = _('URL Launcher')
    typeType = 'URLTransport'
    typeDescription = _('Launchs an external UDS customized URL')
    iconFile = 'url.png'

    ownLink = True
    supportedOss = OsDetector.allOss
    protocol = transports.protocols.OTHER
    group = transports.DIRECT_GROUP

    urlPattern = gui.TextField(label=_('URL Pattern'), order=1, tooltip=_('URL Pattern to open (i.e. https://_IP_/test?user=_USER_'), defvalue='https://www.udsenterprise.com', length=64, required=True)

    forceNewWindow = gui.CheckBoxField(
        label=_('Force new HTML Window'),
        order=91,
        tooltip=_('If checked, every connection will try to open its own window instead of reusing the "global" one.'),
        defvalue=gui.FALSE,
        tab=gui.ADVANCED_TAB
    )

    def initialize(self, values: 'Module.ValuesType'):
        if not values:
            return
        # Strip spaces
        if not (self.urlPattern.value.startswith('http://') or self.urlPattern.value.startswith('https://')):
            raise transports.Transport.ValidationException(_('The url must be http or https'))

    # Same check as normal RDP transport
    def isAvailableFor(self, userService: 'models.UserService', ip: str) -> bool:
        # No check is done for URL transport
        return True

    def getLink(  # pylint: disable=too-many-locals
            self,
            userService: 'models.UserService',
            transport: 'models.Transport',
            ip: str,
            os: typing.Dict[str, str],
            user: '******',
            password: str,
            request: 'HttpRequest'
        ) -> str:

        # Fix username/password acording to os manager
        username: str = user.getUsernameForAuth()
        username, password = userService.processUserPassword(username, password)

        url = (
            self.urlPattern.value.replace('_IP_', ip)
                                 .replace('_USERNAME_', username)
        )

        onw = '&o_n_w={};'.format(hash(transport.name)) if self.forceNewWindow.isTrue() else ''
        return str(
            "{}{}".format(
                url,
                onw
            )
        )
Ejemplo n.º 15
0
class Provider(ServiceProvider):
    """
    This class represents the sample services provider

    In this class we provide:
       * The Provider functionality
       * The basic configuration parameters for the provider
       * The form fields needed by administrators to configure this provider

       :note: At class level, the translation must be simply marked as so
       using ugettext_noop. This is so cause we will translate the string when
       sent to the administration client.

    For this class to get visible at administration client as a provider type,
    we MUST register it at package __init__.

    """
    # : What kind of services we offer, this are classes inherited from Service
    offers = [LiveService]
    # : Name to show the administrator. This string will be translated BEFORE
    # : sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    typeName = _('OpenStack Platform Provider')
    # : Type used internally to identify this provider
    typeType = 'openStackPlatform'
    # : Description shown at administration interface for this provider
    typeDescription = _('OpenStack platform service provider')
    # : Icon file used as icon for this provider. This string will be translated
    # : BEFORE sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    iconFile = 'provider.png'

    # now comes the form fields
    # There is always two fields that are requested to the admin, that are:
    # Service Name, that is a name that the admin uses to name this provider
    # Description, that is a short description that the admin gives to this provider
    # Now we are going to add a few fields that we need to use this provider
    # Remember that these are "dummy" fields, that in fact are not required
    # but used for sample purposes
    # If we don't indicate an order, the output order of fields will be
    # "random"
    host = gui.TextField(length=64,
                         label=_('Host'),
                         order=1,
                         tooltip=_('OpenStack Host'),
                         required=True)
    port = gui.NumericField(length=5,
                            label=_('Port'),
                            defvalue='5000',
                            order=2,
                            tooltip=_('OpenStack Port'),
                            required=True)
    ssl = gui.CheckBoxField(
        label=_('Use SSL'),
        order=3,
        tooltip=
        _('If checked, the connection will be forced to be ssl (will not work if server is not providing ssl)'
          ))

    access = gui.ChoiceField(label=_('Access interface'),
                             order=4,
                             tooltip=_('Access interface to be used'),
                             values=INTERFACE_VALUES,
                             defvalue='public')

    domain = gui.TextField(length=64,
                           label=_('Domain'),
                           order=8,
                           tooltip=_('Domain name (default is Default)'),
                           required=True,
                           defvalue='Default')
    username = gui.TextField(
        length=64,
        label=_('Username'),
        order=9,
        tooltip=_('User with valid privileges on OpenStack'),
        required=True,
        defvalue='admin')
    password = gui.PasswordField(
        lenth=32,
        label=_('Password'),
        order=10,
        tooltip=_('Password of the user of OpenStack'),
        required=True)

    maxPreparingServices = gui.NumericField(
        length=3,
        label=_('Creation concurrency'),
        defvalue='10',
        minValue=1,
        maxValue=65536,
        order=50,
        tooltip=_('Maximum number of concurrently creating VMs'),
        required=True,
        tab=gui.ADVANCED_TAB)
    maxRemovingServices = gui.NumericField(
        length=3,
        label=_('Removal concurrency'),
        defvalue='5',
        minValue=1,
        maxValue=65536,
        order=51,
        tooltip=_('Maximum number of concurrently removing VMs'),
        required=True,
        tab=gui.ADVANCED_TAB)

    timeout = gui.NumericField(
        length=3,
        label=_('Timeout'),
        defvalue='10',
        minValue=1,
        maxValue=128,
        order=99,
        tooltip=_('Timeout in seconds of connection to OpenStack'),
        required=True,
        tab=gui.ADVANCED_TAB)

    # tenant = gui.TextField(length=64, label=_('Project'), order=6, tooltip=_('Project (tenant) for this provider'), required=True, defvalue='')
    # region = gui.TextField(length=64, label=_('Region'), order=7, tooltip=_('Region for this provider'), required=True, defvalue='RegionOne')

    # Own variables
    _api = None

    def initialize(self, values=None):
        """
        We will use the "autosave" feature for form fields
        """
        # Just reset _api connection variable

        if values is not None:
            self.timeout.value = validators.validateTimeout(
                self.timeout.value, returnAsInteger=False)

    def api(self, projectId=None, region=None):
        return openStack.Client(self.host.value,
                                self.port.value,
                                self.domain.value,
                                self.username.value,
                                self.password.value,
                                useSSL=self.ssl.isTrue(),
                                projectId=projectId,
                                region=region,
                                access=self.access.value)

    def sanitizeVmName(self, name):
        return openStack.sanitizeName(name)

    def testConnection(self):
        """
        Test that conection to OpenStack server is fine

        Returns

            True if all went fine, false if id didn't
        """

        try:
            if self.api().testConnection() is False:
                raise Exception('Check connection credentials, server, etc.')
        except Exception as e:
            return [False, '{}'.format(e)]

        return [True, _('OpenStack test connection passed')]

    @staticmethod
    def test(env, data):
        """
        Test ovirt Connectivity

        Args:
            env: environment passed for testing (temporal environment passed)

            data: data passed for testing (data obtained from the form
            definition)

        Returns:
            Array of two elements, first is True of False, depending on test
            (True is all right, false is error),
            second is an String with error, preferably internacionalizated..

        """
        return Provider(env, data).testConnection()
Ejemplo n.º 16
0
class HTML5RDPTransport(transports.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 = transports.protocols.RDP
    group = transports.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('en-gb-qwerty', _('English (GB) keyboard')),
            gui.choiceItem('es-es-qwerty', _('Spanish keyboard')),
            gui.choiceItem('latam-qwerty', _('Latin American keyboard')),
            gui.choiceItem('de-de-qwertz', _('German keyboard (qwertz)')),
            gui.choiceItem('fr-fr-azerty', _('French keyboard (azerty)')),
            gui.choiceItem('fr-ch-qwertz', _('Swiss French keyboard (qwertz)')),
            gui.choiceItem('de-ch-qwertz', _('Swiss German keyboard (qwertz)')),
            gui.choiceItem('it-it-qwerty', _('Italian keyboard')),
            gui.choiceItem('sv-se-qwerty', _('Swedish keyboard')),
            gui.choiceItem('ja-jp-qwerty', _('Japanese keyboard')),
            gui.choiceItem('pt-br-qwerty', _('Brazilian 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
    )
    forceNewWindow = gui.CheckBoxField(
        label=_('Force new HTML Window'),
        order=91,
        tooltip=_('If checked, every connection will try to open its own window instead of reusing the "global" one.'),
        defvalue=gui.FALSE,
        tab=gui.ADVANCED_TAB
    )

    def initialize(self, values: 'Module.ValuesType'):
        if not values:
            return
        # Strip spaces
        self.guacamoleServer.value = self.guacamoleServer.value.strip()
        if self.guacamoleServer.value[0:4] != 'http':
            raise transports.Transport.ValidationException(_('The server must be http or https'))
        if self.useEmptyCreds.isTrue() and self.security.value != 'rdp':
            raise transports.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: 'models.UserService', ip: str) -> bool:
        """
        Checks if the transport is available for the requested destination ip
        Override this in yours transports
        """
        logger.debug('Checking availability for %s', ip)
        ready = self.cache.get(ip)
        if not ready:
            # Check again for readyness
            if self.testServer(userService, ip, '3389') is True:
                self.cache.put(ip, 'Y', READY_CACHE_TIMEOUT)
                return True
            self.cache.put(ip, 'N', READY_CACHE_TIMEOUT)
        return ready == 'Y'

    def processedUser(self, userService: 'models.UserService', user: '******') -> str:
        v = self.processUserAndPassword(userService, user, '')
        return v['username']

    def processUserAndPassword(self, userService: 'models.UserService', user: '******', password: str) -> typing.Dict[str, str]:
        username: str = user.getUsernameForAuth()

        if self.fixedName.value != '':
            username = self.fixedName.value

        proc = username.split('@')
        domain = proc[1] if len(proc) > 1 else ''
        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 no domain to be transfered, set it to ''
        if self.withoutDomain.isTrue():
            domain = ''

        if '.' in domain:  # Dotter domain form
            username = username + '@' + domain
            domain = ''

        # Fix username/password acording to os manager
        username, password = userService.processUserPassword(username, password)

        return {'protocol': self.protocol, 'username': username, 'password': password, 'domain': domain}

    def getLink(  # pylint: disable=too-many-locals
            self,
            userService: 'models.UserService',
            transport: 'models.Transport',
            ip: str,
            os: typing.Dict[str, str],
            user: '******',
            password: str,
            request: 'HttpRequest'
        ) -> str:
        credsInfo = self.processUserAndPassword(userService, user, password)
        username, password, domain = credsInfo['username'], credsInfo['password'], credsInfo['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'
            params['printer-name'] = 'UDS-Printer'

        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: %s', params)

        ticket = models.TicketStore.create(params, validity=self.ticketValidity.num())

        return HttpResponseRedirect(
            "{}/transport/{}?{}.{}&{}".format(
                self.guacamoleServer.value,
                'o_n_w' if self.forceNewWindow.isTrue() else '',
                ticket,
                scrambler,
                'javascript:window.close();'
            )
        )
Ejemplo n.º 17
0
class XenProvider(ServiceProvider):  # pylint: disable=too-many-public-methods
    """
    This class represents the sample services provider

    In this class we provide:
       * The Provider functionality
       * The basic configuration parameters for the provider
       * The form fields needed by administrators to configure this provider

       :note: At class level, the translation must be simply marked as so
       using ugettext_noop. This is so cause we will translate the string when
       sent to the administration client.

    For this class to get visible at administration client as a provider type,
    we MUST register it at package __init__.

    """
    # : What kind of services we offer, this are classes inherited from Service
    offers = [XenLinkedService]
    # : Name to show the administrator. This string will be translated BEFORE
    # : sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    typeName = _('Xenserver/XCP-NG Platforms Provider')
    # : Type used internally to identify this provider
    typeType = 'XenPlatform'
    # : Description shown at administration interface for this provider
    typeDescription = _('XenServer and XCP-NG platforms service provider')
    # : Icon file used as icon for this provider. This string will be translated
    # : BEFORE sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    iconFile = 'provider.png'

    # now comes the form fields
    # There is always two fields that are requested to the admin, that are:
    # Service Name, that is a name that the admin uses to name this provider
    # Description, that is a short description that the admin gives to this provider
    # Now we are going to add a few fields that we need to use this provider
    # Remember that these are "dummy" fields, that in fact are not required
    # but used for sample purposes
    # If we don't indicate an order, the output order of fields will be
    # "random"
    host = gui.TextField(length=64,
                         label=_('Host'),
                         order=1,
                         tooltip=_('XenServer Server IP or Hostname'),
                         required=True)
    username = gui.TextField(
        length=32,
        label=_('Username'),
        order=2,
        tooltip=_('User with valid privileges on XenServer'),
        required=True,
        defvalue='root')
    password = gui.PasswordField(
        lenth=32,
        label=_('Password'),
        order=3,
        tooltip=_('Password of the user of XenServer'),
        required=True)

    maxPreparingServices = gui.NumericField(
        length=3,
        label=_('Creation concurrency'),
        defvalue='10',
        minValue=1,
        maxValue=65536,
        order=50,
        tooltip=_('Maximum number of concurrently creating VMs'),
        required=True,
        tab=gui.ADVANCED_TAB)
    maxRemovingServices = gui.NumericField(
        length=3,
        label=_('Removal concurrency'),
        defvalue='5',
        minValue=1,
        maxValue=65536,
        order=51,
        tooltip=_('Maximum number of concurrently removing VMs'),
        required=True,
        tab=gui.ADVANCED_TAB)

    macsRange = gui.TextField(
        length=36,
        label=_('Macs range'),
        defvalue='02:46:00:00:00:00-02:46:00:FF:FF:FF',
        order=90,
        rdonly=True,
        tooltip=_('Range of valid macs for created machines'),
        required=True,
        tab=gui.ADVANCED_TAB)
    verifySSL = gui.CheckBoxField(
        label=_('Verify Certificate'),
        order=91,
        tooltip=
        _('If selected, certificate will be checked against system valid certificate providers'
          ),
        required=True,
        tab=gui.ADVANCED_TAB,
        defvalue=gui.FALSE)

    _api: typing.Optional[XenServer]

    # XenServer engine, right now, only permits a connection to one server and only one per instance
    # If we want to connect to more than one server, we need keep locked access to api, change api server, etc..
    # We have implemented an "exclusive access" client that will only connect to one server at a time (using locks)
    # and this way all will be fine
    def __getApi(self, force: bool = False) -> XenServer:
        """
        Returns the connection API object for XenServer (using XenServersdk)
        """
        if not self._api or force:
            self._api = XenServer(self.host.value, 443, self.username.value,
                                  self.password.value, True,
                                  self.verifySSL.isTrue())

        return self._api

    # There is more fields type, but not here the best place to cover it
    def initialize(self, values: 'Module.ValuesType') -> None:
        """
        We will use the "autosave" feature for form fields
        """

        # Just reset _api connection variable
        self._api = None

    def testConnection(self):
        """
        Test that conection to XenServer server is fine

        Returns

            True if all went fine, false if id didn't
        """
        self.__getApi().test()

    def checkTaskFinished(
            self, task: typing.Optional[str]) -> typing.Tuple[bool, str]:
        """
        Checks a task state.
        Returns None if task is Finished
        Returns a number indicating % of completion if running
        Raises an exception with status else ('cancelled', 'unknown', 'failure')
        """
        if not task:
            return True, ''

        ts = self.__getApi().getTaskInfo(task)
        logger.debug('Task status: %s', ts)
        if ts['status'] == 'running':
            return False, ts['progress']
        if ts['status'] == 'success':
            return True, ts['result']

        # Any other state, raises an exception
        raise Exception(str(ts['result']))  # Should be error message

    def getMachines(
        self,
        force: bool = False
    ) -> typing.Iterable[typing.MutableMapping[str, typing.Any]]:
        """
        Obtains the list of machines inside XenServer.
        Machines starting with UDS are filtered out

        Args:
            force: If true, force to update the cache, if false, tries to first
            get data from cache and, if valid, return this.

        Returns
            An array of dictionaries, containing:
                'name'
                'id'
                'cluster_id'
        """

        for m in self.__getApi().getVMs():
            if m['name'][:3] == 'UDS':
                continue
            yield m

    def getStorages(
        self,
        force: bool = False
    ) -> typing.Iterable[typing.MutableMapping[str, typing.Any]]:
        """
        Obtains the list of storages inside XenServer.

        Args:
            force: If true, force to update the cache, if false, tries to first
            get data from cache and, if valid, return this.

        Returns
            An array of dictionaries, containing:
                'name'
                'id'
                'size'
                'used'
        """
        return self.__getApi().getSRs()

    def getStorageInfo(self,
                       storageId: str,
                       force=False) -> typing.MutableMapping[str, typing.Any]:
        """
        Obtains the storage info

        Args:
            storageId: Id of the storage to get information about it
            force: If true, force to update the cache, if false, tries to first
            get data from cache and, if valid, return this.

        Returns

            A dictionary with following values
               'id' -> Storage id
               'name' -> Storage name
               'type' -> Storage type ('data', 'iso')
               'available' -> Space available, in bytes
               'used' -> Space used, in bytes
               # 'active' -> True or False --> This is not provided by api?? (api.storagedomains.get)

        """
        return self.__getApi().getSRInfo(storageId)

    def getNetworks(
        self,
        force: bool = False
    ) -> typing.Iterable[typing.MutableMapping[str, typing.Any]]:
        return self.__getApi().getNetworks()

    def cloneForTemplate(self, name: str, comments: str, machineId: str,
                         sr: str):
        task = self.__getApi().cloneVM(machineId, name, sr)
        logger.debug('Task for cloneForTemplate: %s', task)
        return task

    def convertToTemplate(self,
                          machineId: str,
                          shadowMultiplier: int = 4) -> None:
        """
        Publish the machine (makes a template from it so we can create COWs) and returns the template id of
        the creating machine

        Args:
            name: Name of the machine (care, only ascii characters and no spaces!!!)
            machineId: id of the machine to be published
            clusterId: id of the cluster that will hold the machine
            storageId: id of the storage tuat will contain the publication AND linked clones
            displayType: type of display (for XenServer admin interface only)

        Returns
            Raises an exception if operation could not be acomplished, or returns the id of the template being created.
        """
        self.__getApi().convertToTemplate(machineId, shadowMultiplier)

    def removeTemplate(self, templateId: str) -> None:
        """
        Removes a template from XenServer server

        Returns nothing, and raises an Exception if it fails
        """
        self.__getApi().removeTemplate(templateId)

    def startDeployFromTemplate(self, name: str, comments: str,
                                templateId: str) -> str:
        """
        Deploys a virtual machine on selected cluster from selected template

        Args:
            name: Name (sanitized) of the machine
            comments: Comments for machine
            templateId: Id of the template to deploy from
            clusterId: Id of the cluster to deploy to
            displayType: 'vnc' or 'spice'. Display to use ad XenServer admin interface
            memoryMB: Memory requested for machine, in MB
            guaranteedMB: Minimum memory guaranteed for this machine

        Returns:
            Id of the machine being created form template
        """
        return self.__getApi().cloneTemplate(templateId, name)

    def getVMPowerState(self, machineId: str) -> str:
        """
        Returns current machine power state
        """
        return self.__getApi().getVMPowerState(machineId)

    def startVM(self,
                machineId: str,
                asnc: bool = True) -> typing.Optional[str]:
        """
        Tries to start a machine. No check is done, it is simply requested to XenServer.

        This start also "resume" suspended/paused machines

        Args:
            machineId: Id of the machine

        Returns:
        """
        return self.__getApi().startVM(machineId, asnc)

    def stopVM(self,
               machineId: str,
               asnc: bool = True) -> typing.Optional[str]:
        """
        Tries to start a machine. No check is done, it is simply requested to XenServer

        Args:
            machineId: Id of the machine

        Returns:
        """
        return self.__getApi().stopVM(machineId, asnc)

    def resetVM(self,
                machineId: str,
                asnc: bool = True) -> typing.Optional[str]:
        """
        Tries to start a machine. No check is done, it is simply requested to XenServer

        Args:
            machineId: Id of the machine

        Returns:
        """
        return self.__getApi().resetVM(machineId, asnc)

    def canSuspendVM(self, machineId: str) -> bool:
        """
        The machine can be suspended only when "suspend" is in their operations list (mush have xentools installed)

        Args:
            machineId: Id of the machine

        Returns:
            True if the machien can be suspended
        """
        return self.__getApi().canSuspendVM(machineId)

    def suspendVM(self,
                  machineId: str,
                  asnc: bool = True) -> typing.Optional[str]:
        """
        Tries to start a machine. No check is done, it is simply requested to XenServer

        Args:
            machineId: Id of the machine

        Returns:
        """
        return self.__getApi().suspendVM(machineId, asnc)

    def resumeVM(self,
                 machineId: str,
                 asnc: bool = True) -> typing.Optional[str]:
        """
        Tries to start a machine. No check is done, it is simply requested to XenServer

        Args:
            machineId: Id of the machine

        Returns:
        """
        return self.__getApi().resumeVM(machineId, asnc)

    def removeVM(self, machineId: str) -> None:
        """
        Tries to delete a machine. No check is done, it is simply requested to XenServer

        Args:
            machineId: Id of the machine

        Returns:
        """
        self.__getApi().removeVM(machineId)

    def configureVM(self, machineId: str, netId: str, mac: str,
                    memory: int) -> None:
        self.__getApi().configureVM(machineId,
                                    mac={
                                        'network': netId,
                                        'mac': mac
                                    },
                                    memory=memory)

    def provisionVM(self, machineId: str, asnc: bool = True) -> str:
        return self.__getApi().provisionVM(machineId, asnc=asnc)

    def getMacRange(self) -> str:
        return self.macsRange.value

    @staticmethod
    def test(env: 'Environment',
             data: 'Module.ValuesType') -> typing.List[typing.Any]:
        """
        Test XenServer Connectivity

        Args:
            env: environment passed for testing (temporal environment passed)

            data: data passed for testing (data obtained from the form
            definition)

        Returns:
            Array of two elements, first is True of False, depending on test
            (True is all right, false is error),
            second is an String with error, preferably internacionalizated..

        """
        # try:
        #    # We instantiate the provider, but this may fail...
        #    instance = Provider(env, data)
        #    logger.debug('Methuselah has {0} years and is {1} :-)'
        #                 .format(instance.methAge.value, instance.methAlive.value))
        # except ServiceProvider.ValidationException as e:
        #    # If we say that meth is alive, instantiation will
        #    return [False, str(e)]
        # except Exception as e:
        #    logger.exception("Exception caugth!!!")
        #    return [False, str(e)]
        # return [True, _('Nothing tested, but all went fine..')]
        xe = XenProvider(env, data)
        try:
            xe.testConnection()
            return [True, _('Connection test successful')]
        except Exception as e:
            return [False, _("Connection failed: {}").format(str(e))]
Ejemplo n.º 18
0
class BaseX2GOTransport(transports.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 = transports.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)

    screenSize = gui.ChoiceField(label=_('Screen size'),
                                 order=10,
                                 tooltip=_('Screen size'),
                                 defvalue=CommonPrefs.SZ_FULLSCREEN,
                                 values=[{
                                     'id': CommonPrefs.SZ_640x480,
                                     'text': '640x480'
                                 }, {
                                     'id': CommonPrefs.SZ_800x600,
                                     'text': '800x600'
                                 }, {
                                     'id': CommonPrefs.SZ_1024x768,
                                     'text': '1024x768'
                                 }, {
                                     'id': CommonPrefs.SZ_1366x768,
                                     'text': '1366x768'
                                 }, {
                                     'id': CommonPrefs.SZ_1920x1080,
                                     'text': '1920x1080'
                                 }, {
                                     'id': CommonPrefs.SZ_FULLSCREEN,
                                     'text': ugettext_lazy('Full Screen')
                                 }],
                                 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: 'models.UserService',
                       ip: str) -> bool:
        """
        Checks if the transport is available for the requested destination ip
        Override this in yours transports
        """
        logger.debug('Checking availability for %s', ip)
        ready = self.cache.get(ip)
        if ready is None:
            # Check again for ready
            if connection.testServer(ip, 22):
                self.cache.put(ip, 'Y', READY_CACHE_TIMEOUT)
                return True
            self.cache.put(ip, 'N', READY_CACHE_TIMEOUT)
        return ready == 'Y'

    def getScreenSize(self) -> typing.Tuple[int, int]:
        return CommonPrefs.getWidthHeight(self.screenSize.value)

    def processedUser(self, userService: 'models.UserService',
                      user: '******') -> str:
        v = self.processUserPassword(userService, user, '')
        return v['username']

    def processUserPassword(self, userService: 'models.UserService',
                            user: '******',
                            password: str) -> typing.Dict[str, str]:
        username = user.getUsernameForAuth()

        if self.fixedName.value != '':
            username = self.fixedName.value

        # Fix username/password acording to os manager
        username, password = userService.processUserPassword(
            username, password)

        return {
            'protocol': self.protocol,
            'username': username,
            'password': ''
        }

    def getConnectionInfo(self,
                          userService: typing.Union['models.UserService',
                                                    'models.ServicePool'],
                          user: '******',
                          password: str) -> typing.Dict[str, str]:
        return self.processUserPassword(userService, user, password)

    def genKeyPairForSsh(self) -> typing.Tuple[str, str]:
        """
        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 = io.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: str, pubKey: str) -> str:
        with open(
                os.path.join(os.path.dirname(__file__),
                             'scripts/authorize.py')) as f:
            data = f.read()

        return data.replace('__USER__', user).replace('__KEY__', pubKey)

    def getAndPushKey(
            self, userName: str,
            userService: 'models.UserService') -> typing.Tuple[str, str]:
        priv, pub = self.genKeyPairForSsh()
        authScript = self.getAuthorizeScript(userName, pub)
        userServiceManager().sendScript(userService, authScript)
        return priv, pub

    def getScript(
        self, scriptNameTemplate: str, osName: str,
        params: typing.Dict[str, typing.Any]
    ) -> typing.Tuple[str, str, typing.Dict[str, typing.Any]]:
        # Reads script
        scriptNameTemplate = scriptNameTemplate.format(osName)
        with open(os.path.join(os.path.dirname(__file__),
                               scriptNameTemplate)) as f:
            script = f.read()
        # Reads signature
        with open(
                os.path.join(os.path.dirname(__file__),
                             scriptNameTemplate + '.signature')) as f:
            signature = f.read()
        return script, signature, params
Ejemplo n.º 19
0
class WinDomainOsManager(WindowsOsManager):
    typeName = _('Windows Domain OS Manager')
    typeType = 'WinDomainManager'
    typeDescription = _('Os Manager to control windows machines with domain.')
    iconFile = 'wosmanager.png'

    # Apart form data from windows os manager, we need also domain and credentials
    domain = gui.TextField(length=64, label=_('Domain'), order=1, tooltip=_('Domain to join machines to (use FQDN form, Netbios name not supported for most operations)'), required=True)
    account = gui.TextField(length=64, label=_('Account'), order=2, tooltip=_('Account with rights to add machines to domain'), required=True)
    password = gui.PasswordField(length=64, label=_('Password'), order=3, tooltip=_('Password of the account'), required=True)
    ou = gui.TextField(length=128, label=_('OU'), order=4, tooltip=_('Organizational unit where to add machines in domain (check it before using it). i.e.: ou=My Machines,dc=mydomain,dc=local'))
    grp = gui.TextField(length=64, label=_('Machine Group'), order=7, tooltip=_('Group to which add machines on creation. If empty, no group will be used. (experimental)'), tab=_('Advanced'))
    removeOnExit = gui.CheckBoxField(label=_('Machine clean'), order=8, tooltip=_('If checked, UDS will try to remove the machine from the domain USING the provided credentials'), tab=_('Advanced'), defvalue=gui.TRUE)
    serverHint = gui.TextField(length=64, label=_('Server Hint'), order=9, tooltip=_('In case of several AD servers, which one is preferred'), tab=_('Advanced'))
    ssl = gui.CheckBoxField(label=_('Use SSL'), order=10, tooltip=_('If checked,  a ssl connection to Active Directory will be used'), tab=_('Advanced'), defvalue=gui.TRUE)

    # Inherits base "onLogout"
    onLogout = WindowsOsManager.onLogout
    idle = WindowsOsManager.idle

    _domain: str
    _ou: str
    _account: str
    _pasword: str
    _group: str
    _serverHint: str
    _removeOnExit: str
    _ssl: str

    def __init__(self, environment: 'Environment', values: 'Module.ValuesType'):
        super().__init__(environment, values)
        if values:
            if values['domain'] == '':
                raise osmanagers.OSManager.ValidationException(_('Must provide a domain!'))
            # if values['domain'].find('.') == -1:
            #    raise osmanagers.OSManager.ValidationException(_('Must provide domain in FQDN'))
            if values['account'] == '':
                raise osmanagers.OSManager.ValidationException(_('Must provide an account to add machines to domain!'))
            if values['account'].find('\\') != -1:
                raise osmanagers.OSManager.ValidationException(_('DOM\\USER form is not allowed!'))
            if values['password'] == '':
                raise osmanagers.OSManager.ValidationException(_('Must provide a password for the account!'))
            self._domain = values['domain']
            self._ou = values['ou'].strip()
            self._account = values['account']
            self._password = values['password']
            self._group = values['grp'].strip()
            self._serverHint = values['serverHint'].strip()
            self._ssl = 'y' if values['ssl'] else 'n'
            self._removeOnExit = 'y' if values['removeOnExit'] else 'n'
        else:
            self._domain = ""
            self._ou = ""
            self._account = ""
            self._password = ""
            self._group = ""
            self._serverHint = ""
            self._removeOnExit = 'n'
            self._ssl = 'n'

        # self._ou = self._ou.replace(' ', ''), do not remove spaces
        if self._domain != '' and self._ou != '':
            lpath = 'dc=' + ',dc='.join((s.lower() for s in self._domain.split('.')))
            if self._ou.lower().find(lpath) == -1:
                self._ou += ',' + lpath

    def __getServerList(self) -> typing.Iterable[typing.Tuple[str, int]]:
        if self._serverHint != '':
            yield (self._serverHint, 389)

        for server in reversed(sorted(dns.resolver.query('_ldap._tcp.' + self._domain, 'SRV'), key=lambda i: i.priority * 10000 + i.weight)):
            yield (str(server.target)[:-1], server.port)

    def __connectLdap(self, servers: typing.Optional[typing.Iterable[typing.Tuple[str, int]]] = None) -> typing.Any:
        """
        Tries to connect to LDAP
        Raises an exception if not found:
            dns.resolver.NXDOMAIN
            ldaputil.LDAPError
        """
        if servers is None:
            servers = self.__getServerList()

        account = self._account
        if account.find('@') == -1:
            account += '@' + self._domain

        _str = "No servers found"
        # And if not possible, try using NON-SSL
        for server in servers:
            ssl = self._ssl == 'y'
            port = server[1] if not ssl else -1
            try:
                return ldaputil.connection(account, self._password, server[0], port, ssl=ssl, timeout=10, debug=False)
            except Exception as e:
                _str = 'Error: {}'.format(e)

        raise ldaputil.LDAPError(_str)

    def __getGroup(self, ldapConnection: typing.Any) -> typing.Optional[str]:
        base = ','.join(['DC=' + i for i in self._domain.split('.')])
        group = ldaputil.escape(self._group)
        obj: typing.Optional[typing.MutableMapping[str, typing.Any]]
        try:
            obj = next(ldaputil.getAsDict(ldapConnection, base, "(&(objectClass=group)(|(cn={0})(sAMAccountName={0})))".format(group), ['dn'], sizeLimit=50))
        except StopIteration:
            obj = None

        if obj is None:
            return None

        return obj['dn']  # Returns the DN

    def __getMachine(self, ldapConnection, machineName: str) -> typing.Optional[str]:
        # if self._ou:
        #     base = self._ou
        # else:
        base = ','.join(['DC=' + i for i in self._domain.split('.')])

        fltr = '(&(objectClass=computer)(sAMAccountName={}$))'.format(ldaputil.escape(machineName))
        obj: typing.Optional[typing.MutableMapping[str, typing.Any]]
        try:
            obj = next(ldaputil.getAsDict(ldapConnection, base, fltr, ['dn'], sizeLimit=50))
        except StopIteration:
            obj = None

        if obj is None:
            return None

        return obj['dn']  # Returns the DN

    def readyNotified(self, userService: 'UserService') -> None:
        # No group to add
        if self._group == '':
            return

        if '.' not in self._domain:
            logger.info('Adding to a group for a non FQDN domain is not supported')
            return

        # The machine is on a AD for sure, and maybe they are not already sync
        error: typing.Optional[str] = None
        for s in self.__getServerList():
            try:
                ldapConnection = self.__connectLdap(servers=(s,))

                machine = self.__getMachine(ldapConnection, userService.friendly_name)
                group = self.__getGroup(ldapConnection)
                # #
                # Direct LDAP operation "modify", maybe this need to be added to ldaputil? :)
                # #
                ldapConnection.modify_s(group, ((ldap.MOD_ADD, 'member', machine),))  # @UndefinedVariable
                error = None
                break
            except dns.resolver.NXDOMAIN:  # No domain found, log it and pass
                logger.warning('Could not find _ldap._tcp.%s', self._domain)
                log.doLog(userService, log.WARN, "Could not remove machine from domain (_ldap._tcp.{0} not found)".format(self._domain), log.OSMANAGER)
            except ldap.ALREADY_EXISTS:  # @UndefinedVariable
                # Already added this machine to this group, pass
                error = None
                break
            except ldaputil.LDAPError:
                logger.exception('Ldap Exception caught')
                error = "Could not add machine (invalid credentials? for {0})".format(self._account)
            except Exception as e:
                error = "Could not add machine {} to group {}: {}".format(userService.friendly_name, self._group, e)
                # logger.exception('Ldap Exception caught')

        if error:
            log.doLog(userService, log.WARN, error, log.OSMANAGER)
            logger.error(error)

    def release(self, userService: 'UserService') -> None:
        super().release(userService)

        # If no removal requested, just return
        if self._removeOnExit != 'y':
            return

        if '.' not in self._domain:
            # logger.info('Releasing from a not FQDN domain is not supported')
            log.doLog(userService, log.INFO, "Removing a domain machine form a non FQDN domain is not supported.", log.OSMANAGER)
            return

        try:
            ldapConnection = self.__connectLdap()
        except dns.resolver.NXDOMAIN:  # No domain found, log it and pass
            logger.warning('Could not find _ldap._tcp.%s', self._domain)
            log.doLog(userService, log.WARN, "Could not remove machine from domain (_ldap._tcp.{} not found)".format(self._domain), log.OSMANAGER)
            return
        except ldaputil.LDAPError as e:
            # logger.exception('Ldap Exception caught')
            log.doLog(userService, log.WARN, "Could not remove machine from domain ({})".format(e), log.OSMANAGER)
            return
        except Exception as e:
            # logger.exception('Exception caught')
            log.doLog(userService, log.WARN, "Could not remove machine from domain ({})".format(e), log.OSMANAGER)
            return

        try:
            res = self.__getMachine(ldapConnection, userService.friendly_name)
            if res is None:
                raise Exception('Machine {} not found on AD (permissions?)'.format(userService.friendly_name))
            ldaputil.recursive_delete(ldapConnection, res)
        except IndexError:
            logger.error('Error deleting %s from BASE %s', userService.friendly_name, self._ou)
        except Exception:
            logger.exception('Deleting from AD: ')

    def check(self) -> str:
        try:
            ldapConnection = self.__connectLdap()
        except ldaputil.LDAPError as e:
            return _('Check error: {}').format(e)
        except dns.resolver.NXDOMAIN:
            return _('Could not find server parameters (_ldap._tcp.{0} can\'t be resolved)').format(self._domain)
        except Exception as e:
            logger.exception('Exception ')
            return str(e)

        try:
            ldapConnection.search_st(self._ou, ldap.SCOPE_BASE)  # @UndefinedVariable
        except ldaputil.LDAPError as e:
            return _('Check error: {}').format(e)

        # Group
        if self._group != '':
            if self.__getGroup(ldapConnection) is None:
                return _('Check Error: group "{}" not found (using "cn" to locate it)').format(self._group)

        return _('Server check was successful')

    # pylint: disable=protected-access
    @staticmethod
    def test(env: 'Environment', data: typing.Dict[str, str]) -> typing.List[typing.Any]:
        logger.debug('Test invoked')
        try:
            wd: WinDomainOsManager = WinDomainOsManager(env, data)
            logger.debug(wd)
            try:
                ldapConnection = wd.__connectLdap()
            except ldaputil.LDAPError as e:
                return [False, _('Could not access AD using LDAP ({0})').format(e)]

            ou = wd._ou
            if ou == '':
                ou = 'cn=Computers,dc=' + ',dc='.join(wd._domain.split('.'))

            logger.info('Checking %s with ou %s', wd._domain, ou)
            r = ldapConnection.search_st(ou, ldap.SCOPE_BASE)  # @UndefinedVariable
            logger.info('Result of search: %s', r)

        except ldaputil.LDAPError:
            if not wd._ou:
                return [False, _('The default path {0} for computers was not found!!!').format(wd._ou)]
            return [False, _('The ou path {0} was not found!!!').format(wd._ou)]
        except dns.resolver.NXDOMAIN:
            return [True, _('Could not check parameters (_ldap._tcp.{0} can\'r be resolved)').format(wd._domain)]
        except Exception as e:
            logger.exception('Exception ')
            return [False, str(e)]

        return [True, _("All parameters seem to work fine.")]

    def actorData(self, userService: 'UserService') -> typing.MutableMapping[str, typing.Any]:
        return {
            'action': 'rename_ad',
            'name': userService.getName(),
            'ad': self._domain,
            'ou': self._ou,
            'username': self._account,
            'password': self._password,
        }

    def infoVal(self, userService: 'UserService') -> str:
        return 'domain:{0}\t{1}\t{2}\t{3}\t{4}'.format(self.getName(userService), self._domain, self._ou, self._account, self._password)

    def infoValue(self, userService: 'UserService') -> str:
        return 'domain\r{0}\t{1}\t{2}\t{3}\t{4}'.format(self.getName(userService), self._domain, self._ou, self._account, self._password)

    def marshal(self) -> bytes:
        '''
        Serializes the os manager data so we can store it in database
        '''
        base = typing.cast(str, encoders.encode(super().marshal(), 'hex', asText=True))
        return '\t'.join([
            'v4',
            self._domain, self._ou, self._account,
            cryptoManager().encrypt(self._password),
            base,
            self._group, self._serverHint, self._ssl, self._removeOnExit
        ]).encode('utf8')

    def unmarshal(self, data: bytes) -> None:
        values = data.decode('utf8').split('\t')
        if values[0] in ('v1', 'v2', 'v3', 'v4'):
            self._domain = values[1]
            self._ou = values[2]
            self._account = values[3]
            self._password = cryptoManager().decrypt(values[4])

        if values[0] in ('v2', 'v3', 'v4'):
            self._group = values[6]
        else:
            self._group = ''

        if values[0] in ('v3', 'v4'):
            self._serverHint = values[7]
        else:
            self._serverHint = ''

        if values[0] == 'v4':
            self._ssl = values[8]
            self._removeOnExit = values[9]
        else:
            self._ssl = 'n'
            self._removeOnExit = 'y'

        super().unmarshal(typing.cast(bytes, encoders.decode(values[5], 'hex')))

    def valuesDict(self) -> gui.ValuesDictType:
        dct = super().valuesDict()
        dct['domain'] = self._domain
        dct['ou'] = self._ou
        dct['account'] = self._account
        dct['password'] = self._password
        dct['grp'] = self._group
        dct['serverHint'] = self._serverHint
        dct['ssl'] = self._ssl == 'y'
        dct['removeOnExit'] = self._removeOnExit == 'y'
        return dct
Ejemplo n.º 20
0
class Provider(ServiceProvider):
    '''
    This class represents the sample services provider

    In this class we provide:
       * The Provider functionality
       * The basic configuration parameters for the provider
       * The form fields needed by administrators to configure this provider

       :note: At class level, the translation must be simply marked as so
       using ugettext_noop. This is so cause we will translate the string when
       sent to the administration client.

    For this class to get visible at administration client as a provider type,
    we MUST register it at package __init__.

    '''
    # : What kind of services we offer, this are classes inherited from Service
    offers = [LiveService]
    # : Name to show the administrator. This string will be translated BEFORE
    # : sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    typeName = _('OpenNebula Platform Provider')
    # : Type used internally to identify this provider
    typeType = 'openNebulaPlatform'
    # : Description shown at administration interface for this provider
    typeDescription = _('OpenNebula platform service provider')
    # : Icon file used as icon for this provider. This string will be translated
    # : BEFORE sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    iconFile = 'provider.png'

    # now comes the form fields
    # There is always two fields that are requested to the admin, that are:
    # Service Name, that is a name that the admin uses to name this provider
    # Description, that is a short description that the admin gives to this provider
    # Now we are going to add a few fields that we need to use this provider
    # Remember that these are "dummy" fields, that in fact are not required
    # but used for sample purposes
    # If we don't indicate an order, the output order of fields will be
    # "random"
    host = gui.TextField(length=64,
                         label=_('Host'),
                         order=1,
                         tooltip=_('OpenNebula Host'),
                         required=True)
    port = gui.NumericField(
        length=5,
        label=_('Port'),
        defvalue='2633',
        order=2,
        tooltip=_('OpenNebula Port (default is 2633 for non ssl connection)'),
        required=True)
    ssl = gui.CheckBoxField(
        label=_('Use SSL'),
        order=3,
        tooltip=
        _('If checked, the connection will be forced to be ssl (will not work if server is not providing ssl)'
          ))
    username = gui.TextField(
        length=32,
        label=_('Username'),
        order=4,
        tooltip=_('User with valid privileges on OpenNebula'),
        required=True,
        defvalue='oneadmin')
    password = gui.PasswordField(
        lenth=32,
        label=_('Password'),
        order=5,
        tooltip=_('Password of the user of OpenNebula'),
        required=True)

    maxPreparingServices = gui.NumericField(
        length=3,
        label=_('Creation concurrency'),
        defvalue='10',
        minValue=1,
        maxValue=65536,
        order=50,
        tooltip=_('Maximum number of concurrently creating VMs'),
        required=True,
        tab=gui.ADVANCED_TAB)
    maxRemovingServices = gui.NumericField(
        length=3,
        label=_('Removal concurrency'),
        defvalue='5',
        minValue=1,
        maxValue=65536,
        order=51,
        tooltip=_('Maximum number of concurrently removing VMs'),
        required=True,
        tab=gui.ADVANCED_TAB)

    timeout = gui.NumericField(
        length=3,
        label=_('Timeout'),
        defvalue='10',
        order=90,
        tooltip=_('Timeout in seconds of connection to OpenNebula'),
        required=True,
        tab=gui.ADVANCED_TAB)

    # Own variables
    _api = None

    def initialize(self, values=None):
        '''
        We will use the "autosave" feature for form fields
        '''

        # Just reset _api connection variable
        self._api = None

        if values is not None:
            self.timeout.value = validators.validateTimeout(
                self.timeout.value, returnAsInteger=False)
            logger.debug('Endpoint: {}'.format(self.endpoint))

    @property
    def endpoint(self):
        return 'http{}://{}:{}/RPC2'.format('s' if self.ssl.isTrue() else '',
                                            self.host.value, self.port.value)

    @property
    def api(self):
        if self._api is None:
            self._api = on.OpenNebulaClient(self.username.value,
                                            self.password.value, self.endpoint)

        logger.debug('Api: {}'.format(self._api))
        return self._api

    def resetApi(self):
        self._api = None

    def sanitizeVmName(self, name):
        return on.sanitizeName(name)

    def testConnection(self):
        '''
        Test that conection to OpenNebula server is fine

        Returns

            True if all went fine, false if id didn't
        '''

        try:
            if self.api.version[0] < '4':
                return [
                    False,
                    'OpenNebula version is not supported (required version 4.1 or newer)'
                ]
        except Exception as e:
            return [False, '{}'.format(e)]

        return [True, _('Opennebula test connection passed')]

    def getDatastores(self, datastoreType=0):
        return on.storage.enumerateDatastores(self.api, datastoreType)

    def getTemplates(self, force=False):
        return on.template.getTemplates(self.api, force)

    def makeTemplate(self, fromTemplateId, name, toDataStore):
        return on.template.create(self.api, fromTemplateId, name, toDataStore)

    def checkTemplatePublished(self, templateId):
        return on.template.checkPublished(self.api, templateId)

    def removeTemplate(self, templateId):
        return on.template.remove(self.api, templateId)

    def deployFromTemplate(self, name, templateId):
        return on.template.deployFrom(self.api, templateId, name)

    def getMachineState(self, machineId):
        '''
        Returns the state of the machine
        This method do not uses cache at all (it always tries to get machine state from OpenNebula server)

        Args:
            machineId: Id of the machine to get state

        Returns:
            one of the on.VmState Values
        '''
        return on.vm.getMachineState(self.api, machineId)

    def getMachineSubState(self, machineId):
        '''
        Returns the  LCM_STATE of a machine (must be ready)
        '''
        return on.vm.getMachineSubstate(self.api, machineId)

    def startMachine(self, machineId):
        '''
        Tries to start a machine. No check is done, it is simply requested to OpenNebula.

        This start also "resume" suspended/paused machines

        Args:
            machineId: Id of the machine

        Returns:
        '''
        on.vm.startMachine(self.api, machineId)

    def stopMachine(self, machineId):
        '''
        Tries to start a machine. No check is done, it is simply requested to OpenNebula

        Args:
            machineId: Id of the machine

        Returns:
        '''
        on.vm.stopMachine(self.api, machineId)

    def suspendMachine(self, machineId):
        '''
        Tries to start a machine. No check is done, it is simply requested to OpenNebula

        Args:
            machineId: Id of the machine

        Returns:
        '''
        on.vm.suspendMachine(self.api, machineId)

    def removeMachine(self, machineId):
        '''
        Tries to delete a machine. No check is done, it is simply requested to OpenNebula

        Args:
            machineId: Id of the machine

        Returns:
        '''
        on.vm.removeMachine(self.api, machineId)

    def getNetInfo(self, machineId, networkId=None):
        '''
        Changes the mac address of first nic of the machine to the one specified
        '''
        return on.vm.getNetInfo(self.api, machineId, networkId)

    def getConsoleConnection(self, machineId):
        display = on.vm.getDisplayConnection(self.api, machineId)

        return {
            'type': display['type'],
            'address': display['host'],
            'port': display['port'],
            'secure_port': -1,
            'monitors': 1,
            'cert_subject': '',
            'ticket': {
                'value': display['passwd'],
                'expiry': ''
            }
        }

    @staticmethod
    def test(env, data):
        '''
        Test ovirt Connectivity

        Args:
            env: environment passed for testing (temporal environment passed)

            data: data passed for testing (data obtained from the form
            definition)

        Returns:
            Array of two elements, first is True of False, depending on test
            (True is all right, false is error),
            second is an String with error, preferably internacionalizated..

        '''
        return Provider(env, data).testConnection()
Ejemplo n.º 21
0
class OGProvider(ServiceProvider):
    '''
    This class represents the sample services provider

    In this class we provide:
       * The Provider functionality
       * The basic configuration parameters for the provider
       * The form fields needed by administrators to configure this provider

       :note: At class level, the translation must be simply marked as so
       using ugettext_noop. This is so cause we will translate the string when
       sent to the administration client.

    For this class to get visible at administration client as a provider type,
    we MUST register it at package __init__.

    '''
    # : What kind of services we offer, this are classes inherited from Service
    offers = [OGService]
    # : Name to show the administrator. This string will be translated BEFORE
    # : sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    typeName = _('OpenGnsys Platform Provider')
    # : Type used internally to identify this provider
    typeType = 'openGnsysPlatform'
    # : Description shown at administration interface for this provider
    typeDescription = _('OpenGnsys platform service provider (experimental)')
    # : Icon file used as icon for this provider. This string will be translated
    # : BEFORE sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    iconFile = 'provider.png'

    # now comes the form fields
    # There is always two fields that are requested to the admin, that are:
    # Service Name, that is a name that the admin uses to name this provider
    # Description, that is a short description that the admin gives to this provider
    # Now we are going to add a few fields that we need to use this provider
    # Remember that these are "dummy" fields, that in fact are not required
    # but used for sample purposes
    # If we don't indicate an order, the output order of fields will be
    # "random"
    host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('OpenGnsys Host'), required=True)
    port = gui.NumericField(length=5, label=_('Port'), defvalue='443', order=2, tooltip=_('OpenGnsys Port (default is 443, and only ssl connection is allowed)'), required=True)
    checkCert = gui.CheckBoxField(label=_('Check Cert.'), order=3, tooltip=_('If checked, ssl certificate of OpenGnsys server must be valid (not self signed)'))
    username = gui.TextField(length=32, label=_('Username'), order=4, tooltip=_('User with valid privileges on OpenGnsys'), required=True)
    password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the user of OpenGnsys'), required=True)
    udsServerAccessUrl = gui.TextField(length=32, label=_('UDS Server URL'), order=6, tooltip=_('URL used by OpenGnsys to access UDS. If empty, UDS will guess it.'), required=False, tab=gui.PARAMETERS_TAB)

    maxPreparingServices = gui.NumericField(length=3, label=_('Creation concurrency'), defvalue='10', minValue=1, maxValue=65536, order=50, tooltip=_('Maximum number of concurrently creating VMs'), required=True, tab=gui.ADVANCED_TAB)
    maxRemovingServices = gui.NumericField(length=3, label=_('Removal concurrency'), defvalue='5', minValue=1, maxValue=65536, order=51, tooltip=_('Maximum number of concurrently removing VMs'), required=True, tab=gui.ADVANCED_TAB)

    timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=90, tooltip=_('Timeout in seconds of connection to OpenGnsys'), required=True, tab=gui.ADVANCED_TAB)

    # Own variables
    _api = None

    def initialize(self, values=None):
        '''
        We will use the "autosave" feature for form fields
        '''

        # Just reset _api connection variable
        self._api = None

        if values is not None:
            self.timeout.value = validators.validateTimeout(self.timeout.value, returnAsInteger=False)
            logger.debug('Endpoint: {}'.format(self.endpoint))

            try:
                request = values['_request']

                if self.udsServerAccessUrl.value.strip() == '':
                    self.udsServerAccessUrl.value = request.build_absolute_uri('/')

                if self.udsServerAccessUrl.value[-1] != '/':
                    self.udsServerAccessUrl.value += '/'
            except Exception:
                pass

    @property
    def endpoint(self):
        return 'https://{}:{}/opengnsys/rest'.format(self.host.value, self.port.value)

    @property
    def api(self):
        if self._api is None:
            self._api = og.OpenGnsysClient(self.username.value, self.password.value, self.endpoint, self.cache, self.checkCert.isTrue())

        logger.debug('Api: {}'.format(self._api))
        return self._api

    def resetApi(self):
        self._api = None

    def testConnection(self):
        '''
        Test that conection to OpenGnsys server is fine

        Returns

            True if all went fine, false if id didn't
        '''
        try:
            if self.api.version[0:5] < '1.1.0':
                return [False, 'OpenGnsys version is not supported (required version 1.1.0 or newer and found {})'.format(self.api.version)]
        except Exception as e:
            logger.exception('Error')
            return [False, '{}'.format(e)]

        return [True, _('OpenGnsys test connection passed')]

    @staticmethod
    def test(env, data):
        '''
        Test ovirt Connectivity

        Args:
            env: environment passed for testing (temporal environment passed)

            data: data passed for testing (data obtained from the form
            definition)

        Returns:
            Array of two elements, first is True of False, depending on test
            (True is all right, false is error),
            second is an String with error, preferably i18n..

        '''
        return OGProvider(env, data).testConnection()

    def getUDSServerAccessUrl(self):
        return self.udsServerAccessUrl.value

    def reserve(self, ou, image, lab=0, maxtime=0):
        return self.api.reserve(ou, image, lab, maxtime)

    def unreserve(self, machineId):
        return self.api.unreserve(machineId)

    def notifyEvents(self, machineId, loginURL, logoutURL):
        return self.api.notifyURLs(machineId, loginURL, logoutURL)

    def notifyDeadline(self, machineId, deadLine):
        return self.api.notifyDeadline(machineId, deadLine)

    def status(self, machineId):
        return self.api.status(machineId)
Ejemplo n.º 22
0
class TX2GOTransport(BaseX2GOTransport):
    """
    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
    """

    typeName = _('X2Go')
    typeType = 'TX2GOTransport'
    typeDescription = _('X2Go access (Experimental). Tunneled connection.')
    group = transports.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,
    )

    tunnelWait = gui.NumericField(
        length=3,
        label=_('Tunnel wait time'),
        defvalue='30',
        minValue=5,
        maxValue=65536,
        order=2,
        tooltip=_('Maximum time to wait before closing the tunnel listener'),
        required=True,
        tab=gui.TUNNEL_TAB,
    )

    verifyCertificate = gui.CheckBoxField(
        label=_('Force SSL certificate verification'),
        order=23,
        tooltip=
        _('If enabled, the certificate of tunnel server will be verified (recommended).'
          ),
        defvalue=gui.TRUE,
        tab=gui.TUNNEL_TAB,
    )

    fixedName = BaseX2GOTransport.fixedName
    screenSize = BaseX2GOTransport.screenSize
    desktopType = BaseX2GOTransport.desktopType
    customCmd = BaseX2GOTransport.customCmd
    sound = BaseX2GOTransport.sound
    exports = BaseX2GOTransport.exports
    speed = BaseX2GOTransport.speed

    soundType = BaseX2GOTransport.soundType
    keyboardLayout = BaseX2GOTransport.keyboardLayout
    pack = BaseX2GOTransport.pack
    quality = BaseX2GOTransport.quality

    def initialize(self, values: 'Module.ValuesType'):
        if values:
            if values['tunnelServer'].count(':') != 1:
                raise BaseX2GOTransport.ValidationException(
                    _('Must use HOST:PORT in Tunnel Server Field'))

    def getUDSTransportScript(  # pylint: disable=too-many-locals
        self,
        userService: 'models.UserService',
        transport: 'models.Transport',
        ip: str,
        os: typing.Dict[str, str],
        user: '******',
        password: str,
        request: 'HttpRequest',
    ) -> typing.Tuple[str, str, typing.Mapping[str, typing.Any]]:

        ci = self.getConnectionInfo(userService, user, password)
        username = ci['username']

        priv, pub = self.getAndPushKey(username, userService)

        width, height = self.getScreenSize()

        rootless = False
        desktop = self.desktopType.value
        if desktop == "UDSVAPP":
            desktop = "/usr/bin/udsvapp " + self.customCmd.value
            rootless = True

        xf = x2go_file.getTemplate(
            speed=self.speed.value,
            pack=self.pack.value,
            quality=self.quality.value,
            sound=self.sound.isTrue(),
            soundSystem=self.sound.value,
            windowManager=desktop,
            exports=self.exports.isTrue(),
            rootless=rootless,
            width=width,
            height=height,
            user=username,
        )

        ticket = TicketStore.create_for_tunnel(
            userService=userService,
            port=22,
            validity=self.tunnelWait.num() + 60,  # Ticket overtime
        )

        tunHost, tunPort = self.tunnelServer.value.split(':')

        # data
        data = {
            'os': os['OS'],
            'ip': ip,
            'port': 22,
            'key': priv,
            'width': width,
            'height': height,
            'printers': True,
            'drives': self.exports.isTrue(),
            'fullScreen': width == -1 or height == -1,
            'this_server': request.build_absolute_uri('/'),
            'xf': xf,
        }

        m = tools.DictAsObj(data)

        osName = {
            OsDetector.Windows: 'windows',
            OsDetector.Linux: 'linux',
            # OsDetector.Macintosh: 'macosx'
        }.get(os['OS'])

        if osName is None:
            return super().getUDSTransportScript(userService, transport, ip,
                                                 os, user, password, request)

        sp = {
            'tunHost': tunHost,
            'tunPort': tunPort,
            'tunWait': self.tunnelWait.num(),
            'tunChk': self.verifyCertificate.isTrue(),
            'ticket': ticket,
            'key': priv,
            'xf': xf,
        }

        return self.getScript('scripts/{}/tunnel.py', osName, sp)
Ejemplo n.º 23
0
class BaseSpiceTransport(transports.Transport):
    """
    Provides access via SPICE to service.
    This transport can use an domain. If username processed by authenticator contains '@', it will split it and left-@-part will be username, and right password
    """
    iconFile = 'spice.png'
    protocol = protocols.SPICE

    useEmptyCreds = gui.CheckBoxField(
        order=1,
        label=_('Empty credentials'),
        tooltip=_('If checked, the credentials used to connect will be emtpy'),
        tab=gui.CREDENTIALS_TAB)
    fixedName = gui.TextField(
        order=2,
        label=_('Username'),
        tooltip=_(
            'If not empty, this username will be always used as credential'),
        tab=gui.CREDENTIALS_TAB)
    fixedPassword = gui.PasswordField(
        order=3,
        label=_('Password'),
        tooltip=_(
            'If not empty, this password will be always used as credential'),
        tab=gui.CREDENTIALS_TAB)
    serverCertificate = gui.TextField(
        order=4,
        length=4096,
        multiline=4,
        label=_('Certificate'),
        tooltip=
        _('Server certificate (public), can be found on your ovirt engine, probably at /etc/pki/ovirt-engine/certs/ca.der (Use the contents of this file).'
          ),
        required=False)
    fullScreen = gui.CheckBoxField(
        order=5,
        label=_('Fullscreen Mode'),
        tooltip=_('If checked, viewer will be shown on fullscreen mode-'),
        tab=gui.ADVANCED_TAB)
    smartCardRedirect = gui.CheckBoxField(
        order=6,
        label=_('Smartcard Redirect'),
        tooltip=_(
            'If checked, SPICE protocol will allow smartcard redirection.'),
        defvalue=gui.FALSE,
        tab=gui.ADVANCED_TAB)
    usbShare = gui.CheckBoxField(
        order=7,
        label=_('Enable USB'),
        tooltip=_('If checked, USB redirection will be allowed.'),
        defvalue=gui.FALSE,
        tab=gui.ADVANCED_TAB)
    autoNewUsbShare = gui.CheckBoxField(
        order=8,
        label=_('New USB Auto Sharing'),
        tooltip=_('Auto-redirect USB devices when plugged in.'),
        defvalue=gui.FALSE,
        tab=gui.ADVANCED_TAB)

    def isAvailableFor(self, userService: 'models.UserService',
                       ip: str) -> bool:
        """
        Checks if the transport is available for the requested destination ip
        """
        ready = self.cache.get(ip)
        if ready is None:
            userServiceInstance: typing.Any = userService.getInstance(
            )  # Disable mypy checks on this
            con = userServiceInstance.getConsoleConnection()

            logger.debug('Connection data: %s', con)

            if con is None:
                return False

            port, secure_port = con['port'] or -1, con['secure_port'] or -1

            # test ANY of the ports
            port_to_test = port if port != -1 else secure_port
            if port_to_test == -1:
                self.cache.put(
                    'cachedMsg', 'Could not find the PORT for connection',
                    120)  # Write a message, that will be used from getCustom
                logger.info('SPICE didn\'t find has any port: %s', con)
                return False

            self.cache.put(
                'cachedMsg',
                'Could not reach server "{}" on port "{}" from broker (prob. causes are name resolution & firewall rules)'
                .format(con['address'], port_to_test), 120)

            if self.testServer(userService, con['address'],
                               port_to_test) is True:
                self.cache.put(ip, 'Y', READY_CACHE_TIMEOUT)
                ready = 'Y'

        return ready == 'Y'

    def getCustomAvailableErrorMsg(self, userService: 'models.UserService',
                                   ip: str) -> str:
        msg = self.cache.get('cachedMsg')
        if msg is None:
            return transports.Transport.getCustomAvailableErrorMsg(
                self, userService, ip)
        return msg

    def processedUser(self, userService: 'models.UserService',
                      user: '******') -> str:
        v = self.processUserPassword(userService, user, '')
        return v['username']

    def processUserPassword(self, userService: 'models.UserService',
                            user: '******',
                            password: str) -> typing.Dict[str, str]:
        username = user.getUsernameForAuth()

        if self.fixedName.value:
            username = self.fixedName.value

        if self.fixedPassword.value:
            password = self.fixedPassword.value

        if self.useEmptyCreds.isTrue():
            username, password = '', ''

        # Fix username/password acording to os manager
        username, password = userService.processUserPassword(
            username, password)

        return {
            'protocol': self.protocol,
            'username': username,
            'password': password
        }

    def getConnectionInfo(self,
                          userService: typing.Union['models.UserService',
                                                    'models.ServicePool'],
                          user: '******',
                          password: str) -> typing.Dict[str, str]:
        return self.processUserPassword(userService, user, password)

    def getScript(
        self, scriptNameTemplate: str, osName: str,
        params: typing.Dict[str, typing.Any]
    ) -> typing.Tuple[str, str, typing.Dict[str, typing.Any]]:
        # Reads script
        scriptNameTemplate = scriptNameTemplate.format(osName)
        with open(os.path.join(os.path.dirname(__file__),
                               scriptNameTemplate)) as f:
            script = f.read()
        # Reads signature
        with open(
                os.path.join(os.path.dirname(__file__),
                             scriptNameTemplate + '.signature')) as f:
            signature = f.read()
        return script, signature, params
Ejemplo n.º 24
0
class InternalDBAuth(Authenticator):
    typeName = _('Internal Database')
    typeType = 'InternalDBAuth'
    typeDescription = _('Internal dabasase authenticator. Doesn\'t use external sources')
    iconFile = 'auth.png'

    # If we need to enter the password for this user
    needsPassword = True

    # This is the only internal source
    isExternalSource = False

    differentForEachHost = gui.CheckBoxField(label=_('Different user for each host'), order=1, tooltip=_('If checked, each host will have a different user name'), defvalue="false", rdonly=True, tab=gui.ADVANCED_TAB)
    reverseDns = gui.CheckBoxField(label=_('Reverse DNS'), order=2, tooltip=_('If checked, the host will be reversed dns'), defvalue="false", rdonly=True, tab=gui.ADVANCED_TAB)
    acceptProxy = gui.CheckBoxField(label=_('Accept proxy'), order=3, tooltip=_('If checked, requests via proxy will get FORWARDED ip address (take care with this bein checked, can take internal IP addresses from internet)'), tab=gui.ADVANCED_TAB)

    def initialize(self, values):
        if values is None:
            return

    def getIp(self):
        ip = getRequest().ip_proxy if self.acceptProxy.isTrue() else getRequest().ip  # pylint: disable=maybe-no-member
        if self.reverseDns.isTrue():
            try:
                return str(dns.resolver.query(dns.reversename.from_address(ip), 'PTR')[0])
            except Exception:
                pass
        return ip

    def transformUsername(self, username):
        if self.differentForEachHost.isTrue():
            newUsername = self.getIp() + '-' + username  # pylint: disable=maybe-no-member
            # Duplicate basic user into username.
            auth = self.dbAuthenticator()
            # "Derived" users will belong to no group at all, because we will extract groups from "base" user
            # This way also, we protect from using forged "ip" + "username", because those will belong in fact to no group
            # and access will be denied
            try:
                usr = auth.users.get(name=username, state=State.ACTIVE)
                parent = usr.uuid
                usr.id = usr.uuid = None  # Empty "key" fields for replication
                if usr.real_name.strip() == '':
                    usr.real_name = usr.name
                usr.name = newUsername
                usr.parent = parent
                usr.save()
            except Exception:
                pass  # User already exists
            username = newUsername

        return username

    def authenticate(self, username, credentials, groupsManager):
        logger.debug('Username: {0}, Password: {1}'.format(username, credentials))
        auth = self.dbAuthenticator()
        try:
            try:
                usr = auth.users.get(name=username, state=State.ACTIVE)
            except Exception:
                return False

            if usr.parent is not None and usr.parent != '':  # Direct auth not allowed for "derived" users
                return False

            # Internal Db Auth has its own groups, and if it active it is valid
            if usr.password == cryptoManager().hash(credentials):
                #  hashlib.sha1(credentials.encode('utf-8')).hexdigest():
                groupsManager.validate([g.name for g in usr.groups.all()])
                return True
            return False
        except dbAuthenticator.DoesNotExist:  # @UndefinedVariable
            return False

    def createUser(self, usrData):
        pass

    @staticmethod
    def test(env, data):
        return [True, _("Internal structures seems ok")]

    def check(self):
        return _("All seems fine in the authenticator.")

    def __str__(self):
        return "Internal DB Authenticator Authenticator"
Ejemplo n.º 25
0
class OpenNebulaProvider(ServiceProvider):  # pylint: disable=too-many-public-methods
    # : What kind of services we offer, this are classes inherited from Service
    offers = [LiveService]
    # : Name to show the administrator. This string will be translated BEFORE
    # : sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    typeName = _('OpenNebula Platform Provider')
    # : Type used internally to identify this provider
    typeType = 'openNebulaPlatform'
    # : Description shown at administration interface for this provider
    typeDescription = _('OpenNebula platform service provider')
    # : Icon file used as icon for this provider. This string will be translated
    # : BEFORE sending it to administration interface, so don't forget to
    # : mark it as _ (using ugettext_noop)
    iconFile = 'provider.png'

    # now comes the form fields
    # There is always two fields that are requested to the admin, that are:
    # Service Name, that is a name that the admin uses to name this provider
    # Description, that is a short description that the admin gives to this provider
    # Now we are going to add a few fields that we need to use this provider
    # Remember that these are "dummy" fields, that in fact are not required
    # but used for sample purposes
    # If we don't indicate an order, the output order of fields will be
    # "random"
    host = gui.TextField(length=64,
                         label=_('Host'),
                         order=1,
                         tooltip=_('OpenNebula Host'),
                         required=True)
    port = gui.NumericField(
        length=5,
        label=_('Port'),
        defvalue='2633',
        order=2,
        tooltip=_('OpenNebula Port (default is 2633 for non ssl connection)'),
        required=True)
    ssl = gui.CheckBoxField(
        label=_('Use SSL'),
        order=3,
        tooltip=
        _('If checked, the connection will be forced to be ssl (will not work if server is not providing ssl)'
          ))
    username = gui.TextField(
        length=32,
        label=_('Username'),
        order=4,
        tooltip=_('User with valid privileges on OpenNebula'),
        required=True,
        defvalue='oneadmin')
    password = gui.PasswordField(
        lenth=32,
        label=_('Password'),
        order=5,
        tooltip=_('Password of the user of OpenNebula'),
        required=True)

    maxPreparingServices = gui.NumericField(
        length=3,
        label=_('Creation concurrency'),
        defvalue='10',
        minValue=1,
        maxValue=65536,
        order=50,
        tooltip=_('Maximum number of concurrently creating VMs'),
        required=True,
        tab=gui.ADVANCED_TAB)
    maxRemovingServices = gui.NumericField(
        length=3,
        label=_('Removal concurrency'),
        defvalue='5',
        minValue=1,
        maxValue=65536,
        order=51,
        tooltip=_('Maximum number of concurrently removing VMs'),
        required=True,
        tab=gui.ADVANCED_TAB)

    timeout = gui.NumericField(
        length=3,
        label=_('Timeout'),
        defvalue='10',
        order=90,
        tooltip=_('Timeout in seconds of connection to OpenNebula'),
        required=True,
        tab=gui.ADVANCED_TAB)

    # Own variables
    _api: typing.Optional[on.client.OpenNebulaClient] = None

    def initialize(self, values: 'Module.ValuesType') -> None:
        '''
        We will use the "autosave" feature for form fields
        '''

        # Just reset _api connection variable
        self._api = None

        if values:
            self.timeout.value = validators.validateTimeout(self.timeout.value)
            logger.debug('Endpoint: %s', self.endpoint)

    @property
    def endpoint(self) -> str:
        return 'http{}://{}:{}/RPC2'.format('s' if self.ssl.isTrue() else '',
                                            self.host.value, self.port.value)

    @property
    def api(self) -> on.client.OpenNebulaClient:
        if self._api is None:
            self._api = on.client.OpenNebulaClient(self.username.value,
                                                   self.password.value,
                                                   self.endpoint)

        return self._api

    def resetApi(self) -> None:
        self._api = None

    def sanitizeVmName(self, name: str) -> str:
        return on.sanitizeName(name)

    def testConnection(self) -> typing.List[typing.Any]:
        '''
        Test that conection to OpenNebula server is fine

        Returns

            True if all went fine, false if id didn't
        '''

        try:
            if self.api.version[0] < '4':
                return [
                    False,
                    'OpenNebula version is not supported (required version 4.1 or newer)'
                ]
        except Exception as e:
            return [False, '{}'.format(e)]

        return [True, _('Opennebula test connection passed')]

    def getDatastores(
            self,
            datastoreType: int = 0) -> typing.Iterable[on.types.StorageType]:
        yield from on.storage.enumerateDatastores(self.api, datastoreType)

    def getTemplates(
            self,
            force: bool = False) -> typing.Iterable[on.types.TemplateType]:
        yield from on.template.getTemplates(self.api, force)

    def makeTemplate(self, fromTemplateId: str, name, toDataStore: str) -> str:
        return on.template.create(self.api, fromTemplateId, name, toDataStore)

    def checkTemplatePublished(self, templateId: str) -> bool:
        return on.template.checkPublished(self.api, templateId)

    def removeTemplate(self, templateId: str) -> None:
        on.template.remove(self.api, templateId)

    def deployFromTemplate(self, name: str, templateId: str) -> str:
        return on.template.deployFrom(self.api, templateId, name)

    def getMachineState(self, machineId: str) -> on.types.VmState:
        '''
        Returns the state of the machine
        This method do not uses cache at all (it always tries to get machine state from OpenNebula server)

        Args:
            machineId: Id of the machine to get state

        Returns:
            one of the on.VmState Values
        '''
        return on.vm.getMachineState(self.api, machineId)

    def getMachineSubstate(self, machineId: str) -> int:
        '''
        Returns the  LCM_STATE of a machine (STATE must be ready or this will return -1)
        '''
        return on.vm.getMachineSubstate(self.api, machineId)

    def startMachine(self, machineId: str) -> None:
        '''
        Tries to start a machine. No check is done, it is simply requested to OpenNebula.

        This start also "resume" suspended/paused machines

        Args:
            machineId: Id of the machine

        Returns:
        '''
        on.vm.startMachine(self.api, machineId)

    def stopMachine(self, machineId: str) -> None:
        '''
        Tries to stop a machine. No check is done, it is simply requested to OpenNebula

        Args:
            machineId: Id of the machine

        Returns:
        '''
        on.vm.stopMachine(self.api, machineId)

    def suspendMachine(self, machineId: str) -> None:
        '''
        Tries to suspend a machine. No check is done, it is simply requested to OpenNebula

        Args:
            machineId: Id of the machine

        Returns:
        '''
        on.vm.suspendMachine(self.api, machineId)

    def shutdownMachine(self, machineId: str) -> None:
        '''
        Tries to shutdown "gracefully" a machine. No check is done, it is simply requested to OpenNebula

        Args:
            machineId: Id of the machine

        Returns:
        '''
        on.vm.shutdownMachine(self.api, machineId)

    def resetMachine(self, machineId: str) -> None:
        '''
        Resets a machine (hard-reboot)
        '''
        on.vm.resetMachine(self.api, machineId)

    def removeMachine(self, machineId: str) -> None:
        '''
        Tries to delete a machine. No check is done, it is simply requested to OpenNebula

        Args:
            machineId: Id of the machine

        Returns:
        '''
        on.vm.removeMachine(self.api, machineId)

    def getNetInfo(
            self,
            machineId: str,
            networkId: typing.Optional[str] = None) -> typing.Tuple[str, str]:
        '''
        Changes the mac address of first nic of the machine to the one specified
        '''
        return on.vm.getNetInfo(self.api, machineId, networkId)

    def getConsoleConnection(self,
                             machineId: str) -> typing.Dict[str, typing.Any]:
        display = on.vm.getDisplayConnection(self.api, machineId)

        if display is None:
            raise Exception('Invalid console connection on OpenNebula!!!')

        return {
            'type': display['type'],
            'address': display['host'],
            'port': display['port'],
            'secure_port': -1,
            'monitors': 1,
            'cert_subject': '',
            'ticket': {
                'value': display['passwd'],
                'expiry': ''
            }
        }

    def desktopLogin(self, machineId: str, username: str, password: str,
                     domain: str):
        '''
        Not provided by OpenNebula API right now
        '''
        return

    @staticmethod
    def test(env: 'Environment',
             data: 'Module.ValuesType') -> typing.List[typing.Any]:
        return OpenNebulaProvider(env, data).testConnection()
Ejemplo n.º 26
0
class HTML5RDPTransport(transports.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 = transports.protocols.RDP
    group = transports.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,
    )

    useGlyptodonTunnel = gui.CheckBoxField(
        label=_('Use Glyptodon Enterprise tunnel'),
        order=2,
        tooltip=
        _('If checked, UDS will use Glyptodon Enterprise Tunnel for HTML tunneling instead of UDS Tunnel'
          ),
        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,
    )
    withoutDomain = gui.CheckBoxField(
        label=_('Without Domain'),
        order=6,
        tooltip=
        _('If checked, the domain part will always be emptied (to connecto to xrdp for example is needed)'
          ),
        tab=gui.CREDENTIALS_TAB,
    )
    fixedDomain = gui.TextField(
        label=_('Domain'),
        order=7,
        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 remote session (if client browser supports it)'
          ),
        tab=gui.PARAMETERS_TAB,
        defvalue=gui.TRUE,
    )
    enableAudioInput = gui.CheckBoxField(
        label=_('Enable Microphone'),
        order=24,
        tooltip=
        _('If checked, the microphone will be redirected to remote session (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 remote session (if client browser supports it)'
          ),
        tab=gui.PARAMETERS_TAB,
    )
    enableFileSharing = gui.ChoiceField(
        label=_('File Sharing'),
        order=22,
        tooltip=_('File upload/download redirection policy'),
        defvalue='false',
        values=[
            {
                'id': 'false',
                'text': 'Disable file sharing'
            },
            {
                'id': 'down',
                'text': 'Allow download only'
            },
            {
                'id': 'up',
                'text': 'Allow upload only'
            },
            {
                'id': 'true',
                'text': 'Enable file sharing'
            },
        ],
        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('en-gb-qwerty', _('English (GB) keyboard')),
            gui.choiceItem('es-es-qwerty', _('Spanish keyboard')),
            gui.choiceItem('es-latam-qwerty', _('Latin American keyboard')),
            gui.choiceItem('de-de-qwertz', _('German keyboard (qwertz)')),
            gui.choiceItem('fr-fr-azerty', _('French keyboard (azerty)')),
            gui.choiceItem('fr-ch-qwertz',
                           _('Swiss French keyboard (qwertz)')),
            gui.choiceItem('de-ch-qwertz',
                           _('Swiss German keyboard (qwertz)')),
            gui.choiceItem('it-it-qwerty', _('Italian keyboard')),
            gui.choiceItem('sv-se-qwerty', _('Swedish keyboard')),
            gui.choiceItem('ja-jp-qwerty', _('Japanese keyboard')),
            gui.choiceItem('pt-br-qwerty', _('Brazilian 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(
                'nla-ext',
                _('NLA extended (Network Layer authentication. Requires VALID username&password, or connection will fail)'
                  ),
            ),
            gui.choiceItem('tls',
                           _('TLS (Transport Security Layer encryption)')),
        ],
        defvalue='any',
        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,
    )

    forceNewWindow = gui.ChoiceField(
        order=91,
        label=_('Force new HTML Window'),
        tooltip=_('Select windows behavior for new connections on HTML5'),
        required=True,
        values=[
            gui.choiceItem(
                gui.FALSE,
                _('Open every connection on the same window, but keeps UDS window.'
                  ),
            ),
            gui.choiceItem(
                gui.TRUE,
                _('Force every connection to be opened on a new window.')),
            gui.choiceItem(
                'overwrite',
                _('Override UDS window and replace it with the connection.'),
            ),
        ],
        defvalue=gui.FALSE,
        tab=gui.ADVANCED_TAB,
    )

    customGEPath = gui.TextField(
        label=_('Glyptodon Enterprise context path'),
        order=92,
        tooltip=
        _('Customized path for Glyptodon Enterprise tunnel. (Only valid for Glyptodon Enterprise Tunnel)'
          ),
        defvalue='/',
        length=128,
        required=False,
        tab=gui.ADVANCED_TAB,
    )

    def initialize(self, values: 'Module.ValuesType'):
        if not values:
            return
        # Strip spaces
        self.guacamoleServer.value = self.guacamoleServer.value.strip()
        if self.guacamoleServer.value[0:4] != 'http':
            raise transports.Transport.ValidationException(
                _('The server must be http or https'))
        if self.useEmptyCreds.isTrue() and self.security.value != 'rdp':
            raise transports.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: 'models.UserService',
                       ip: str) -> bool:
        """
        Checks if the transport is available for the requested destination ip
        Override this in yours transports
        """
        logger.debug('Checking availability for %s', ip)
        ready = self.cache.get(ip)
        if not ready:
            # Check again for readyness
            if self.testServer(userService, ip, '3389') is True:
                self.cache.put(ip, 'Y', READY_CACHE_TIMEOUT)
                return True
            self.cache.put(ip, 'N', READY_CACHE_TIMEOUT)
        return ready == 'Y'

    def processedUser(self, userService: 'models.UserService',
                      user: '******') -> str:
        v = self.processUserAndPassword(userService, user, '')
        return v['username']

    def processUserAndPassword(self, userService: 'models.UserService',
                               user: '******',
                               password: str) -> typing.Dict[str, str]:
        username: str = user.getUsernameForAuth()

        if self.fixedName.value != '':
            username = self.fixedName.value

        proc = username.split('@')
        domain = proc[1] if len(proc) > 1 else ''
        username = proc[0]

        if self.fixedPassword.value != '':
            password = self.fixedPassword.value

        azureAd = False
        if self.fixedDomain.value != '':
            if self.fixedDomain.value.lower() == 'azuread':
                azureAd = True
            else:
                domain = self.fixedDomain.value

        if self.useEmptyCreds.isTrue():
            username, password, domain = '', '', ''

        # If no domain to be transfered, set it to ''
        if self.withoutDomain.isTrue():
            domain = ''

        if '.' in domain:  # Dotter domain form
            username = username + '@' + domain
            domain = ''

        # If AzureAD, include it on username
        if azureAd:
            username = '******' + username

        # Fix username/password acording to os manager
        username, password = userService.processUserPassword(
            username, password)

        return {
            'protocol': self.protocol,
            'username': username,
            'password': password,
            'domain': domain,
        }

    def getLink(  # pylint: disable=too-many-locals
        self,
        userService: 'models.UserService',
        transport: 'models.Transport',
        ip: str,
        os: typing.Dict[str, str],
        user: '******',
        password: str,
        request: 'HttpRequest',
    ) -> str:
        credsInfo = self.processUserAndPassword(userService, user, password)
        username, password, domain = (
            credsInfo['username'],
            credsInfo['password'],
            credsInfo['domain'],
        )

        scrambler = cryptoManager().randomString(32)
        passwordCrypted = cryptoManager().symCrypt(password, scrambler)

        # Build params dict
        params = {
            'protocol': 'rdp',
            'hostname': ip,
            'username': username,
            'password': passwordCrypted,
            'resize-method': 'display-update',
            'ignore-cert': 'true',
            'security': self.security.value,
            'drive-path': '/share/{}'.format(user.uuid),
            'create-drive-path': 'true',
            'ticket-info': {
                'userService': userService.uuid,
                'user': userService.user.uuid,
            },
        }

        if False:  # Future imp
            sanitize = lambda x: re.sub("[^a-zA-Z0-9_-]", "_", x)
            params['recording-path'] = (
                '/share/recording/' + sanitize(user.manager.name) + '_' +
                sanitize(user.name) + '/' +
                getSqlDatetime().strftime('%Y%m%d-%H%M'))
            params['create-recording-path'] = 'true'

        if domain:
            params['domain'] = domain

        if self.enableFileSharing.value == 'true':
            params['enable-drive'] = 'true'
        elif self.enableFileSharing.value == 'down':
            params['enable-drive'] = 'true'
            params['disable-upload'] = 'true'
        elif self.enableFileSharing.value == 'up':
            params['enable-drive'] = 'true'
            params['disable-download'] = 'true'

        if self.serverLayout.value != '-':
            params['server-layout'] = self.serverLayout.value

        if not self.enableAudio.isTrue():
            params['disable-audio'] = 'true'
        elif self.enableAudioInput.isTrue():
            params['enable-audio-input'] = 'true'

        if self.enablePrinting.isTrue():
            params['enable-printing'] = 'true'
            params['printer-name'] = 'UDS-Printer'

        if self.wallpaper.isTrue():
            params['enable-wallpaper'] = 'true'

        if self.desktopComp.isTrue():
            params['enable-desktop-composition'] = 'true'

        if self.smooth.isTrue():
            params['enable-font-smoothing'] = 'true'

        logger.debug('RDP Params: %s', params)

        ticket = models.TicketStore.create(params,
                                           validity=self.ticketValidity.num())

        onw = ''
        if self.forceNewWindow.value == gui.TRUE:
            onw = '&o_n_w={}'
        elif self.forceNewWindow.value == 'overwrite':
            onw = '&o_s_w=yes'
        onw = onw.format(hash(transport.name))
        path = (self.customGEPath.value
                if self.useGlyptodonTunnel.isTrue() else '/guacamole')
        # Remove trailing /
        if path[-1] == '/':
            path = path[:-1]

        return str("{server}{path}/#/?data={ticket}.{scrambler}{onw}".format(
            server=self.guacamoleServer.value,
            path=path,
            ticket=ticket,
            scrambler=scrambler,
            onw=onw,
        ))