示例#1
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)
示例#2
0
class ServiceOne(Service):
    '''
    Basic service, the first part (variables) include the description of the service.
    
    Remember to fill all variables needed, but at least you must define:
        * typeName
        * typeType
        * typeDescription
        * iconFile (defaults to service.png)
        * publicationType, type of publication in case it needs publication. 
          If this is not provided, core will assume that the service do not 
          needs publishing.
        * deployedType, type of deployed user service. Do not forget this!!!
        
    The rest of them can be ommited, but its recommended that you fill all
    declarations shown in this sample (that in fact, are all)
    
    This description informs the core what this service really provides,
    and how this is done. Look at description of class variables for more
    information.
    
    '''
    #: 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 Service One')
    #: Type used internally to identify this provider
    typeType = 'SampleService1'
    #: Description shown at administration interface for this provider
    typeDescription = _('Sample (and dummy) service ONE')
    #: 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 = 'service.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 = False
    #: Tooltip shown to user when this item is pointed at admin interface, none
    #: because we don't use it
    cacheTooltip = _('None')
    #: If we need to generate a "Level 2" cache for this service (i.e., L1
    #: could be running machines and L2 suspended machines)
    usesCache_L2 = False
    #: Tooltip shown to user when this item is pointed at admin interface, None
    #: also because we don't use it
    cacheTooltip_L2 = _('None')

    #: 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 = None
    #: Types of deploys (services in cache and/or assigned to users)
    deployedType = SampleUserDeploymentOne

    # Now the form part, this service will have only two "dummy" fields
    # If we don't indicate an order, the output order of fields will be
    # "random"

    colour = gui.ChoiceField(
        order=1,
        label=_('Colour'),
        tooltip=_('Colour of the field'),
        # In this case, the choice can have none value selected by default
        required=True,
        values=[
            gui.choiceItem('red', 'Red'),
            gui.choiceItem('green', 'Green'),
            gui.choiceItem('blue', 'Blue'),
            gui.choiceItem('nonsense', 'Blagenta')
        ],
        defvalue='1'  # Default value is the ID of the choicefield
    )

    passw = gui.PasswordField(
        order=2,
        label=_('Password'),
        tooltip=_('Password for testing purposes'),
        required=True,
        defvalue='1234'  #: Default password are nonsense?? :-)
    )

    baseName = gui.TextField(
        order=3,
        label=_('Services names'),
        tooltip=_('Base name for this user services'),
        # In this case, the choice can have none value selected by default
        required=True,
        defvalue=''  # Default value is the ID of the choicefield
    )

    def initialize(self, values):
        '''
        We check here form values to see if they are valid.
        
        Note that we check them throught FROM variables, that already has been
        initialized by __init__ method of base class, before invoking this.
        '''

        # We don't need to check anything, bat because this is a sample, we do
        # As in provider, we receive values only at new Service creation,
        # so we only need to validate params if values is not None
        if values is not None:
            if self.colour.value == 'nonsense':
                raise Service.ValidationException(
                    'The selected colour is invalid!!!')

    # Services itself are non testeable right now, so we don't even have
    # to provide one!!!

    # Congratulations!!!, the needed part of your first simple service 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 getColour(self):
        '''
        Simply returns colour, for deployed user services.
        
        Remember that choiceField.value returns the id part of the ChoiceItem
        '''
        return self.colour.value

    def getPassw(self):
        '''
        Simply returns passwd, for deloyed user services
        '''
        return self.passw.value

    def getBaseName(self):
        '''
        '''
        return self.baseName.value
