Mercurial > public > mercurial-scm > hg-stable
diff tests/test-http-api-httpv2.t @ 37050:fddcb51b5084
wireproto: define permissions-based routing of HTTPv2 wire protocol
Now that we have a scaffolding for serving version 2 of the HTTP
protocol, let's start implementing it.
A good place to start is URL routing and basic request processing
semantics. We can focus on content types, capabilities detect, etc
later.
Version 2 of the HTTP wire protocol encodes the needed permissions
of the request in the URL path. The reasons for this are documented
in the added documentation. In short, a) it makes it really easy and
fail proof for server administrators to implement path-based
authentication and b) it will enable clients to realize very early in
a server exchange that authentication will be required to complete
the operation. This latter point avoids all kinds of complexity and
problems, like dealing with Expect: 100-continue and clients finding
out later during `hg push` that they need to provide authentication.
This will avoid the current badness where clients send a full bundle,
get an HTTP 403, provide authentication, then retransmit the bundle.
In order to implement command checking, we needed to implement a
protocol handler for the new wire protocol. Our handler is just
small enough to run the code we've implemented.
Tests for the defined functionality have been added.
I very much want to refactor the permissions checking code and define
a better response format. But this can be done later. Nothing is
covered by backwards compatibility at this point.
Differential Revision: https://phab.mercurial-scm.org/D2836
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Mon, 19 Mar 2018 16:43:47 -0700 |
parents | 1cfef5693203 |
children | fc5e261915b9 |
line wrap: on
line diff
--- a/tests/test-http-api-httpv2.t Tue Mar 13 16:53:21 2018 -0700 +++ b/tests/test-http-api-httpv2.t Mon Mar 19 16:43:47 2018 -0700 @@ -1,7 +1,24 @@ + $ HTTPV2=exp-http-v2-0001 + $ send() { > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/ > } + $ cat > dummycommands.py << EOF + > from mercurial import wireprototypes, wireproto + > @wireproto.wireprotocommand('customreadonly', permission='pull') + > def customreadonly(repo, proto): + > return wireprototypes.bytesresponse(b'customreadonly bytes response') + > @wireproto.wireprotocommand('customreadwrite', permission='push') + > def customreadwrite(repo, proto): + > return wireprototypes.bytesresponse(b'customreadwrite bytes response') + > EOF + + $ cat >> $HGRCPATH << EOF + > [extensions] + > dummycommands = $TESTTMP/dummycommands.py + > EOF + $ hg init server $ cat > server/.hg/hgrc << EOF > [experimental] @@ -13,7 +30,7 @@ HTTP v2 protocol not enabled by default $ send << EOF - > httprequest GET api/exp-http-v2-0001 + > httprequest GET api/$HTTPV2 > user-agent: test > EOF using raw connection to peer @@ -43,14 +60,14 @@ $ hg -R server serve -p $HGPORT -d --pid-file hg.pid $ cat hg.pid > $DAEMON_PIDS -Requests simply echo their path (for now) +Request to read-only command works out of the box $ send << EOF - > httprequest GET api/exp-http-v2-0001/path1/path2 + > httprequest GET api/$HTTPV2/ro/customreadonly > user-agent: test > EOF using raw connection to peer - s> GET /api/exp-http-v2-0001/path1/path2 HTTP/1.1\r\n + s> GET /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n s> Accept-Encoding: identity\r\n s> user-agent: test\r\n s> host: $LOCALIP:$HGPORT\r\n (glob) @@ -60,6 +77,178 @@ s> Server: testing stub value\r\n s> Date: $HTTP_DATE$\r\n s> Content-Type: text/plain\r\n - s> Content-Length: 12\r\n + s> Content-Length: 18\r\n + s> \r\n + s> ro/customreadonly\n + +Request to unknown command yields 404 + + $ send << EOF + > httprequest GET api/$HTTPV2/ro/badcommand + > user-agent: test + > EOF + using raw connection to peer + s> GET /api/exp-http-v2-0001/ro/badcommand HTTP/1.1\r\n + s> Accept-Encoding: identity\r\n + s> user-agent: test\r\n + s> host: $LOCALIP:$HGPORT\r\n (glob) + s> \r\n + s> makefile('rb', None) + s> HTTP/1.1 404 Not Found\r\n + s> Server: testing stub value\r\n + s> Date: $HTTP_DATE$\r\n + s> Content-Type: text/plain\r\n + s> Content-Length: 42\r\n + s> \r\n + s> unknown wire protocol command: badcommand\n + +Request to read-write command fails because server is read-only by default + +GET to read-write request not allowed + + $ send << EOF + > httprequest GET api/$HTTPV2/rw/customreadonly + > user-agent: test + > EOF + using raw connection to peer + s> GET /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n + s> Accept-Encoding: identity\r\n + s> user-agent: test\r\n + s> host: $LOCALIP:$HGPORT\r\n (glob) + s> \r\n + s> makefile('rb', None) + s> HTTP/1.1 405 push requires POST request\r\n + s> Server: testing stub value\r\n + s> Date: $HTTP_DATE$\r\n + s> Content-Length: 17\r\n + s> \r\n + s> permission denied + +Even for unknown commands + + $ send << EOF + > httprequest GET api/$HTTPV2/rw/badcommand + > user-agent: test + > EOF + using raw connection to peer + s> GET /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n + s> Accept-Encoding: identity\r\n + s> user-agent: test\r\n + s> host: $LOCALIP:$HGPORT\r\n (glob) + s> \r\n + s> makefile('rb', None) + s> HTTP/1.1 405 push requires POST request\r\n + s> Server: testing stub value\r\n + s> Date: $HTTP_DATE$\r\n + s> Content-Length: 17\r\n + s> \r\n + s> permission denied + +SSL required by default + + $ send << EOF + > httprequest POST api/$HTTPV2/rw/customreadonly + > user-agent: test + > EOF + using raw connection to peer + s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n + s> Accept-Encoding: identity\r\n + s> user-agent: test\r\n + s> host: $LOCALIP:$HGPORT\r\n (glob) + s> \r\n + s> makefile('rb', None) + s> HTTP/1.1 403 ssl required\r\n + s> Server: testing stub value\r\n + s> Date: $HTTP_DATE$\r\n + s> Content-Length: 17\r\n s> \r\n - s> path1/path2\n + s> permission denied + +Restart server to allow non-ssl read-write operations + + $ killdaemons.py + $ cat > server/.hg/hgrc << EOF + > [experimental] + > web.apiserver = true + > web.api.http-v2 = true + > [web] + > push_ssl = false + > EOF + + $ hg -R server serve -p $HGPORT -d --pid-file hg.pid + $ cat hg.pid > $DAEMON_PIDS + +Server insists on POST for read-write commands + + $ send << EOF + > httprequest GET api/$HTTPV2/rw/customreadonly + > user-agent: test + > EOF + using raw connection to peer + s> GET /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n + s> Accept-Encoding: identity\r\n + s> user-agent: test\r\n + s> host: $LOCALIP:$HGPORT\r\n (glob) + s> \r\n + s> makefile('rb', None) + s> HTTP/1.1 405 push requires POST request\r\n + s> Server: testing stub value\r\n + s> Date: $HTTP_DATE$\r\n + s> Content-Length: 17\r\n + s> \r\n + s> permission denied + + $ killdaemons.py + $ cat > server/.hg/hgrc << EOF + > [experimental] + > web.apiserver = true + > web.api.http-v2 = true + > [web] + > push_ssl = false + > allow-push = * + > EOF + + $ hg -R server serve -p $HGPORT -d --pid-file hg.pid + $ cat hg.pid > $DAEMON_PIDS + +Authorized request for valid read-write command works + + $ send << EOF + > httprequest POST api/$HTTPV2/rw/customreadonly + > user-agent: test + > EOF + using raw connection to peer + s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n + s> Accept-Encoding: identity\r\n + s> user-agent: test\r\n + s> host: $LOCALIP:$HGPORT\r\n (glob) + s> \r\n + s> makefile('rb', None) + s> HTTP/1.1 200 OK\r\n + s> Server: testing stub value\r\n + s> Date: $HTTP_DATE$\r\n + s> Content-Type: text/plain\r\n + s> Content-Length: 18\r\n + s> \r\n + s> rw/customreadonly\n + +Authorized request for unknown command is rejected + + $ send << EOF + > httprequest POST api/$HTTPV2/rw/badcommand + > user-agent: test + > EOF + using raw connection to peer + s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n + s> Accept-Encoding: identity\r\n + s> user-agent: test\r\n + s> host: $LOCALIP:$HGPORT\r\n (glob) + s> \r\n + s> makefile('rb', None) + s> HTTP/1.1 404 Not Found\r\n + s> Server: testing stub value\r\n + s> Date: $HTTP_DATE$\r\n + s> Content-Type: text/plain\r\n + s> Content-Length: 42\r\n + s> \r\n + s> unknown wire protocol command: badcommand\n