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
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()
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))
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))
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' )
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')
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')
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
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' )
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
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
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
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}' )
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)
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
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}' )
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))
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'])
def json_validator(value): try: return json.loads(value) except ValueError: raise ConfigError('setting is incorrect json expected but %r found' % value)