示例#3
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()
示例#4
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))]
示例#5
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
示例#6
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)
示例#7
0
class ProxmoxProvider(services.ServiceProvider):  # pylint: disable=too-many-public-methods
    offers = [ProxmoxLinkedService]
    typeName = _('Proxmox Platform Provider')
    typeType = 'ProxmoxPlatform'
    typeDescription = _('Proxmox platform service provider')
    iconFile = 'provider.png'

    host = gui.TextField(length=64,
                         label=_('Host'),
                         order=1,
                         tooltip=_('Proxmox Server IP or Hostname'),
                         required=True)
    port = gui.NumericField(lengh=5,
                            label=_('Port'),
                            order=2,
                            tooltip=_('Proxmox API port (default is 8006)'),
                            required=True,
                            defvalue='8006')

    username = gui.TextField(
        length=32,
        label=_('Username'),
        order=3,
        tooltip=
        _('User with valid privileges on Proxmox, (use "user@authenticator" form)'
          ),
        required=True,
        defvalue='root@pam')
    password = gui.PasswordField(lenth=32,
                                 label=_('Password'),
                                 order=4,
                                 tooltip=_('Password of the user of Proxmox'),
                                 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='20',
        order=90,
        tooltip=_('Timeout in seconds of connection to Proxmox'),
        required=True,
        tab=gui.ADVANCED_TAB)

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

    def __getApi(self) -> client.ProxmoxClient:
        """
        Returns the connection API object
        """
        if self._api is None:
            self._api = client.ProxmoxClient(self.host.value, self.port.num(),
                                             self.username.value,
                                             self.password.value,
                                             self.timeout.num(), False,
                                             self.cache)

        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

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

    def testConnection(self) -> bool:
        """
        Test that conection to Proxmox server is fine

        Returns

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

        return self.__getApi().test()

    def listMachines(self) -> typing.List[client.types.VMInfo]:
        return self.__getApi().listVms()

    def getMachineInfo(self, vmId: int) -> client.types.VMInfo:
        return self.__getApi().getVmInfo(vmId, force=True)

    def getMachineConfiguration(self,
                                vmId: int) -> client.types.VMConfiguration:
        return self.__getApi().getVmConfiguration(vmId, force=True)

    def getStorageInfo(self, storageId: str,
                       node: str) -> client.types.StorageInfo:
        return self.__getApi().getStorage(storageId, node)

    def listStorages(
            self, node: typing.Optional[str]
    ) -> typing.List[client.types.StorageInfo]:
        return self.__getApi().listStorages(node=node, content='images')

    def listPools(self) -> typing.List[client.types.PoolInfo]:
        return self.__getApi().listPools()

    def makeTemplate(self, vmId: int) -> None:
        return self.__getApi().convertToTemplate(vmId)

    def cloneMachine(self,
                     vmId: int,
                     name: str,
                     description: typing.Optional[str],
                     linkedClone: bool,
                     toNode: typing.Optional[str] = None,
                     toStorage: typing.Optional[str] = None,
                     toPool: typing.Optional[str] = None,
                     memory: int = 0) -> client.types.VmCreationResult:
        return self.__getApi().cloneVm(vmId, name, description, linkedClone,
                                       toNode, toStorage, toPool, memory)

    def startMachine(self, vmId: int) -> client.types.UPID:
        return self.__getApi().startVm(vmId)

    def stopMachine(self, vmId: int) -> client.types.UPID:
        return self.__getApi().stopVm(vmId)

    def suspendMachine(self, vmId: int) -> client.types.UPID:
        return self.__getApi().suspendVm(vmId)

    def removeMachine(self, vmId: int) -> client.types.UPID:
        return self.__getApi().deleteVm(vmId)

    def getTaskInfo(self, node: str, upid: str) -> client.types.TaskStatus:
        return self.__getApi().getTask(node, upid)

    def enableHA(self,
                 vmId: int,
                 started: bool = False,
                 group: typing.Optional[str] = None) -> None:
        self.__getApi().enableVmHA(vmId, started, group)

    def disableHA(self, vmId: int) -> None:
        self.__getApi().disableVmHA(vmId)

    def listHaGroups(self) -> typing.List[str]:
        return self.__getApi().listHAGroups()

    def getConsoleConnection(
        self, machineId: str
    ) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
        # TODO: maybe proxmox also supports "spice"? for future release...
        return None

    @staticmethod
    def test(env: 'Environment',
             data: 'Module.ValuesType') -> typing.List[typing.Any]:
        """
        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..

        """
        # 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..')]
        prox = ProxmoxProvider(env, data)
        if prox.testConnection() is True:
            return [True, 'Test successfully passed']

        return [False, _("Connection failed. Check connection params")]
示例#8
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)
示例#9
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()
示例#10
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();'
            )
        )
示例#11
0
class WinRandomPassManager(WindowsOsManager):
    typeName = _('Windows Random Password OS Manager')
    typeType = 'WinRandomPasswordManager'
    typeDescription = _(
        'Os Manager to control windows machines, with user password set randomly.'
    )
    iconFile = 'wosmanager.png'

    # Apart form data from windows os manager, we need also domain and credentials
    userAccount = gui.TextField(length=64,
                                label=_('Account'),
                                order=2,
                                tooltip=_('User account to change password'),
                                required=True)
    password = gui.PasswordField(
        length=64,
        label=_('Password'),
        order=3,
        tooltip=_('Current (template) password of the user account'),
        required=True)

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

    def __init__(self, environment: 'Environment',
                 values: 'Module.ValuesType'):
        super().__init__(environment, values)
        if values:
            if values['userAccount'] == '':
                raise osmanagers.OSManager.ValidationException(
                    _('Must provide an user account!!!'))
            if values['password'] == '':
                raise osmanagers.OSManager.ValidationException(
                    _('Must provide a password for the account!!!'))
            self._userAccount = values['userAccount']
            self._password = values['password']
        else:
            self._userAccount = ''
            self._password = ""

    def processUserPassword(self, userService: 'UserService', username: str,
                            password: str) -> typing.Tuple[str, str]:
        if username == self._userAccount:
            password = userService.recoverValue('winOsRandomPass')

        return WindowsOsManager.processUserPassword(self, userService,
                                                    username, password)

    def genPassword(self, userService: 'UserService'):
        randomPass = userService.recoverValue('winOsRandomPass')
        if not randomPass:
            # Generates a password that conforms to complexity
            rnd = random.SystemRandom()
            base = ''.join(
                rnd.choice(v)
                for v in (string.ascii_lowercase, string.ascii_uppercase,
                          string.digits)) + rnd.choice('.+-')
            randomPass = ''.join(
                rnd.choice(string.ascii_letters + string.digits)
                for _ in range(12))
            pos = rnd.randrange(0, len(randomPass))
            randomPass = randomPass[:pos] + base + randomPass[pos:]
            userService.storeValue('winOsRandomPass', randomPass)
            log.doLog(userService, log.INFO,
                      "Password set to \"{}\"".format(randomPass),
                      log.OSMANAGER)
        return randomPass

    def actorData(
            self, userService: 'UserService'
    ) -> typing.MutableMapping[str, typing.Any]:
        return {
            'action': 'rename',
            'name': userService.getName(),
            'username': self._userAccount,
            'password': self._password,
            'new_password': self.genPassword(userService)
        }

    def infoVal(self, userService: 'UserService') -> str:
        return 'rename:{0}\t{1}\t{2}\t{3}'.format(
            self.getName(userService), self._userAccount, self._password,
            self.genPassword(userService))

    def infoValue(self, userService: 'UserService') -> str:
        return 'rename\r{0}\t{1}\t{2}\t{3}'.format(
            self.getName(userService), self._userAccount, self._password,
            self.genPassword(userService))

    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([
            'v1', self._userAccount,
            cryptoManager().encrypt(self._password), base
        ]).encode('utf8')

    def unmarshal(self, data: bytes) -> None:
        values = data.decode('utf8').split('\t')
        if values[0] == 'v1':
            self._userAccount = values[1]
            self._password = cryptoManager().decrypt(values[2])
            super().unmarshal(
                typing.cast(bytes, encoders.decode(values[3], 'hex')))

    def valuesDict(self) -> gui.ValuesDictType:
        dic = super().valuesDict()
        dic['userAccount'] = self._userAccount
        dic['password'] = self._password
        return dic
示例#12
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
        )
示例#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))
示例#14
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
示例#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 = _('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()
示例#16
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)
示例#17
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)
示例#18
0
class OVirtProvider(services.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 = [OVirtLinkedService]
    # : 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 = _('oVirt/RHEV Platform Provider')
    # : Type used internally to identify this provider
    typeType = 'oVirtPlatform'
    # : Description shown at administration interface for this provider
    typeDescription = _('oVirt 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"
    ovirtVersion = gui.ChoiceField(
        order=1,
        label=_('oVirt Version'),
        tooltip=_('oVirt Server Version'),
        # In this case, the choice can have none value selected by default
        required=True,
        rdonly=False,
        values=[
            gui.choiceItem('4', '4.x'),
        ],
        defvalue='4'  # Default value is the ID of the choicefield
    )

    host = gui.TextField(length=64,
                         label=_('Host'),
                         order=2,
                         tooltip=_('oVirt Server IP or Hostname'),
                         required=True)
    username = gui.TextField(
        length=32,
        label=_('Username'),
        order=3,
        tooltip=_(
            'User with valid privileges on oVirt, (use "user@domain" form)'),
        required=True,
        defvalue='admin@internal')
    password = gui.PasswordField(lenth=32,
                                 label=_('Password'),
                                 order=4,
                                 tooltip=_('Password of the user of oVirt'),
                                 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 oVirt'),
        required=True,
        tab=gui.ADVANCED_TAB)
    macsRange = gui.TextField(
        length=36,
        label=_('Macs range'),
        defvalue='52:54:00:00:00:00-52:54:00:FF:FF:FF',
        order=91,
        rdonly=True,
        tooltip=_('Range of valid macs for UDS managed machines'),
        required=True,
        tab=gui.ADVANCED_TAB)

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

    # oVirt 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) -> client.Client:
        """
        Returns the connection API object for oVirt (using ovirtsdk)
        """
        if self._api is None:
            self._api = client.Client(self.host.value, self.username.value,
                                      self.password.value, self.timeout.value,
                                      self.cache)

        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

        if values is not None:
            self.macsRange.value = validators.validateMacRange(
                self.macsRange.value)
            self.timeout.value = validators.validateTimeout(self.timeout.value)
            logger.debug(self.host.value)

    def testConnection(self) -> bool:
        """
        Test that conection to oVirt server is fine

        Returns

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

        return self.__getApi().test()

    def testValidVersion(self):
        """
        Checks that this version of ovirt if "fully functional" and does not needs "patchs'
        """
        return list(self.__getApi().isFullyFunctionalVersion())

    def getMachines(
        self,
        force: bool = False
    ) -> typing.List[typing.MutableMapping[str, typing.Any]]:
        """
        Obtains the list of machines inside oVirt.
        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'
        """

        return self.__getApi().getVms(force)

    def getClusters(
        self,
        force: bool = False
    ) -> typing.List[typing.MutableMapping[str, typing.Any]]:
        """
        Obtains the list of clusters inside oVirt.

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

        Returns
            Filters out clusters not attached to any datacenter
            An array of dictionaries, containing:
                'name'
                'id'
                'datacenter_id'
        """

        return self.__getApi().getClusters(force)

    def getClusterInfo(
            self,
            clusterId: str,
            force: bool = False) -> typing.MutableMapping[str, typing.Any]:
        """
        Obtains the cluster info

        Args:
            datacenterId: Id of the cluster 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
                'name'
                'id'
                'datacenter_id'
        """
        return self.__getApi().getClusterInfo(clusterId, force)

    def getDatacenterInfo(
            self,
            datacenterId: str,
            force: bool = False) -> typing.MutableMapping[str, typing.Any]:
        """
        Obtains the datacenter info

        Args:
            datacenterId: Id of the datacenter 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
                'name'
                'id'
                'storage_type' -> ('isisi', 'nfs', ....)
                'storage_format' -> ('v1', v2')
                'description'
                'storage' -> array of dictionaries, with:
                   '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

        """
        return self.__getApi().getDatacenterInfo(datacenterId, force)

    def getStorageInfo(
            self,
            storageId: str,
            force: bool = 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().getStorageInfo(storageId, force)

    def makeTemplate(self, name: str, comments: str, machineId: str,
                     clusterId: str, storageId: str, displayType: str) -> str:
        """
        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 oVirt admin interface only)

        Returns
            Raises an exception if operation could not be acomplished, or returns the id of the template being created.
        """
        return self.__getApi().makeTemplate(name, comments, machineId,
                                            clusterId, storageId, displayType)

    def getTemplateState(self, templateId: str) -> str:
        """
        Returns current template state.

        Returned values could be:
            ok
            locked
            removed

        (don't know if ovirt returns something more right now, will test what happens when template can't be published)
        """
        return self.__getApi().getTemplateState(templateId)

    def getMachineState(self, machineId: str) -> str:
        """
        Returns the state of the machine
        This method do not uses cache at all (it always tries to get machine state from oVirt 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)

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

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

    def deployFromTemplate(self, name: str, comments: str, templateId: str,
                           clusterId: str, displayType: str, usbType: str,
                           memoryMB: int, guaranteedMB: int) -> 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 oVirt 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().deployFromTemplate(name, comments, templateId,
                                                  clusterId, displayType,
                                                  usbType, memoryMB,
                                                  guaranteedMB)

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

        This start also "resume" suspended/paused machines

        Args:
            machineId: Id of the machine

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

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

        Args:
            machineId: Id of the machine

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

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

        Args:
            machineId: Id of the machine

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

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

        Args:
            machineId: Id of the machine

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

    def updateMachineMac(self, machineId: str, macAddres: str) -> None:
        """
        Changes the mac address of first nic of the machine to the one specified
        """
        self.__getApi().updateMachineMac(machineId, macAddres)

    def fixUsb(self, machineId: str) -> None:
        self.__getApi().fixUsb(machineId)

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

    def getConsoleConnection(
        self, machineId: str
    ) -> typing.Optional[typing.MutableMapping[str, typing.Any]]:
        return self.__getApi().getConsoleConnection(machineId)

    @staticmethod
    def test(env: 'Environment',
             data: 'Module.ValuesType') -> typing.List[typing.Any]:
        """
        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..

        """
        # 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..')]
        ov = OVirtProvider(env, data)
        if ov.testConnection() is True:
            return ov.testValidVersion()

        return [False, _("Connection failed. Check connection params")]
示例#19
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
示例#20
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()
示例#21
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,
        ))