def test_map(self): validator = validators.Map(a=1, b=2, c=3) self.assertEqual(validator.__call__('a'), 1) self.assertEqual(validator.__call__('b'), 2) self.assertEqual(validator.__call__('c'), 3) self.assertRaises(ValueError, validator.__call__, 'd') self.assertEqual(validator.__call__(None), None)
class TestSearchCommand(SearchCommand): boolean = Option( doc=''' **Syntax:** **boolean=***<value>* **Description:** A boolean value''', validate=validators.Boolean()) required_boolean = Option( doc=''' **Syntax:** **boolean=***<value>* **Description:** A boolean value''', require=True, validate=validators.Boolean()) aliased_required_boolean = Option( doc=''' **Syntax:** **boolean=***<value>* **Description:** A boolean value''', name='foo', require=True, validate=validators.Boolean()) code = Option( doc=''' **Syntax:** **code=***<value>* **Description:** A Python expression, if mode == "eval", or statement, if mode == "exec"''', validate=validators.Code()) required_code = Option( doc=''' **Syntax:** **code=***<value>* **Description:** A Python expression, if mode == "eval", or statement, if mode == "exec"''', require=True, validate=validators.Code()) duration = Option( doc=''' **Syntax:** **duration=***<value>* **Description:** A length of time''', validate=validators.Duration()) required_duration = Option( doc=''' **Syntax:** **duration=***<value>* **Description:** A length of time''', require=True, validate=validators.Duration()) fieldname = Option( doc=''' **Syntax:** **fieldname=***<value>* **Description:** Name of a field''', validate=validators.Fieldname()) required_fieldname = Option( doc=''' **Syntax:** **fieldname=***<value>* **Description:** Name of a field''', require=True, validate=validators.Fieldname()) file = Option( doc=''' **Syntax:** **file=***<value>* **Description:** Name of a file''', validate=validators.File()) required_file = Option( doc=''' **Syntax:** **file=***<value>* **Description:** Name of a file''', require=True, validate=validators.File()) integer = Option( doc=''' **Syntax:** **integer=***<value>* **Description:** An integer value''', validate=validators.Integer()) required_integer = Option( doc=''' **Syntax:** **integer=***<value>* **Description:** An integer value''', require=True, validate=validators.Integer()) map = Option( doc=''' **Syntax:** **map=***<value>* **Description:** A mapping from one value to another''', validate=validators.Map(foo=1, bar=2, test=3)) required_map = Option( doc=''' **Syntax:** **map=***<value>* **Description:** A mapping from one value to another''', require=True, validate=validators.Map(foo=1, bar=2, test=3)) match = Option( doc=''' **Syntax:** **match=***<value>* **Description:** A value that matches a regular expression pattern''', validate=validators.Match('social security number', r'\d{3}-\d{2}-\d{4}')) required_match = Option( doc=''' **Syntax:** **required_match=***<value>* **Description:** A value that matches a regular expression pattern''', require=True, validate=validators.Match('social security number', r'\d{3}-\d{2}-\d{4}')) optionname = Option( doc=''' **Syntax:** **optionname=***<value>* **Description:** The name of an option (used internally)''', validate=validators.OptionName()) required_optionname = Option( doc=''' **Syntax:** **optionname=***<value>* **Description:** The name of an option (used internally)''', require=True, validate=validators.OptionName()) regularexpression = Option( doc=''' **Syntax:** **regularexpression=***<value>* **Description:** Regular expression pattern to match''', validate=validators.RegularExpression()) required_regularexpression = Option( doc=''' **Syntax:** **regularexpression=***<value>* **Description:** Regular expression pattern to match''', require=True, validate=validators.RegularExpression()) set = Option( doc=''' **Syntax:** **set=***<value>* **Description:** A member of a set''', validate=validators.Set('foo', 'bar', 'test')) required_set = Option( doc=''' **Syntax:** **set=***<value>* **Description:** A member of a set''', require=True, validate=validators.Set('foo', 'bar', 'test')) class ConfigurationSettings(SearchCommand.ConfigurationSettings): @classmethod def fix_up(cls, command_class): pass
class LdapFilterCommand(StreamingCommand): """ Filters and augments events with information from Active Directory. This command follows a search or similar command in the pipeline so that you can feed it events: .. code-block:: text eventtype=msad-user-logons | ldapfilter domain=$dest_nt_domain$ search="(objectClass=$src_user$)" attrs="telephoneNumber,displayName" """ # region Command options search = Option( doc=''' Specifies an RFC 2254 compliant search string. ''', require=True) domain = Option( doc=''' Specifies the Active Directory domain to search. ''', default='default') attrs = Option( doc=''' Specifies a comma separated list of attributes to return as fields. **Default:** '*', specifying that all attributes should be returned as fields. ''', default=[ldap3.ALL_ATTRIBUTES], validate=validators.List()) basedn = Option( doc=''' Specifies the starting point for the search. Default: The value of `basedn` as specified in the configuration stanza for `domain`. ''') scope = Option( doc=''' Specifies the scope of the search to be one of `base`, `one`, or `sub`. **Default:** sub. ''', default='sub', validate=validators.Map( base=ldap3.SEARCH_SCOPE_BASE_OBJECT, one=ldap3.SEARCH_SCOPE_SINGLE_LEVEL, sub=ldap3.SEARCH_SCOPE_WHOLE_SUBTREE )) decode = Option( doc=''' True, if Active Directory formatting rules should be applied to attribute types. **Default:** The value of decode as specified in the configuration stanza for domain. ''', default=True, validate=validators.Boolean()) limit = Option( doc=''' Specifies an upper bound on the number of matching entries returned by the search. **Default:** 0, specifying that there is no upper bound on the number of entries returned by the search. ''', default=0, validate=validators.Integer(minimum=0)) debug = Option( doc=''' True, if the logging_level should be set to DEBUG; otherwise False. **Default:** The current value of logging_level. ''', default=False, validate=validators.Boolean()) # endregion # region Command implementation def stream(self, records): """ :param records: An iterable stream of events from the command pipeline. :return: `None`. """ configuration = app.Configuration(self, is_expanded=True) expanded_domain = app.ExpandedString(self.domain) expanded_search_filter = app.ExpandedString(self.search, converter=app.escape_assertion_value) try: with configuration.open_connection_pool(self.attrs) as connection_pool: for record in records: domain = expanded_domain.get_value(record) if domain is None: continue search_filter = expanded_search_filter.get_value(record) if len(search_filter) == 0: continue connection = connection_pool.select(domain) if not connection: self.logger.warning('search="%s": domain="%s" is not configured', search_filter, domain) continue search_base = app.ExpandedString(self.basedn).get_value(record) # must be instantiated here entry_generator = connection.extend.standard.paged_search( search_base=search_base, search_filter=search_filter, search_scope=self.scope, attributes=connection_pool.attributes, paged_size=configuration.paged_size) for entry in entry_generator: attributes = app.get_attributes(self, entry) if not attributes: continue for name in connection_pool.attributes: record[name] = attributes.get(name, '') yield record pass except ldap3.LDAPException as error: self.error_exit(error, app.get_ldap_error_message(error, configuration)) return
class LdapSearchCommand(GeneratingCommand): """ Retrieves results from the specified search in a configured domain and generates events. This command must be placed at the beginning of a search pipeline: .. code-block:: text | ldapsearch domain=splunk.com search="(objectCategory=User)" attrs="distinguishedName" """ search = Option(doc=''' Specifies an RFC 2254 compliant search string. ''', require=True) attrs = Option( doc= ''' Specifies a comma separated list of attributes to be returned as fields. **Default:** '*', specifying that all attributes should be returned as fields. ''', default=[ldap3.ALL_ATTRIBUTES], validate=validators.List()) basedn = Option(doc=''' Specifies the starting point for the search. Default: The value of basedn as specified in the configuration stanza for domain. ''') domain = Option( doc= ''' Specifies the LDAP or Active Directory domain directory to search. ''', default='default') scope = Option( doc=''' Specifies the scope of the search to be one of base, one, or sub. **Default:** sub. ''', default='sub', validate=validators.Map(base=ldap3.SEARCH_SCOPE_BASE_OBJECT, one=ldap3.SEARCH_SCOPE_SINGLE_LEVEL, sub=ldap3.SEARCH_SCOPE_WHOLE_SUBTREE)) debug = Option( doc= ''' True, if the logging_level should be set to DEBUG; otherwise False. **Default:** The current value of logging_level. ''', default=False, validate=validators.Boolean()) decode = Option( doc= ''' True, if Active Directory formatting rules should be applied to attribute types. **Default:** The value of decode as specified in the configuration stanza for domain. ''', default=True, validate=validators.Boolean()) limit = Option( doc= ''' Specifies an upper bound on the number of matching entries returned by the search. **Default:** 0, specifying that there is no upper bound on the number of entries returned by the search. ''', default=0, validate=validators.Integer(minimum=0)) def generate(self): """ :return: `None`. """ configuration = app.Configuration(self) try: with ldap3.Connection( configuration.server, read_only=True, raise_exceptions=True, user=configuration.credentials.username, password=configuration.credentials.password) as connection: attribute_names = app.get_normalized_attribute_names( self.attrs, connection, configuration) entry_generator = connection.extend.standard.paged_search( search_base=self.basedn, search_filter=self.search, search_scope=self.scope, attributes=self.attrs, paged_size=configuration.paged_size) encoder = JSONEncoder(ensure_ascii=False, separators=(',', ':')) time_stamp = time() serial_number = 0 for entry in entry_generator: attributes = app.get_attributes(self, entry) if attributes: dn = entry['dn'] yield LdapSearchCommand._record( serial_number, time_stamp, connection.server.host, dn, attributes, attribute_names, encoder) serial_number += 1 if self.limit and serial_number == self.limit: break pass pass except ldap3.LDAPException as error: self.error_exit(error, app.get_ldap_error_message(error, configuration)) return @staticmethod def _record(serial_number, time_stamp, host, dn, attributes, attribute_names, encoder): # Base-64 encode binary values (they're stored as str values--byte strings--not unicode values) for name, value in attributes.iteritems(): if isinstance(value, str): attributes[name] = b64encode(value) elif isinstance(value, list): for i in range(len(value)): if isinstance(value[i], str): value[i] = b64encode(value[i]) raw = encoder.encode(attributes) # Formulate record if serial_number > 0: attributes['_serial'] = serial_number attributes['_time'] = time_stamp attributes['_raw'] = raw attributes['host'] = host attributes['dn'] = dn return attributes record = OrderedDict( chain((('_serial', serial_number), ('_time', time_stamp), ('_raw', raw), ('host', host), ('dn', dn)), imap(lambda name: (name, attributes.get(name, '')), attribute_names))) return record