[PATCH 3 of 3 V2] url: support auth.cookiefile for adding cookies to HTTP requests
Augie Fackler
raf at durin42.com
Mon Apr 3 21:00:13 UTC 2017
On Tue, Mar 28, 2017 at 08:00:50PM -0700, Gregory Szorc wrote:
> # HG changeset patch
> # User Gregory Szorc <gregory.szorc at gmail.com>
> # Date 1490756307 25200
> # Tue Mar 28 19:58:27 2017 -0700
> # Node ID 05b01865ed532a11cd6a824b600876c94c2e293e
> # Parent 5cf00a25e8c066bd664dde4cb302bd52a75195d6
> url: support auth.cookiefile for adding cookies to HTTP requests
I think I'm fine with these patches as-is. I assume the theory is that
this cookie would be checked by some reverse proxy, rather than hgweb
itself? Or should we be expecting more changes that introduce
cookie-checking hooks of some kind right into hgweb?
>
> Mercurial can't currently send cookies as part of HTTP requests.
> Some authentication systems use cookies. So, adding support for
> sending cookies seems like a useful feature.
>
> This patch implements support for reading cookies from a file
> and automatically sending them as part of the request. We rely
> on the "cookiejar" Python module to do the heavy lifting of
> parsing cookies files. We currently support the Mozilla (really
> Netscape-era) and LWP cookie formats. These are the formats
> supported by Python 2's cookielib.
>
> diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
> --- a/mercurial/help/config.txt
> +++ b/mercurial/help/config.txt
> @@ -323,12 +323,31 @@ related options for the diff command.
> ``auth``
> --------
>
> -Authentication credentials for HTTP authentication. This section
> -allows you to store usernames and passwords for use when logging
> -*into* HTTP servers. See :hg:`help config.web` if
> -you want to configure *who* can login to your HTTP server.
> -
> -Each line has the following format::
> +Authentication credentials and other authentication-like configuration
> +for HTTP connections. This section allows you to store usernames and
> +passwords for use when logging *into* HTTP servers. See
> +:hg:`help config.web` if you want to configure *who* can login to
> +your HTTP server.
> +
> +The following options apply to all hosts.
> +
> +``cookiefile``
> + Path to a file containing HTTP cookie lines. Cookies matching a
> + host will be sent automatically.
> +
> + The file format uses the Mozilla cookies.txt or libwww-perl "Set-Cookie3"
> + format. For specifics of each format, do an Internet search for
> + "Netscape cookies.txt format" or "libwww-perl cookie format."
> +
> + Note: the Mozilla cookies parser does not handle port numbers on domains.
> + You will need to remove ports from the domain for the cookie to be
> + recognized. This could result in a cookie being disclosed to an unwanted
> + server.
> +
> + The cookies file is read-only.
> +
> +Other options in this section are grouped by name and have the following
> +format::
>
> <name>.<argument> = <value>
>
> diff --git a/mercurial/url.py b/mercurial/url.py
> --- a/mercurial/url.py
> +++ b/mercurial/url.py
> @@ -417,6 +417,45 @@ class httpbasicauthhandler(urlreq.httpba
> else:
> return None
>
> +class cookiehandler(urlreq.basehandler):
> + """HTTP request handler that adds cookies seen in a cookies file."""
> +
> + # Classes that support the CookieJar interface.
> + COOKIEJARS = (
> + util.cookielib.MozillaCookieJar,
> + util.cookielib.LWPCookieJar,
> + )
> +
> + def __init__(self, ui):
> + self.cookiejar = None
> +
> + cookiefile = ui.config('auth', 'cookiefile')
> + if not cookiefile:
> + return
> +
> + cookiefile = util.expandpath(cookiefile)
> +
> + for cls in self.COOKIEJARS:
> + try:
> + jar = cls(cookiefile)
> + jar.load()
> + self.cookiejar = jar
> + break
> + except util.cookielib.LoadError:
> + pass
> +
> + if self.cookiejar is None:
> + ui.warn(_('(error loading cookie file %s: continuing without '
> + 'cookies)\n') % cookiefile)
> +
> + def http_request(self, request):
> + if self.cookiejar:
> + self.cookiejar.add_cookie_header(request)
> +
> + return request
> +
> + https_request = http_request
> +
> handlerfuncs = []
>
> def opener(ui, authinfo=None):
> @@ -450,6 +489,7 @@ def opener(ui, authinfo=None):
> handlers.extend((httpbasicauthhandler(passmgr),
> httpdigestauthhandler(passmgr)))
> handlers.extend([h(ui, passmgr) for h in handlerfuncs])
> + handlers.append(cookiehandler(ui))
> opener = urlreq.buildopener(*handlers)
>
> # The user agent should should *NOT* be used by servers for e.g.
> diff --git a/tests/test-http.t b/tests/test-http.t
> --- a/tests/test-http.t
> +++ b/tests/test-http.t
> @@ -1,4 +1,4 @@
> -#require serve
> +#require killdaemons serve
>
> $ hg init test
> $ cd test
> @@ -333,3 +333,87 @@ check abort error reporting while pullin
> abort: pull failed on remote
> [255]
> $ cat error.log
> +
> +corrupt cookies file should yield a warning
> +
> + $ cat > $TESTTMP/cookies.txt << EOF
> + > bad format
> + > EOF
> +
> + $ hg --config auth.cookiefile=$TESTTMP/cookies.txt id http://localhost:$HGPORT/
> + (error loading cookie file $TESTTMP/cookies.txt: continuing without cookies)
> + 56f9bc90cce6
> +
> + $ killdaemons.py
> +
> +Create dummy authentication handler that looks for cookies. It doesn't do anything
> +useful. It just raises an HTTP 500 with details about the Cookie request header.
> +We raise HTTP 500 because its message is printed in the abort message.
> +
> + $ cat > cookieauth.py << EOF
> + > from mercurial import util
> + > from mercurial.hgweb import common
> + > def perform_authentication(hgweb, req, op):
> + > cookie = req.env.get('HTTP_COOKIE')
> + > if not cookie:
> + > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, 'no-cookie')
> + > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, 'Cookie: %s' % cookie)
> + > def extsetup():
> + > common.permhooks.insert(0, perform_authentication)
> + > EOF
> +
> + $ hg serve --config extensions.cookieauth=cookieauth.py -R test -p $HGPORT -d --pid-file=pid
> + $ cat pid > $DAEMON_PIDS
> +
> +Request without cookie sent should fail due to lack of cookie
> +
> + $ hg id http://localhost:$HGPORT
> + abort: HTTP Error 500: no-cookie
> + [255]
> +
> +Populate a Netscape cookies file
> +
> + $ cat > cookies.txt << EOF
> + > # HTTP Cookie File
> + > # Expiration is 2030-01-01 at midnight
> + > .example.com TRUE / FALSE 1893456000 hgkey mozvalue
> + > EOF
> +
> +Should not send a cookie for another domain
> +
> + $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
> + abort: HTTP Error 500: no-cookie
> + [255]
> +
> +Add a cookie entry for our test server and verify it is sent
> +
> + $ cat >> cookies.txt << EOF
> + > localhost.local FALSE / FALSE 1893456000 hgkey mozlocalhostvalue
> + > EOF
> +
> + $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
> + abort: HTTP Error 500: Cookie: hgkey=mozlocalhostvalue
> + [255]
> +
> +Now do the same for an LWP cookie jar
> +
> + $ cat > cookies.txt << EOF
> + > #LWP-Cookies-1.0
> + > Set-Cookie3: hgkey=lwpvalue; path="/"; domain=.example.com; path_spec; expires="2030-01-01 00:00:00"; version=0
> + > EOF
> +
> +Should not send a cookie for another domain
> +
> + $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
> + abort: HTTP Error 500: no-cookie
> + [255]
> +
> +Cookie for our test server should be sent
> +
> + $ cat >> cookies.txt << EOF
> + > Set-Cookie3: hgkey=lwplocalhostvalue; path="/"; domain=localhost.local; path_spec; expires="2030-01-01 00:00:00"; version=0
> + > EOF
> +
> + $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
> + abort: HTTP Error 500: Cookie: hgkey=lwplocalhostvalue
> + [255]
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
More information about the Mercurial-devel
mailing list