コード例 #1
0
ファイル: sccm.py プロジェクト: itam-junky/oomnitza-connector
    def pick_odbc_driver(driver_candidate):
        """
        Pick suitable ODBC driver to communicate with SCCM DB
        :type driver_candidate: str or None
        :rtype: str
        """
        drivers = pyodbc.drivers()
        if not driver_candidate:

            # if driver is empty, choose the best one from allowed
            new_drivers_regexp = r'^ODBC Driver .* for SQL Server$'
            supported_new_drivers = sorted([_ for _ in drivers if re.match(new_drivers_regexp, _)])
            if supported_new_drivers:
                driver_candidate = supported_new_drivers[-1]  # choose the last one

            # if we have no new ODBC drivers, use the legacy one built-in for Windows
            elif 'SQL Server' in drivers:
                driver_candidate = 'SQL Server'

        else:
            # given `driver` string is not empty, validate it against allowed drivers
            if driver_candidate not in drivers:
                raise ConfigError('Given driver "%s" is not supported. The available drivers are: %s' %
                                  (driver_candidate, ', '.join(['"%s"' % _ for _ in drivers])))

        # final check to be sure driver not empty
        if not driver_candidate:
            raise ConfigError('Your environment has no supported drivers to use. '
                              'The drivers can be downloaded from here: \n'
                              'https://docs.microsoft.com/en-us/sql/connect/odbc/download-odbc-driver-for-sql-server')
        return driver_candidate
コード例 #2
0
    def __init__(self, service_name, backend_name):
        self._service_name = service_name

        keyring_backend = keyring.get_keyring()

        if backend_name == StrongboxBackend.KEYRING:
            Strategy = DefaultStrategy
        elif backend_name == StrongboxBackend.VAULT:
            Strategy = VaultStrategy
        elif backend_name == StrongboxBackend.CYBERARK:
            Strategy = CyberArkStrategy
        else:
            raise ConfigError(
                "Invalid strongbox backend: '{}', e.g. only 'keyring' "
                "'vault' and 'cyberark' values are allowed".format(backend_name)
            )

        LOG.info(
            "The {backend_name} backend is used as the secret storage "
            "for {service_name}".format(
                backend_name=backend_name, service_name=service_name
            )
        )
        strategy = Strategy(keyring_backend, service_name)
        self._keyring_backend = strategy.get_keyring_backend()
コード例 #3
0
 def _raise_error(self, service_name, secret_name):
     raise ConfigError(
         "Unable to find secret in keyring, ensure secret "
         "key/value pair has been inserted before starting "
         "connector:\n\t"
         "python strongbox.py --connector={} --key={} --value=".format(
             service_name, secret_name))
コード例 #4
0
    def authenticate(self):
        if not any((
                self.settings['api_token'],  # given token
                self.settings.get('user_pem_file'),  # given .pem certificate
                self.settings['username']
                and self.settings['password']  # given pass + username
        )):
            raise ConfigError(
                "Oomnitza section needs either: api_token or username & password or PEM certificate."
            )

        try:
            if self.settings['api_token']:
                self.get("{url}/api/v2/mappings?name=AuthTest".format(
                    **self.settings))
                return

            auth_url = "{url}/api/request_token".format(**self.settings)
            response = self.post(
                auth_url,
                {
                    'login': self.settings['username'],
                    'password': self.settings['password']
                },
                post_as_json=False,
            )
            self.settings['api_token'] = response.json()["token"]
        except RequestException as exp:
            raise AuthenticationError(str(exp))
コード例 #5
0
    def oomnitza_authorization_loader(self):
        """
        There can be three options here
        - there is API token ID to be used in case of cloud connector setup
                {"token_id": 1234567890}

        - there is API token value as is to be used in on-premise setup
                "qwertyuio1234567890"

        - nothing set, in this case use the same token as it defined for the [oomnitza] section basic setup

        """
        value = self.settings['oomnitza_authorization']
        if isinstance(value, str) and value:
            # on-premise setup, the token explicitly set, nothing to do
            return

        elif not value:
            # on-premise setup, the token not set, pick the same as from the self.OomnitzaConnector
            self.settings[
                'oomnitza_authorization'] = self.OomnitzaConnector.settings[
                    'api_token']

        elif isinstance(value, dict) and value.get("token_id"):
            # cloud based setup
            self.settings['oomnitza_authorization'] = {
                "token_id": value['token_id']
            }

        else:
            raise ConfigError(
                f'Managed connector #{self.ConnectorID}: Oomnitza authorization format is invalid. Exiting'
            )
