blob: 8843297b695495cd8492e0a239a25944cc1fac6c [file] [log] [blame]
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Twisted implementation of an interface to the Gerrit REST API and
associated JSON objects."""
import json
import urlparse
from common.gerrit.base import GERRIT_JSON_HEADER
from common.twisted_util.agent import Agent
from common.twisted_util.agent_util import ToRelativeURL, RelativeURLJoin
from common.twisted_util.response import JsonResponse, StringResponse
from common.twisted_util.body_producers import JsonBodyProducer
from common.twisted_util.netrc_authorizer import NETRCAuthorizer
from common.twisted_util.gce_authorizer import GCEAuthorizer
from twisted.python import log
class GerritJsonResponse(JsonResponse):
"""JsonResponse specialization for Gerrit responses.
Gerrit includes a header in its JSON responses to prevent XSS attacks:
https://gerrit-review.googlesource.com/Documentation/rest-api.html#output
This class strips that off before allowing standard JSON processing to
happen on the remainder of the body.
"""
# pylint: disable=no-init
def _processBody(self, body):
if not body.startswith(GERRIT_JSON_HEADER):
raise ValueError("Mal-formed JSON response does not begin with Gerrit "
"JSON header: (%r != %r)" % (
body[:len(GERRIT_JSON_HEADER)],
GERRIT_JSON_HEADER))
return JsonResponse._processBody(
self,
body[len(GERRIT_JSON_HEADER):]
)
class GerritAgent(Agent):
"""An 'Agent' that is specialized to query Gerrit servers."""
def __init__(self, host, *args, **kwargs):
# Use 'HTTPS' as the default protocol (backwards compatibility)
url = urlparse.urlparse(host)
if url.scheme == '':
host = 'https://%s' % (host,)
is_https = True
elif url.scheme == 'https':
is_https = True
else:
is_https = False
# Use 'NETRCAuthorizer' if none is provided (backwards-compatibility), but
# only for HTTPS. For GCE hosts, use custom mechanism to retrieve token as
# GCE machines do not use .netrc.
if (kwargs.get('authorizer') is None) and (is_https):
if GCEAuthorizer.is_gce_host():
log.msg("Using default 'GCE' authorizer for HTTPS connection")
kwargs['authorizer'] = GCEAuthorizer(GCEAuthorizer.get_token_dict())
else:
log.msg("Using default 'NETRC' authorizer for HTTPS connection")
kwargs['authorizer'] = NETRCAuthorizer()
Agent.__init__(self, host, *args, **kwargs)
# Overrides 'Agent._buildRequest'
def _buildRequest(self, path, headers):
"""Constructs a Gerrit request.
See 'Agent._buildRequest' for details.
"""
# Add authorization
if self._authorizer is not None:
if self._authorizer.addAuthHeadersForURL(
headers,
self.base_url):
# Switch to authenticated Gerrit URL
path = ToRelativeURL(path)
if not path.startswith('a/'):
path = RelativeURLJoin('a/', path)
elif self.verbose:
log.msg("No authentication for URL %r" % (self.base_url,))
return RelativeURLJoin(self.base_url, path)
# Disable argument number difference | pylint: disable=W0221
def request(self, method, path, body=None, protocol=None, **kwargs):
"""Makes a request to a Gerrit server.
'protocol' is a function that accepts a 'Response' object and
returns a Deferred whose return value is the loaded body. Some examples of
such functions are:
- StringResponse.Get
- GerritJsonResponse.Get
If omitted, the 'GerritJsonResponse.Get' function will be used, treating
the Gerrit response as JSON and deserializing it as a return value.
Args:
method: (str) The HTTP request type (GET, PUT, POST, DELETE)
path: (str) The path within the Agent's host to query
body: (object) If not 'None', the JSON object to serialize as the HTTP
request body.
protocol: (func) The Response processing function; if omitted, the
Response will be treated as JSON and deserialized.
kwargs: Remaining keyword arguments to 'Agent.request'
Returns: (Deferred) By default, the Deferred will return the JSON response;
this can be overridden via the 'protocol' parameter.
"""
if body is not None:
kwargs.setdefault('body_producer', JsonBodyProducer(body))
kwargs.setdefault('error_protocol', StringResponse.Get)
default_json = (protocol is None)
if default_json:
protocol = GerritJsonResponse.Get
d = Agent.request(
self,
method,
path,
**kwargs)
d.addCallback(protocol)
if (self.verbose) and (default_json):
def cbDumpResponse(response):
log.msg("Gerrit response:\n", json.dumps(response, indent=2))
return response
d.addCallback(cbDumpResponse)
return d