Skip to content

Commit 3cefafe

Browse files
committed
Add llrest.RESTService(cookie_policy) with special case for False.
The new cookie_policy parameter allows passing a subclass (instance) of http.cookiejar.CookiePolicy (in Python 2, cookielib.CookiePolicy) to RESTService.session.cookies.set_policy(). But because creating a block-all policy subclass is nontrivial, recognize False as a special case to provide an implicit block-all-cookies policy subclass instance. Setting cookie_policy=False bypasses TeamCity CSRF checks. Recent TeamCity releases are paranoid about cookies, which we don't need and don't use. Without cookie_policy=False, though, TC fails valid requests with 403.
1 parent ff6885b commit 3cefafe

File tree

1 file changed

+46
-6
lines changed

1 file changed

+46
-6
lines changed

llbase/llrest.py

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@
3434
try:
3535
# python 3
3636
from urllib.parse import urlsplit, urlunsplit, SplitResult
37+
from http import cookiejar
3738
except ImportError:
3839
# python 2
3940
from urlparse import urlsplit, urlunsplit, SplitResult
41+
import cookielib as cookiejar
4042
from urllib3 import poolmanager
4143
from xml.etree import cElementTree as ElementTree
4244

@@ -228,17 +230,28 @@ class RESTService(object):
228230
undesired dependency between that module and the calling script.
229231
"""
230232

231-
def __init__(self, name, baseurl, codec=RESTEncoding.LLSD, authenticated=True, username=None, password=None, proxy_hostport=None, cert=None, basepath='', **session_params):
233+
def __init__(self, name, baseurl, codec=RESTEncoding.LLSD, authenticated=True,
234+
username=None, password=None, proxy_hostport=None, cert=None, basepath='',
235+
cookie_policy=None, **session_params):
232236
"""
233237
Parameters:
234238
name: used in generating error messages
235-
baseurl: a url prefix used for all requests to the service; may be an empty string
239+
baseurl: a url prefix used for all requests to the service; may be an
240+
empty string
236241
basepath: a url path that overrides the path part of baseurl
237-
codec: one of the constants defined by the RESTEncoding class - determines expected request/response encoding
238-
authenticated: a boolean: whether or not the service expects HTTP authentication (see get_credentials)
242+
codec: one of the constants defined by the RESTEncoding class -
243+
determines expected request/response encoding
244+
authenticated: a boolean: whether or not the service expects HTTP
245+
authentication (see get_credentials)
239246
username: the username to be used for HTTP authentication
240247
password: the password to be used for HTTP authentication
241-
proxy_hostport: an HTTP proxy hostname and port number (host:port) through which requests should be routed
248+
proxy_hostport: an HTTP proxy hostname and port number (host:port)
249+
through which requests should be routed
250+
cert: string .pem pathname or (cert, key) pathname pair
251+
https://2.python-requests.org/en/master/api/#requests.Session.cert
252+
cookie_policy: http.cookiejar.CookiePolicy subclass for the session
253+
https://docs.python.org/3/library/http.cookiejar.html#http.cookiejar.CookiePolicy
254+
Special value cookie_policy=False blocks all cookies.
242255
243256
Any other keyword parameters are passed through to the requests.Session()
244257
"""
@@ -260,7 +273,15 @@ def __init__(self, name, baseurl, codec=RESTEncoding.LLSD, authenticated=True, u
260273
'https':'http://%s' % proxy_hostport } \
261274
if proxy_hostport else None
262275
if cert:
263-
self.session.cert = cert
276+
self.session.cert = cert
277+
if cookie_policy is not None:
278+
if cookie_policy == False:
279+
# request implicit blocking
280+
cookie_policy = _BlockAllCookies()
281+
elif issubclass(cookie_policy, cookiejar.CookiePolicy):
282+
# set_policy() wants an instance, not a class
283+
cookie_policy = cookie_policy()
284+
self.session.cookies.set_policy(cookie_policy)
264285

265286
# defer setting the Session credentials to the request methods,
266287
# so that the _get_credentials method can provide prompting for them
@@ -655,13 +676,32 @@ def init_poolmanager(self, connections, maxsize, block=False):
655676
"""Create and initialize the urllib3 PoolManager."""
656677
ctx = ssl.create_default_context()
657678
ctx.set_ciphers('DEFAULT@SECLEVEL=1')
679+
# https://stackoverflow.com/a/59059052
680+
ctx.check_hostname = False
681+
ctx.verify_mode = ssl.CERT_NONE
658682
self.poolmanager = poolmanager.PoolManager(
659683
num_pools=connections,
660684
maxsize=maxsize,
661685
block=block,
662686
ssl_version=ssl.PROTOCOL_TLS,
663687
ssl_context=ctx)
664688

689+
class _BlockAllCookies(cookiejar.CookiePolicy):
690+
"""
691+
Helper for RESTService(cookie_policy=False), derived from
692+
https://stackoverflow.com/a/21714597
693+
"""
694+
netscape = True
695+
rfc2965 = False
696+
hide_cookie2 = False
697+
698+
def return_ok(self, *args, **kwds):
699+
return False
700+
701+
set_ok = return_ok
702+
domain_return_ok = return_ok
703+
path_return_ok = return_ok
704+
665705
class SimpleRESTService(RESTService):
666706
"""
667707
Subclass wrapper around RESTService to use the right defaults

0 commit comments

Comments
 (0)