コード例 #6
0
 def _decide_api_version(self):
     if self.settings.get('client_id') and self.settings.get('client_secret'):
         self.api_version = Connector.Version.slash_one
     elif self.settings.get('api_token'):
         LOG.warning('Deprecated API used! Please switch to the new OneLogin API')
         self.api_version = Connector.Version.v1_to_v3
     else:
         raise ConfigError('OneLogin connector configured improperly')
コード例 #7
0
 def _init_url_template(self):
     if self.api_version == Connector.Version.slash_one:
         self.url_template = "%s?after_cursor={0}" % self.settings['url']
         self.test_conn_url = self.url_template.format('')
     elif self.api_version == Connector.Version.v1_to_v3:
         self.url_template = "%s?include_custom_attributes=true&page={0}" % self.settings['url']
         self.test_conn_url = self.url_template.format(1)
     else:
         raise ConfigError('OneLogin connector url template cannot be initialized with invalid version')
コード例 #8
0
 def get_local_inputs(self) -> dict:
     if isinstance(self.settings.get("local_inputs"), str):
         inputs_from_local = json.loads(self.settings["local_inputs"])
     elif isinstance(self.settings.get("local_inputs"), dict):
         inputs_from_local = self.settings["local_inputs"]
     else:
         raise ConfigError(
             f'Managed connector #{self.ConnectorID}: local inputs have invalid format. Exiting'
         )
     return inputs_from_local
コード例 #9
0
    def saas_authorization_loader(self):
        """
        There can be two options here:
        - there is credential_id string to be used in case of cloud connector setup
                {"credential_id": "qwertyuio1234567890", ...}

        - there is a JSON containing the ready-to-use headers and/or params in case of on-premise connector setup
                {"headers": {"Authorization": "Bearer qwertyuio1234567890"}}

        """
        value = self.settings['saas_authorization']
        if isinstance(value, str):
            value = json.loads(value)

        if not isinstance(value, dict):
            raise ConfigError(
                f'Managed connector #{self.ConnectorID}: Information for the authorization in SaaS must be presented in form of dictionary JSON'
            )

        if isinstance(value.get('credential_id'), str):
            # cloud-based setup with the credential ID
            self.settings['saas_authorization'] = {
                'credential_id': value['credential_id']
            }

        elif value.get('type') == 'session':
            # special session-based configuration where the credentials can be generated dynamically locally, so we should not expect the ready headers or params here
            self.settings['saas_authorization'] = {}
            self.session_auth_behavior = value['behavior']

        elif (isinstance(value.get('headers', {}), dict)
              and value.get('headers')) or (isinstance(value.get(
                  'params', {}), dict) and value.get('params')):
            # on-premise setup with ready-to-use headers and params
            self.settings['saas_authorization'] = {
                'headers': value.get('headers', {}),
                'params': value.get('params', {})
            }

        else:
            raise ConfigError(
                f'Managed connector #{self.ConnectorID}: SaaS authorization format is invalid. Exiting'
            )
コード例 #10
0
    def get_sync_type_from_settings(self):
        sync_type = self.settings.get('sync_type', None)
        if not sync_type:
            LOG.warning(
                "No sync_type configured or set as empty. Defaulting to '%s'."
                % COMPUTERS)
            sync_type = COMPUTERS

        if sync_type not in SyncTypes:
            raise ConfigError("Invalid sync_type: %r", self.sync_type)

        return sync_type
コード例 #11
0
    def __init__(self, section, settings):
        self.folder_path = settings.pop('folder_path', None)
        if not os.path.isdir(self.folder_path):
            try:
                os.mkdir(self.folder_path)
            except FileExistsError:
                raise ConfigError('The specified folder path already occupied by the file with the same name')

        self.overwrite_reports = settings.pop('overwrite_reports', False) in TrueValues
        self.data_sources = settings.pop('data_sources', [])
        super().__init__(section, settings)

        # force the save_data to be false just because of the nature of the connector - it cannot dumps the content of the file to the JSON
        self.settings["__save_data__"] = False
