diff --git a/examples/reference-gertty.yaml b/examples/reference-gertty.yaml index bbf5043..4eb77c3 100644 --- a/examples/reference-gertty.yaml +++ b/examples/reference-gertty.yaml @@ -23,9 +23,8 @@ servers: # username: CHANGEME # Your password in Gerrit (Settings -> HTTP Password). [required] # password: CHANGEME -# Authentication type required by the Gerrit server. Can be 'basic' or -# 'digest'. Defaults to 'digest' if not set or set to an unexpected -# value. +# Authentication type required by the Gerrit server. Can be 'basic', 'digest' or +# 'form'. Defaults to 'digest' if not set or set to an unexpected value. # auth-type: digest # A location where Gertty should store its git repositories. These # can be the same git repositories where you do your own work -- diff --git a/gertty/auth.py b/gertty/auth.py new file mode 100644 index 0000000..436df74 --- /dev/null +++ b/gertty/auth.py @@ -0,0 +1,68 @@ +# Copyright 2015 Christoph Gysin +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging +import requests +import urlparse + + +class FormAuth(requests.auth.AuthBase): + + def __init__(self, username, password): + self.username = username + self.password = password + self.log = logging.getLogger('gertty.auth') + + def _retry_using_form_auth(self, response, args): + adapter = requests.adapters.HTTPAdapter() + request = _copy_request(response.request) + + u = urlparse.urlparse(response.url) + url = urlparse.urlunparse([u.scheme, u.netloc, '/login', + None, None, None]) + auth = {'username': self.username, + 'password': self.password} + request2 = requests.Request('POST', url, data=auth).prepare() + response2 = adapter.send(request2, **args) + + if response2.status_code == 401: + self.log.error('Login failed: Invalid username or password?') + return response + + cookie = response2.headers.get('set-cookie') + if cookie is not None: + request.headers['Cookie'] = cookie + + response3 = adapter.send(request, **args) + return response3 + + def _response_hook(self, response, **kwargs): + if response.status_code == 401: + return self._retry_using_form_auth(response, kwargs) + return response + + def __call__(self, request): + request.headers["Connection"] = "Keep-Alive" + request.register_hook('response', self._response_hook) + return request + + +def _copy_request(request): + new_request = requests.PreparedRequest() + new_request.method = request.method + new_request.url = request.url + new_request.body = request.body + new_request.hooks = request.hooks + new_request.headers = request.headers.copy() + return new_request diff --git a/gertty/config.py b/gertty/config.py index f20fd0a..79c22dc 100644 --- a/gertty/config.py +++ b/gertty/config.py @@ -165,7 +165,7 @@ class Config(object): "Permissions are: {}".format(self.path, oct(mode))) exit(1) self.auth_type = server.get('auth-type', 'digest') - auth_types = ['digest', 'basic'] + auth_types = ['digest', 'basic', 'form'] if self.auth_type not in auth_types: self.auth_type = 'digest' self.verify_ssl = server.get('verify-ssl', True) diff --git a/gertty/sync.py b/gertty/sync.py index 5f8539f..09f549d 100644 --- a/gertty/sync.py +++ b/gertty/sync.py @@ -37,6 +37,7 @@ from six.moves.urllib import parse as urlparse import gertty.version from gertty import gitrepo +from gertty.auth import FormAuth HIGH_PRIORITY=0 NORMAL_PRIORITY=1 @@ -1306,6 +1307,8 @@ class Sync(object): self.session = requests.Session() if self.app.config.auth_type == 'basic': authclass = requests.auth.HTTPBasicAuth + elif self.app.config.auth_type == 'form': + authclass = FormAuth else: authclass = requests.auth.HTTPDigestAuth self.auth = authclass(