summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGauvain Pocentek <gauvain@pocentek.net>2018-08-24 17:09:55 +0200
committerGauvain Pocentek <gauvain@pocentek.net>2018-08-24 17:09:55 +0200
commita221d7b35bc20da758e7467fe789e16613c54275 (patch)
treea3d669b74286f4185b28d9cd5d7e2722b08ce58f
parent80a68f9258422d5d74f05a20234070ce3d6f5559 (diff)
downloadgitlab-a221d7b35bc20da758e7467fe789e16613c54275.tar.gz
Raise an exception on https redirects for PUT/POST
POST and PUT requests are modified by clients when redirections happen. A common problem with python-gitlab is a misconfiguration of the server URL: the http to https redirection breaks some requests. With this change python-gitlab should detect problematic redirections, and raise a proper exception instead of failing with a cryptic error. Closes #565
-rw-r--r--gitlab/__init__.py48
-rw-r--r--gitlab/exceptions.py4
-rw-r--r--gitlab/utils.py20
3 files changed, 53 insertions, 19 deletions
diff --git a/gitlab/__init__.py b/gitlab/__init__.py
index 4b9c4f4..5b23ef8 100644
--- a/gitlab/__init__.py
+++ b/gitlab/__init__.py
@@ -28,6 +28,7 @@ import six
import gitlab.config
from gitlab.const import * # noqa
from gitlab.exceptions import * # noqa
+from gitlab import utils # noqa
__title__ = 'python-gitlab'
__version__ = '1.5.1'
@@ -39,6 +40,9 @@ __copyright__ = 'Copyright 2013-2018 Gauvain Pocentek'
warnings.filterwarnings('default', category=DeprecationWarning,
module='^gitlab')
+REDIRECT_MSG = ('python-gitlab detected an http to https redirection. You '
+ 'must update your GitLab URL to use https:// to avoid issues.')
+
def _sanitize(value):
if isinstance(value, dict):
@@ -394,6 +398,26 @@ class Gitlab(object):
else:
return '%s%s' % (self._url, path)
+ def _check_redirects(self, result):
+ # Check the requests history to detect http to https redirections.
+ # If the initial verb is POST, the next request will use a GET request,
+ # leading to an unwanted behaviour.
+ # If the initial verb is PUT, the data will not be send with the next
+ # request.
+ # If we detect a redirection to https with a POST or a PUT request, we
+ # raise an exception with a useful error message.
+ if result.history and self._base_url.startswith('http:'):
+ for item in result.history:
+ if item.status_code not in (301, 302):
+ continue
+ # GET methods can be redirected without issue
+ if result.request.method == 'GET':
+ continue
+ # Did we end-up with an https:// URL?
+ location = item.headers.get('Location', None)
+ if location and location.startswith('https://'):
+ raise RedirectError(REDIRECT_MSG)
+
def http_request(self, verb, path, query_data={}, post_data=None,
streamed=False, files=None, **kwargs):
"""Make an HTTP request to the Gitlab server.
@@ -417,27 +441,11 @@ class Gitlab(object):
GitlabHttpError: When the return code is not 2xx
"""
- def sanitized_url(url):
- parsed = six.moves.urllib.parse.urlparse(url)
- new_path = parsed.path.replace('.', '%2E')
- return parsed._replace(path=new_path).geturl()
-
url = self._build_url(path)
- def copy_dict(dest, src):
- for k, v in src.items():
- if isinstance(v, dict):
- # Transform dict values in new attributes. For example:
- # custom_attributes: {'foo', 'bar'} =>
- # custom_attributes['foo']: 'bar'
- for dict_k, dict_v in v.items():
- dest['%s[%s]' % (k, dict_k)] = dict_v
- else:
- dest[k] = v
-
params = {}
- copy_dict(params, query_data)
- copy_dict(params, kwargs)
+ utils.copy_dict(params, query_data)
+ utils.copy_dict(params, kwargs)
opts = self._get_session_opts(content_type='application/json')
@@ -462,7 +470,7 @@ class Gitlab(object):
req = requests.Request(verb, url, json=json, data=data, params=params,
files=files, **opts)
prepped = self.session.prepare_request(req)
- prepped.url = sanitized_url(prepped.url)
+ prepped.url = utils.sanitized_url(prepped.url)
settings = self.session.merge_environment_settings(
prepped.url, {}, streamed, verify, None)
@@ -472,6 +480,8 @@ class Gitlab(object):
while True:
result = self.session.send(prepped, timeout=timeout, **settings)
+ self._check_redirects(result)
+
if 200 <= result.status_code < 300:
return result
diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py
index 6736f67..650328a 100644
--- a/gitlab/exceptions.py
+++ b/gitlab/exceptions.py
@@ -41,6 +41,10 @@ class GitlabAuthenticationError(GitlabError):
pass
+class RedirectError(GitlabError):
+ pass
+
+
class GitlabParsingError(GitlabError):
pass
diff --git a/gitlab/utils.py b/gitlab/utils.py
index a449f81..49e2c88 100644
--- a/gitlab/utils.py
+++ b/gitlab/utils.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import six
+
class _StdoutStream(object):
def __call__(self, chunk):
@@ -31,3 +33,21 @@ def response_content(response, streamed, action, chunk_size):
for chunk in response.iter_content(chunk_size=chunk_size):
if chunk:
action(chunk)
+
+
+def copy_dict(dest, src):
+ for k, v in src.items():
+ if isinstance(v, dict):
+ # Transform dict values to new attributes. For example:
+ # custom_attributes: {'foo', 'bar'} =>
+ # "custom_attributes['foo']": "bar"
+ for dict_k, dict_v in v.items():
+ dest['%s[%s]' % (k, dict_k)] = dict_v
+ else:
+ dest[k] = v
+
+
+def sanitized_url(url):
+ parsed = six.moves.urllib.parse.urlparse(url)
+ new_path = parsed.path.replace('.', '%2E')
+ return parsed._replace(path=new_path).geturl()