コード例 #12
0
 def get_device_groups_to_process(self):
     device_groups = []
     if self.settings['device_groups'].strip():
         try:
             device_groups = list(
                 map(
                     int,
                     list(
                         map(str.strip,
                             self.settings['device_groups'].split(',')))))
         except:
             raise ConfigError(
                 "Device groups have to be set as the integer IDs of groups separated with a comma"
             )
     return device_groups
コード例 #13
0
 def render_to_string(self, template: Any) -> str:
     """
     Render the value to the string
     """
     try:
         return self.jinja_string_env.from_string(
             str(template)).render(**self.rendering_context)
     except UndefinedError:
         logger.debug(
             f'Failed to render to string. Template: {str(template)}. Context: {self.rendering_context}'
         )
         return ''
     except TemplateSyntaxError as e:
         raise ConfigError(
             f'Invalid configuration for the managed connector: {e.message}'
         )
コード例 #14
0
ファイル: casper.py プロジェクト: vitalk/oomnitza-connector
    def __init__(self, section, settings):
        super(Connector, self).__init__(section, settings)

        # ensure URL does not end with a trailing '/'
        if self.settings['url'].endswith('/'):
            LOG.warning("Casper URL should not end with a '/'.")
            self.settings['url'] = self.settings['url'][:-1]

        self.url_template = "%s/{0}" % self.settings['url']
        sync_type = self.settings.get('sync_type', None)
        if sync_type is None:
            LOG.warning("No sync_type configured. Defaulting to 'computers'.")
            sync_type = "computers"
        elif not sync_type:
            LOG.warning(
                "Empty sync_type configured. Defaulting to 'computers'.")
            sync_type = "computers"

        self.sync_type = SyncTypes.get(sync_type, None)
        if self.sync_type is None:
            raise ConfigError("Invalid sync_type: %r", self.sync_type)

        if sync_type == "computers":
            # print self.field_mappings.keys()
            if 'APPLICATIONS' not in self.field_mappings:
                self.field_mappings['APPLICATIONS'] = {
                    "source": "software.applications"
                }
        else:
            self.MappingName = Connector.MappingName + ".MDM"

        self.group_name = self.settings.get("group_name", "")
        if self.group_name:
            LOG.info("Loading assets from group: %r", self.group_name)
            self.ids_url = self.url_template.format(
                self.sync_type['group_ids_path'].format(
                    name=urllib.quote(self.group_name)))
            self.ids_converter = lambda data: data['{}_group'.format(
                self.sync_type['data'])][self.sync_type['array']]
        else:
            self.ids_url = self.url_template.format(
                self.sync_type['all_ids_path'])
            self.ids_converter = lambda data: data[self.sync_type['array']]

        self.details_url = self.get_details_url(sync_type)
コード例 #15
0
    def get_device_types_to_process(self):
        try:
            _device_types = list(
                map(
                    str.lower,
                    list(
                        map(str.strip,
                            self.settings['device_types'].split(',')))))
        except:
            raise ConfigError(
                "Invalid string values are used for the device types")
        device_types = [
            device_type for device_type in _device_types
            if device_type in (COMPUTERS, MOBILE_DEVICES)
        ]
        if not device_types:
            device_types = [COMPUTERS, MOBILE_DEVICES]

        return device_types
コード例 #16
0
    def render_to_native(self, template: Any) -> Any:
        """
        Render the value to its native type based on the inputs
        """
        try:
            val = self.jinja_native_env.from_string(
                str(template)).render(**self.rendering_context)
            if val == Undefined():
                raise UndefinedError

            if isinstance(val, _RawValue):
                return val.render()

            return val
        except UndefinedError:
            logger.debug(
                f'Failed to render to native. Template: {str(template)}. Context: {self.rendering_context}'
            )
            return None
        except TemplateSyntaxError as e:
            raise ConfigError(
                f'Invalid configuration for the managed connector: {e.message}'
            )
