102102PATH_USERS = "authentication/users/"
103103PATH_RECEIVERS_STREAM = "receivers/stream"
104104PATH_RECEIVERS_SIMPLE = "receivers/simple"
105+ PATH_STORAGE_PASSWORDS = "storage/passwords"
105106
106107XNAMEF_ATOM = "{http://www.w3.org/2005/Atom}%s"
107108XNAME_ENTRY = XNAMEF_ATOM % "entry"
108109XNAME_CONTENT = XNAMEF_ATOM % "content"
109110
110111MATCH_ENTRY_CONTENT = "%s/%s/*" % (XNAME_ENTRY , XNAME_CONTENT )
111112
113+
112114class IllegalOperationException (Exception ):
113115 """Thrown when an operation is not possible on the Splunk instance that a
114116 :class:`Service` object is connected to."""
115117 pass
116118
119+
117120class IncomparableException (Exception ):
118121 """Thrown when trying to compare objects (using ``==``, ``<``, ``>``, and
119122 so on) of a type that doesn't support it."""
120123 pass
121124
125+
122126class AmbiguousReferenceException (ValueError ):
123127 """Thrown when the name used to fetch an entity matches more than one entity."""
124128 pass
125129
130+
126131class InvalidNameException (Exception ):
127132 """Thrown when the specified name contains characters that are not allowed
128133 in Splunk entity names."""
129134 pass
130135
136+
131137class NoSuchCapability (Exception ):
132138 """Thrown when the capability that has been referred to doesn't exist."""
133139 pass
134140
141+
135142class OperationError (Exception ):
136143 """Raised for a failed operation, such as a time out."""
137144 pass
138145
146+
139147class NotSupportedError (Exception ):
140148 """Raised for operations that are not supported on a given object."""
141149 pass
142150
151+
143152def _trailing (template , * targets ):
144153 """Substring of *template* following all *targets*.
145154
@@ -168,6 +177,7 @@ def _trailing(template, *targets):
168177 s = s [n + len (t ):]
169178 return s
170179
180+
171181# Filter the given state content record according to the given arg list.
172182def _filter_content (content , * args ):
173183 if len (args ) > 0 :
@@ -180,10 +190,12 @@ def _path(base, name):
180190 if not base .endswith ('/' ): base = base + '/'
181191 return base + name
182192
193+
183194# Load an atom record from the body of the given response
184195def _load_atom (response , match = None ):
185196 return data .load (response .body .read (), match )
186197
198+
187199# Load an array of atom entries from the body of the given response
188200def _load_atom_entries (response ):
189201 r = _load_atom (response )
@@ -203,10 +215,12 @@ def _load_atom_entries(response):
203215 if entries is None : return None
204216 return entries if isinstance (entries , list ) else [entries ]
205217
218+
206219# Load the sid from the body of the given response
207220def _load_sid (response ):
208221 return _load_atom (response ).response .sid
209222
223+
210224# Parse the given atom entry record into a generic entity state record
211225def _parse_atom_entry (entry ):
212226 title = entry .get ('title' , None )
@@ -233,6 +247,7 @@ def _parse_atom_entry(entry):
233247 'content' : content
234248 })
235249
250+
236251# Parse the metadata fields out of the given atom entry content record
237252def _parse_atom_metadata (content ):
238253 # Hoist access metadata
@@ -247,6 +262,7 @@ def _parse_atom_metadata(content):
247262
248263 return record ({'access' : access , 'fields' : fields })
249264
265+
250266# kwargs: scheme, host, port, app, owner, username, password
251267def connect (** kwargs ):
252268 """This function connects and logs in to a Splunk instance.
@@ -455,6 +471,14 @@ def modular_input_kinds(self):
455471 else :
456472 raise IllegalOperationException ("Modular inputs are not supported before Splunk version 5." )
457473
474+ @property
475+ def storage_passwords (self ):
476+ """Returns the collection of the modular input kinds on this Splunk instance.
477+
478+ :return: A :class:`ReadOnlyCollection` of :class:`ModularInputKind` entities.
479+ """
480+ return StoragePasswords (self )
481+
458482 # kwargs: enable_lookups, reload_macros, parse_only, output_mode
459483 def parse (self , query , ** kwargs ):
460484 """Parses a search query and returns a semantic map of the search.
@@ -736,11 +760,8 @@ def post(self, path_segment="", owner=None, app=None, sharing=None, **query):
736760 if path_segment .startswith ('/' ):
737761 path = path_segment
738762 else :
739- path = self .service ._abspath (self .path + path_segment , owner = owner ,
740- app = app , sharing = sharing )
741- return self .service .post (path ,
742- owner = owner , app = app , sharing = sharing ,
743- ** query )
763+ path = self .service ._abspath (self .path + path_segment , owner = owner , app = app , sharing = sharing )
764+ return self .service .post (path , owner = owner , app = app , sharing = sharing , ** query )
744765
745766
746767# kwargs: path, app, owner, sharing, state
@@ -819,7 +840,8 @@ def __init__(self, service, path, **kwargs):
819840 Endpoint .__init__ (self , service , path )
820841 self ._state = None
821842 if not kwargs .get ('skip_refresh' , False ):
822- self .refresh (kwargs .get ('state' , None )) # "Prefresh"
843+ self .refresh (kwargs .get ('state' , None )) # "Prefresh"
844+ return
823845
824846 def __contains__ (self , item ):
825847 try :
@@ -1273,6 +1295,7 @@ def _load_list(self, response):
12731295 self ._entity_path (state ),
12741296 state = state )
12751297 entities .append (entity )
1298+
12761299 return entities
12771300
12781301 def itemmeta (self ):
@@ -1475,7 +1498,7 @@ def create(self, name, **params):
14751498 new_app = applications.create("my_fake_app")
14761499 """
14771500 if not isinstance (name , basestring ):
1478- raise InvalidNameException ("%s is not a valid name for an entity." % name )
1501+ raise InvalidNameException ("%s is not a valid name for an entity." % name )
14791502 if 'namespace' in params :
14801503 namespace = params .pop ('namespace' )
14811504 params ['owner' ] = namespace .owner
@@ -1701,6 +1724,102 @@ def __len__(self):
17011724 if not x .startswith ('eai' ) and x != 'disabled' ])
17021725
17031726
1727+ class StoragePassword (Entity ):
1728+ """This class contains a storage password.
1729+ """
1730+ def __init__ (self , service , path , ** kwargs ):
1731+ state = kwargs .get ('state' , None )
1732+ kwargs ['skip_refresh' ] = kwargs .get ('skip_refresh' , state is not None )
1733+ super (StoragePassword , self ).__init__ (service , path , ** kwargs )
1734+ self ._state = state
1735+
1736+ @property
1737+ def clear_password (self ):
1738+ return self .content .get ('clear_password' )
1739+
1740+ @property
1741+ def encrypted_password (self ):
1742+ return self .content .get ('encr_password' )
1743+
1744+ @property
1745+ def realm (self ):
1746+ return self .content .get ('realm' )
1747+
1748+ @property
1749+ def username (self ):
1750+ return self .content .get ('username' )
1751+
1752+
1753+ class StoragePasswords (Collection ):
1754+ """This class provides access to the storage passwords from this Splunk
1755+ instance. Retrieve this collection using :meth:`Service.storage_passwords`.
1756+ """
1757+ def __init__ (self , service ):
1758+ if service .namespace .owner == '-' or service .namespace .app == '-' :
1759+ raise ValueError ("StoragePasswords cannot have wildcards in namespace." )
1760+ super (StoragePasswords , self ).__init__ (service , PATH_STORAGE_PASSWORDS , item = StoragePassword )
1761+
1762+ def create (self , password , username , realm = None ):
1763+ """ Creates a storage password.
1764+
1765+ A `StoragePassword` can be identified by <username>, or by <realm>:<username> if the
1766+ optional realm parameter is also provided.
1767+
1768+ :param password: The password for the credentials - this is the only part of the credentials that will be stored securely.
1769+ :type name: ``string``
1770+ :param username: The username for the credentials.
1771+ :type name: ``string``
1772+ :param realm: The credential realm. (optional)
1773+ :type name: ``string``
1774+
1775+ :return: The :class:`StoragePassword` object created.
1776+ """
1777+ if not isinstance (username , basestring ):
1778+ raise ValueError ("Invalid name: %s" % repr (username ))
1779+
1780+ if realm is None :
1781+ response = self .post (password = password , name = username )
1782+ else :
1783+ response = self .post (password = password , realm = realm , name = username )
1784+
1785+ if response .status != 201 :
1786+ raise ValueError ("Unexpected status code %s returned from creating a stanza" % response .status )
1787+
1788+ entries = _load_atom_entries (response )
1789+ state = _parse_atom_entry (entries [0 ])
1790+ storage_password = StoragePassword (self .service , self ._entity_path (state ), state = state , skip_refresh = True )
1791+
1792+ return storage_password
1793+
1794+ def delete (self , username , realm = None ):
1795+ """Delete a storage password by username and/or realm.
1796+
1797+ The identifier can be passed in through the username parameter as
1798+ <username> or <realm>:<username>, but the preferred way is by
1799+ passing in the username and realm parameters.
1800+
1801+ :param username: The username for the credentials, or <realm>:<username> if the realm parameter is omitted.
1802+ :type name: ``string``
1803+ :param realm: The credential realm. (optional)
1804+ :type name: ``string``
1805+ :return: The `StoragePassword` collection.
1806+ :rtype: ``self``
1807+ """
1808+ if realm is None :
1809+ # This case makes the username optional, so
1810+ # the full name can be passed in as realm.
1811+ # Assume it's already encoded.
1812+ name = username
1813+ else :
1814+ # Encode each component separately
1815+ name = UrlEncoded (realm , encode_slash = True ) + ":" + UrlEncoded (username , encode_slash = True )
1816+
1817+ # Append the : expected at the end of the name
1818+ if name [- 1 ] is not ":" :
1819+ name = name + ":"
1820+ return Collection .delete (self , name )
1821+
1822+
17041823class AlertGroup (Entity ):
17051824 """This class represents a group of fired alerts for a saved search. Access
17061825 it using the :meth:`alerts` property."""
0 commit comments