3131import urllib
3232import io
3333import sys
34+ import Cookie
3435
3536from datetime import datetime
3637from functools import wraps
@@ -67,6 +68,45 @@ def new_f(*args, **kwargs):
6768 return new_f
6869
6970
71+ def _parse_cookies (cookie_str , dictionary ):
72+ """Tries to parse any key-value pairs of cookies in a string,
73+ then updates the the dictionary with any key-value pairs found.
74+
75+ **Example**::
76+ dictionary = {}
77+ _parse_cookies('my=value', dictionary)
78+ # Now the following is True
79+ dictionary['my'] == 'value'
80+
81+ :param cookie_str: A string containing "key=value" pairs from an HTTP "Set-Cookie" header.
82+ :type cookie_str: ``str``
83+ :param dictionary: A dictionary to update with any found key-value pairs.
84+ :type dictionary: ``dict``
85+ """
86+ parsed_cookie = Cookie .SimpleCookie (cookie_str )
87+ for cookie in parsed_cookie .values ():
88+ dictionary [cookie .key ] = cookie .coded_value
89+
90+
91+ def _make_cookie_header (cookies ):
92+ """
93+ Takes a list of 2-tuples of key-value pairs of
94+ cookies, and returns a valid HTTP ``Cookie``
95+ header.
96+
97+ **Example**::
98+
99+ header = _make_cookie_header([("key", "value"), ("key_2", "value_2")])
100+ # Now the following is True
101+ header == "key=value; key_2=value_2"
102+
103+ :param cookies: A list of 2-tuples of cookie key-value pairs.
104+ :type cookies: ``list`` of 2-tuples
105+ :return: ``str` An HTTP header cookie string.
106+ :rtype: ``str``
107+ """
108+ return "; " .join ("%s=%s" % (key , value ) for key , value in cookies )
109+
70110# Singleton values to eschew None
71111class _NoAuthenticationToken (object ):
72112 """The value stored in a :class:`Context` or :class:`splunklib.client.Service`
@@ -224,7 +264,8 @@ def f():
224264 """
225265 @wraps (request_fun )
226266 def wrapper (self , * args , ** kwargs ):
227- if self .token is _NoAuthenticationToken :
267+ if self .token is _NoAuthenticationToken and \
268+ not self .has_cookies ():
228269 # Not yet logged in.
229270 if self .autologin and self .username and self .password :
230271 # This will throw an uncaught
@@ -390,6 +431,10 @@ class Context(object):
390431 :param app: The app context of the namespace (optional, the default is "None").
391432 :type app: ``string``
392433 :param token: A session token. When provided, you don't need to call :meth:`login`.
434+ :type token: ``string``
435+ :param cookie: A session cookie. When provided, you don't need to call :meth:`login`.
436+ This parameter is only supported for Splunk 6.2+.
437+ :type cookie: ``string``
393438 :param username: The Splunk account username, which is used to
394439 authenticate the Splunk instance.
395440 :type username: ``string``
@@ -405,8 +450,10 @@ class Context(object):
405450 c.login()
406451 # Or equivalently
407452 c = binding.connect(username="boris", password="natasha")
408- # Of if you already have a session token
453+ # Or if you already have a session token
409454 c = binding.Context(token="atg232342aa34324a")
455+ # Or if you already have a valid cookie
456+ c = binding.Context(cookie="splunkd_8089=...")
410457 """
411458 def __init__ (self , handler = None , ** kwargs ):
412459 self .http = HttpLib (handler )
@@ -422,18 +469,41 @@ def __init__(self, handler=None, **kwargs):
422469 self .password = kwargs .get ("password" , "" )
423470 self .autologin = kwargs .get ("autologin" , False )
424471
472+ # Store any cookies in the self.http._cookies dict
473+ if kwargs .has_key ("cookie" ) and kwargs ['cookie' ] not in [None , _NoAuthenticationToken ]:
474+ _parse_cookies (kwargs ["cookie" ], self .http ._cookies )
475+
476+ def get_cookies (self ):
477+ """Gets the dictionary of cookies from the ``HttpLib`` member of this instance.
478+
479+ :return: Dictionary of cookies stored on the ``self.http``.
480+ :rtype: ``dict``
481+ """
482+ return self .http ._cookies
483+
484+ def has_cookies (self ):
485+ """Returns true if the ``HttpLib` member of this instance has at least
486+ one cookie stored.
487+
488+ :return: ``True`` if there is at least one cookie, else ``False``
489+ :rtype: ``bool``
490+ """
491+ return len (self .get_cookies ()) > 0
492+
425493 # Shared per-context request headers
426494 @property
427495 def _auth_headers (self ):
428496 """Headers required to authenticate a request.
429497
430- Assumes your ``Context`` already has a authentication token,
431- either provided explicitly or obtained by logging into the
432- Splunk instance.
498+ Assumes your ``Context`` already has a authentication token or
499+ cookie, either provided explicitly or obtained by logging
500+ into the Splunk instance.
433501
434502 :returns: A list of 2-tuples containing key and value
435503 """
436- if self .token is _NoAuthenticationToken :
504+ if self .has_cookies ():
505+ return [("Cookie" , _make_cookie_header (self .get_cookies ().items ()))]
506+ elif self .token is _NoAuthenticationToken :
437507 return []
438508 else :
439509 # Ensure the token is properly formatted
@@ -749,17 +819,29 @@ def login(self):
749819 c = binding.Context(...).login()
750820 # Then issue requests...
751821 """
822+
823+ if self .has_cookies () and \
824+ (not self .username and not self .password ):
825+ # If we were passed session cookie(s), but no username or
826+ # password, then login is a nop, since we're automatically
827+ # logged in.
828+ return
829+
752830 if self .token is not _NoAuthenticationToken and \
753831 (not self .username and not self .password ):
754832 # If we were passed a session token, but no username or
755833 # password, then login is a nop, since we're automatically
756834 # logged in.
757835 return
836+
837+ # Only try to get a token and updated cookie if username & password are specified
758838 try :
759839 response = self .http .post (
760840 self .authority + self ._abspath ("/services/auth/login" ),
761841 username = self .username ,
762- password = self .password )
842+ password = self .password ,
843+ cookie = "1" ) # In Splunk 6.2+, passing "cookie=1" will return the "set-cookie" header
844+
763845 body = response .body .read ()
764846 session = XML (body ).findtext ("./sessionKey" )
765847 self .token = "Splunk %s" % session
@@ -771,8 +853,9 @@ def login(self):
771853 raise
772854
773855 def logout (self ):
774- """Forgets the current session token."""
856+ """Forgets the current session token, and cookies ."""
775857 self .token = _NoAuthenticationToken
858+ self .http ._cookies = {}
776859 return self
777860
778861 def _abspath (self , path_segment ,
@@ -859,6 +942,9 @@ def connect(**kwargs):
859942 :param token: The current session token (optional). Session tokens can be
860943 shared across multiple service instances.
861944 :type token: ``string``
945+ :param cookie: A session cookie. When provided, you don't need to call :meth:`login`.
946+ This parameter is only supported for Splunk 6.2+.
947+ :type cookie: ``string``
862948 :param username: The Splunk account username, which is used to
863949 authenticate the Splunk instance.
864950 :type username: ``string``
@@ -1003,6 +1089,7 @@ class HttpLib(object):
10031089 """
10041090 def __init__ (self , custom_handler = None ):
10051091 self .handler = handler () if custom_handler is None else custom_handler
1092+ self ._cookies = {}
10061093
10071094 def delete (self , url , headers = None , ** kwargs ):
10081095 """Sends a DELETE request to a URL.
@@ -1112,6 +1199,18 @@ def request(self, url, message, **kwargs):
11121199 response = record (response )
11131200 if 400 <= response .status :
11141201 raise HTTPError (response )
1202+
1203+ # Update the cookie with any HTTP request
1204+ # Initially, assume list of 2-tuples
1205+ key_value_tuples = response .headers
1206+ # If response.headers is a dict, get the key-value pairs as 2-tuples
1207+ # this is the case when using urllib2
1208+ if isinstance (response .headers , dict ):
1209+ key_value_tuples = response .headers .items ()
1210+ for key , value in key_value_tuples :
1211+ if key .lower () == "set-cookie" :
1212+ _parse_cookies (value , self ._cookies )
1213+
11151214 return response
11161215
11171216
0 commit comments