コード例 #17
0
    def authenticate(self):
        if not self.settings['api_token']:
            if not self.settings['username'] or not self.settings['password']:
                raise ConfigError(
                    "Oomnitza section needs either: api_token or username & password."
                )

        try:
            if self.settings['api_token']:
                self.get("{url}/api/v2/mappings?name=AuthTest".format(
                    **self.settings))
                return

            auth_url = "{url}/api/request_token".format(**self.settings)
            response = self.post(
                auth_url,
                {
                    'login': self.settings['username'],
                    'password': self.settings['password']
                },
                post_as_json=False,
            )
            self.settings['api_token'] = response.json()["token"]
        except RequestException as exp:
            if isinstance(exp.message, basestring):
                raise AuthenticationError("{} returned {}.".format(
                    self.settings['url'], exp.message))
            if len(exp.message.args) > 2 and isinstance(
                    exp.message.args[1], gaierror):
                msg = "Unable to connect to {} ({}).".format(
                    self.settings['url'], exp.message.args[1].errno)
                if exp.message.args[1].errno == 8:
                    msg = "Unable to get address for {}.".format(
                        self.settings['url'])
                raise AuthenticationError(msg)
            raise AuthenticationError(str(exp))
コード例 #18
0
    def authenticate(self):
        # ldap.set_option(ldap.OPT_DEBUG_LEVEL, 1)
        ldap.set_option(ldap.OPT_REFERRALS, 0)
        ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 30)

        # the default LDAP protocol version - if not recognized - is v3
        if self.settings['protocol_version'] == '2':
            ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION2)
        else:
            if self.settings['protocol_version'] != '3':
                LOG.warning("Unrecognized Protocol Version '%s', setting to '3'.", self.settings['protocol_version'])
                self.settings['protocol_version'] = '3'
            ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3)

        try:
            parsed_url = ldapurl.LDAPUrl(self.settings['url'])
        except ValueError:
            raise AuthenticationError("Invalid url to LDAP service. "
                                      "Check config examples at https://github.com/Oomnitza/oomnitza-connector")
        self.ldap_connection = ldap.initialize(parsed_url.unparse())

        cacert_file = self.settings.get('cacert_file', '')
        if cacert_file:
            cacert_file = os.path.abspath(cacert_file)
            if not os.path.isfile(cacert_file):
                raise ConfigError("%s is not a valid file!" % cacert_file)
            LOG.info("Setting CACert File to: %r.", cacert_file)
            ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, cacert_file)
        cacert_dir = self.settings.get('cacert_dir', '')
        if cacert_dir:
            cacert_dir = os.path.abspath(cacert_dir)
            if not os.path.isdir(cacert_dir):
                raise ConfigError("%s is not a valid directory!" % cacert_dir)
            LOG.info("Setting CACert Dir to: %r.", cacert_dir)
            ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, cacert_dir)

        # check for tls
        # if self.settings['enable_tls'] in self.TrueValues and self.settings['protocol_version'] == '3':
        if self.settings.get('verify_ssl', True) in TrueValues:
            ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
        else:
            LOG.warning("verify_ssl = '%s' so SSL certificate validation has been disabled.", self.settings.get('verify_ssl', True))
            ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)

        try:
            if self.settings['username'].lower() == 'anonymous':
                self.ldap_connection.simple_bind_s()
            else:
                password = self.settings['password']
                if not password:
                    LOG.warning("No password set for LDAP. Connecting without password.")
                    password = u""

                self.ldap_connection.simple_bind_s(self.settings['username'], password)
        except ldap.INVALID_CREDENTIALS:
            LOG.exception("Error calling simple_bind_s()")
            raise AuthenticationError("Cannot connect to the LDAP server with given credentials. "
                                      "Check the 'username', 'password' and 'dn' options "
                                      "in the config file in the '[ldap]' section.")
        except ldap.UNWILLING_TO_PERFORM as exp:
            LOG.exception("Error calling simple_bind_s()")
            raise AuthenticationError("Cannot connect to the LDAP server with given credentials: " + exp.args[0]['info'])
コード例 #19
0
def json_validator(value):
    try:
        return json.loads(value)
    except ValueError:
        raise ConfigError('setting is incorrect json expected but %r found' %
                          value)