6363LOG = logging .getLogger ("shotgun_api3" )
6464LOG .setLevel (logging .WARN )
6565
66+
6667SG_TIMEZONE = SgTimezone ()
6768
6869
@@ -92,6 +93,10 @@ class Fault(ShotgunError):
9293 """Exception when server side exception detected."""
9394 pass
9495
96+ class AuthenticationFault (Fault ):
97+ """Exception when the server side reports an error related to authentication"""
98+ pass
99+
95100# ----------------------------------------------------------------------------
96101# API
97102
@@ -209,6 +214,15 @@ def __init__(self):
209214 self .scheme = None
210215 self .server = None
211216 self .api_path = None
217+ # The raw_http_proxy reflects the exact string passed in
218+ # to the Shotgun constructor. This can be useful if you
219+ # need to construct a Shotgun API instance based on
220+ # another Shotgun API instance.
221+ self .raw_http_proxy = None
222+ # if a proxy server is being used, the proxy_handler
223+ # below will contain a urllib2.ProxyHandler instance
224+ # which can be used whenever a request needs to be made.
225+ self .proxy_handler = None
212226 self .proxy_server = None
213227 self .proxy_port = 8080
214228 self .proxy_user = None
@@ -217,6 +231,7 @@ def __init__(self):
217231 self .authorization = None
218232 self .no_ssl_validation = False
219233
234+
220235class Shotgun (object ):
221236 """Shotgun Client Connection"""
222237
@@ -237,10 +252,11 @@ def __init__(self,
237252 http_proxy = None ,
238253 ensure_ascii = True ,
239254 connect = True ,
240- ca_certs = None ,
255+ ca_certs = None ,
241256 login = None ,
242257 password = None ,
243- sudo_as_login = None ):
258+ sudo_as_login = None ,
259+ session_token = None ):
244260 """Initialises a new instance of the Shotgun client.
245261
246262 :param base_url: http or https url to the shotgun server.
@@ -263,9 +279,9 @@ def __init__(self,
263279 form [username:pass@]proxy.com[:8080]
264280
265281 :param connect: If True, connect to the server. Only used for testing.
266-
267- :param ca_certs: The path to the SSL certificate file. Useful for users
268- who would like to package their application into an executable.
282+
283+ :param ca_certs: The path to the SSL certificate file. Useful for users
284+ who would like to package their application into an executable.
269285
270286 :param login: The login to use to authenticate to the server. If login
271287 is provided, then password must be as well and neither script_name nor
@@ -279,9 +295,21 @@ def __init__(self,
279295 be applied to all actions and who will be logged as the user performing
280296 all actions. Note that logged events will have an additional extra meta-data parameter
281297 'sudo_actual_user' indicating the script or user that actually authenticated.
298+
299+ :param session_token: The session token to use to authenticate to the server. This
300+ can be used as an alternative to authenticating with a script user or regular user.
301+ You retrieve the session token by running the get_session_token() method.
282302 """
283303
284304 # verify authentication arguments
305+ if session_token is not None :
306+ if script_name is not None or api_key is not None :
307+ raise ValueError ("cannot provide both session_token "
308+ "and script_name/api_key" )
309+ if login is not None or password is not None :
310+ raise ValueError ("cannot provide both session_token "
311+ "and login/password" )
312+
285313 if login is not None or password is not None :
286314 if script_name is not None or api_key is not None :
287315 raise ValueError ("cannot provide both login/password "
@@ -298,19 +326,20 @@ def __init__(self,
298326 raise ValueError ("script_name provided without api_key" )
299327
300328 # Can't use 'all' with python 2.4
301- if len ([x for x in [script_name , api_key , login , password ] if x ]) == 0 :
329+ if len ([x for x in [session_token , script_name , api_key , login , password ] if x ]) == 0 :
302330 if connect :
303- raise ValueError ("must provide either login/password "
304- "or script_name/api_key" )
331+ raise ValueError ("must provide login/password, session_token or script_name/api_key" )
305332
306333 self .config = _Config ()
307334 self .config .api_key = api_key
308335 self .config .script_name = script_name
309336 self .config .user_login = login
310337 self .config .user_password = password
338+ self .config .session_token = session_token
311339 self .config .sudo_as_login = sudo_as_login
312340 self .config .convert_datetimes_to_utc = convert_datetimes_to_utc
313341 self .config .no_ssl_validation = NO_SSL_VALIDATION
342+ self .config .raw_http_proxy = http_proxy
314343 self ._connection = None
315344 self .__ca_certs = ca_certs
316345
@@ -353,6 +382,15 @@ def __init__(self,
353382 ". If no port is specified, a default of %d will be " \
354383 "used." % (http_proxy , self .config .proxy_port ))
355384
385+ # now populate self.config.proxy_handler
386+ if self .config .proxy_user and self .config .proxy_pass :
387+ auth_string = "%s:%s@" % (self .config .proxy_user , self .config .proxy_pass )
388+ else :
389+ auth_string = ""
390+ proxy_addr = "http://%s%s:%d" % (auth_string , self .config .proxy_server , self .config .proxy_port )
391+ self .config .proxy_handler = urllib2 .ProxyHandler ({self .config .scheme : proxy_addr })
392+
393+
356394
357395 if ensure_ascii :
358396 self ._json_loads = self ._json_loads_ascii
@@ -1389,7 +1427,7 @@ def set_up_auth_cookie(self):
13891427 """Sets up urllib2 with a cookie for authentication on the Shotgun
13901428 instance.
13911429 """
1392- sid = self ._get_session_token ()
1430+ sid = self .get_session_token ()
13931431 cj = cookielib .LWPCookieJar ()
13941432 c = cookielib .Cookie ('0' , '_session_id' , sid , None , False ,
13951433 self .config .server , False , False , "/" , True , False , None , True ,
@@ -1503,10 +1541,13 @@ def update_project_last_accessed(self, project, user=None):
15031541 record = self ._call_rpc ("update_project_last_accessed_by_current_user" , params )
15041542 result = self ._parse_records (record )[0 ]
15051543
1506-
1507- def _get_session_token (self ):
1508- """Hack to authenticate in order to download protected content
1509- like Attachments
1544+ def get_session_token (self ):
1545+ """
1546+ Get the session token associated with the current session.
1547+ If a session token has already been established, this is returned,
1548+ otherwise a new one is generated on the server and returned.
1549+
1550+ :returns: String containing a session token
15101551 """
15111552 if self .config .session_token :
15121553 return self .config .session_token
@@ -1515,22 +1556,13 @@ def _get_session_token(self):
15151556 session_token = (rv or {}).get ("session_id" )
15161557 if not session_token :
15171558 raise RuntimeError ("Could not extract session_id from %s" , rv )
1518-
1519- self .config .session_token = session_token
1520- return self .config .session_token
1559+ self .config .session_token = session_token
1560+ return session_token
15211561
15221562 def _build_opener (self , handler ):
15231563 """Build urllib2 opener with appropriate proxy handler."""
1524- if self .config .proxy_server :
1525- # handle proxy auth
1526- if self .config .proxy_user and self .config .proxy_pass :
1527- auth_string = "%s:%s@" % (self .config .proxy_user , self .config .proxy_pass )
1528- else :
1529- auth_string = ""
1530- proxy_addr = "http://%s%s:%d" % (auth_string , self .config .proxy_server , self .config .proxy_port )
1531- proxy_support = urllib2 .ProxyHandler ({self .config .scheme : proxy_addr })
1532-
1533- opener = urllib2 .build_opener (proxy_support , handler )
1564+ if self .config .proxy_handler :
1565+ opener = urllib2 .build_opener (self .config .proxy_handler , handler )
15341566 else :
15351567 opener = urllib2 .build_opener (handler )
15361568 return opener
@@ -1589,6 +1621,7 @@ def _call_rpc(self, method, params, include_auth_params=True, first=False):
15891621
15901622 def _auth_params (self ):
15911623 """ return a dictionary of the authentication parameters being used. """
1624+
15921625 # Used to authenticate HumanUser credentials
15931626 if self .config .user_login and self .config .user_password :
15941627 auth_params = {
@@ -1603,6 +1636,14 @@ def _auth_params(self):
16031636 "script_key" : str (self .config .api_key ),
16041637 }
16051638
1639+ # Authenticate using session_id
1640+ elif self .config .session_token :
1641+ auth_params = {
1642+ "session_token" : str (self .config .session_token ),
1643+ # Request server side to raise exception for expired sessions
1644+ "reject_if_expired" : True
1645+ }
1646+
16061647 else :
16071648 raise ValueError ("invalid auth params" )
16081649
@@ -1780,10 +1821,17 @@ def _response_errors(self, sg_response):
17801821
17811822 :raises ShotgunError: If the server response contains an exception.
17821823 """
1824+
1825+ ERR_AUTH = 102 # error code for authentication related problems
17831826
17841827 if isinstance (sg_response , dict ) and sg_response .get ("exception" ):
1785- raise Fault (sg_response .get ("message" ,
1786- "Unknown Error" ))
1828+
1829+ if sg_response .get ("error_code" ) == ERR_AUTH :
1830+ raise AuthenticationFault (sg_response .get ("message" , "Unknown Authentication Error" ))
1831+
1832+ else :
1833+ # raise general Fault
1834+ raise Fault (sg_response .get ("message" , "Unknown Error" ))
17871835 return
17881836
17891837 def _visit_data (self , data , visitor ):
0 commit comments