| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -14,7 +14,9 @@ To connect to a GitLab server, create a ``gitlab.Gitlab`` object: | |||
| 14 | 14 | import gitlab | |
| 15 | 15 | ||
| 16 | 16 | # private token or personal token authentication | |
| 17 | - gl = gitlab.Gitlab('http://10.0.0.1', private_token='JVNSESs8EwWRx5yDxM5q') | ||
| 17 | + # Note that a 'url' that results in 301/302 redirects will cause an error | ||
| 18 | + # (see below for more information). | ||
| 19 | + gl = gitlab.Gitlab(url='https://gitlab.example.com', private_token='JVNSESs8EwWRx5yDxM5q') | ||
| 18 | 20 | ||
| 19 | 21 | # oauth token authentication | |
| 20 | 22 | gl = gitlab.Gitlab('http://10.0.0.1', oauth_token='my_long_token_here') | |
@@ -47,6 +49,13 @@ configuration files. | |||
| 47 | 49 | If the GitLab server you are using redirects requests from http to https, | |
| 48 | 50 | make sure to use the ``https://`` protocol in the URL definition. | |
| 49 | 51 | ||
| 52 | + .. note:: | ||
| 53 | + | ||
| 54 | + It is highly recommended to use the final destination in the ``url`` field. | ||
| 55 | + What this means is that you should not use a URL which redirects as it will | ||
| 56 | + most likely cause errors. python-gitlab will raise a ``RedirectionError`` | ||
| 57 | + when it encounters a redirect which it believes will cause an error. | ||
| 58 | + | ||
| 50 | 59 | Note on password authentication | |
| 51 | 60 | ------------------------------- | |
| 52 | 61 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -89,6 +89,13 @@ You must define the ``url`` in each GitLab server section. | |||
| 89 | 89 | If the GitLab server you are using redirects requests from http to https, | |
| 90 | 90 | make sure to use the ``https://`` protocol in the ``url`` definition. | |
| 91 | 91 | ||
| 92 | + .. note:: | ||
| 93 | + | ||
| 94 | + It is highly recommended to use the final destination in the ``url`` field. | ||
| 95 | + What this means is that you should not use a URL which redirects as it will | ||
| 96 | + most likely cause errors. python-gitlab will raise a ``RedirectionError`` | ||
| 97 | + when it encounters a redirect which it believes will cause an error. | ||
| 98 | + | ||
| 92 | 99 | Only one of ``private_token``, ``oauth_token`` or ``job_token`` should be | |
| 93 | 100 | defined. If neither are defined an anonymous request will be sent to the Gitlab | |
| 94 | 101 | server, with very limited permissions. | |
@@ -101,7 +108,7 @@ We recommend that you use `Credential helpers`_ to securely store your tokens. | |||
| 101 | 108 | * - Option | |
| 102 | 109 | - Description | |
| 103 | 110 | * - ``url`` | |
| 104 | - - URL for the GitLab server | ||
| 111 | + - URL for the GitLab server. Do **NOT** use a URL which redirects. | ||
| 105 | 112 | * - ``private_token`` | |
| 106 | 113 | - Your user token. Login/password is not supported. Refer to `the | |
| 107 | 114 | official documentation | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -29,8 +29,9 @@ | |||
| 29 | 29 | from gitlab import utils | |
| 30 | 30 | ||
| 31 | 31 | REDIRECT_MSG = ( | |
| 32 | - "python-gitlab detected an http to https redirection. You " | ||
| 33 | - "must update your GitLab URL to use https:// to avoid issues." | ||
| 32 | + "python-gitlab detected a {status_code} ({reason!r}) redirection. You must update " | ||
| 33 | + "your GitLab URL to the correct URL to avoid issues. The redirection was from: " | ||
| 34 | + "{source!r} to {target!r}" | ||
| 34 | 35 | ) | |
| 35 | 36 | ||
| 36 | 37 | ||
@@ -456,24 +457,29 @@ def _build_url(self, path: str) -> str: | |||
| 456 | 457 | return "%s%s" % (self._url, path) | |
| 457 | 458 | ||
| 458 | 459 | def _check_redirects(self, result: requests.Response) -> None: | |
| 459 | - # Check the requests history to detect http to https redirections. | ||
| 460 | - # If the initial verb is POST, the next request will use a GET request, | ||
| 461 | - # leading to an unwanted behaviour. | ||
| 462 | - # If the initial verb is PUT, the data will not be send with the next | ||
| 463 | - # request. | ||
| 464 | - # If we detect a redirection to https with a POST or a PUT request, we | ||
| 460 | + # Check the requests history to detect 301/302 redirections. | ||
| 461 | + # If the initial verb is POST or PUT, the redirected request will use a | ||
| 462 | + # GET request, leading to unwanted behaviour. | ||
| 463 | + # If we detect a redirection with a POST or a PUT request, we | ||
| 465 | 464 | # raise an exception with a useful error message. | |
| 466 | - if result.history and self._base_url.startswith("http:"): | ||
| 467 | - for item in result.history: | ||
| 468 | - if item.status_code not in (301, 302): | ||
| 469 | - continue | ||
| 470 | - # GET methods can be redirected without issue | ||
| 471 | - if item.request.method == "GET": | ||
| 472 | - continue | ||
| 473 | - # Did we end-up with an https:// URL? | ||
| 474 | - location = item.headers.get("Location", None) | ||
| 475 | - if location and location.startswith("https://"): | ||
| 476 | - raise gitlab.exceptions.RedirectError(REDIRECT_MSG) | ||
| 465 | + if not result.history: | ||
| 466 | + return | ||
| 467 | + | ||
| 468 | + for item in result.history: | ||
| 469 | + if item.status_code not in (301, 302): | ||
| 470 | + continue | ||
| 471 | + # GET methods can be redirected without issue | ||
| 472 | + if item.request.method == "GET": | ||
| 473 | + continue | ||
| 474 | + target = item.headers.get("location") | ||
| 475 | + raise gitlab.exceptions.RedirectError( | ||
| 476 | + REDIRECT_MSG.format( | ||
| 477 | + status_code=item.status_code, | ||
| 478 | + reason=item.reason, | ||
| 479 | + source=item.url, | ||
| 480 | + target=target, | ||
| 481 | + ) | ||
| 482 | + ) | ||
| 477 | 483 | ||
| 478 | 484 | def _prepare_send_data( | |
| 479 | 485 | self, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,7 +2,7 @@ | |||
| 2 | 2 | import requests | |
| 3 | 3 | from httmock import HTTMock, response, urlmatch | |
| 4 | 4 | ||
| 5 | - from gitlab import GitlabHttpError, GitlabList, GitlabParsingError | ||
| 5 | + from gitlab import GitlabHttpError, GitlabList, GitlabParsingError, RedirectError | ||
| 6 | 6 | ||
| 7 | 7 | ||
| 8 | 8 | def test_build_url(gl): | |
@@ -123,9 +123,96 @@ def resp_cont(url, request): | |||
| 123 | 123 | assert call_count == 1 | |
| 124 | 124 | ||
| 125 | 125 | ||
| 126 | + def create_redirect_response( | ||
| 127 | + *, request: requests.models.PreparedRequest, http_method: str, api_path: str | ||
| 128 | + ) -> requests.models.Response: | ||
| 129 | + """Create a Requests response object that has a redirect in it""" | ||
| 130 | + | ||
| 131 | + assert api_path.startswith("/") | ||
| 132 | + http_method = http_method.upper() | ||
| 133 | + | ||
| 134 | + # Create a history which contains our original request which is redirected | ||
| 135 | + history = [ | ||
| 136 | + response( | ||
| 137 | + status_code=302, | ||
| 138 | + content="", | ||
| 139 | + headers={"Location": f"http://example.com/api/v4{api_path}"}, | ||
| 140 | + reason="Moved Temporarily", | ||
| 141 | + request=request, | ||
| 142 | + ) | ||
| 143 | + ] | ||
| 144 | + | ||
| 145 | + # Create a "prepped" Request object to be the final redirect. The redirect | ||
| 146 | + # will be a "GET" method as Requests changes the method to "GET" when there | ||
| 147 | + # is a 301/302 redirect code. | ||
| 148 | + req = requests.Request( | ||
| 149 | + method="GET", | ||
| 150 | + url=f"http://example.com/api/v4{api_path}", | ||
| 151 | + ) | ||
| 152 | + prepped = req.prepare() | ||
| 153 | + | ||
| 154 | + resp_obj = response( | ||
| 155 | + status_code=200, | ||
| 156 | + content="", | ||
| 157 | + headers={}, | ||
| 158 | + reason="OK", | ||
| 159 | + elapsed=5, | ||
| 160 | + request=prepped, | ||
| 161 | + ) | ||
| 162 | + resp_obj.history = history | ||
| 163 | + return resp_obj | ||
| 164 | + | ||
| 165 | + | ||
| 166 | + def test_http_request_302_get_does_not_raise(gl): | ||
| 167 | + """Test to show that a redirect of a GET will not cause an error""" | ||
| 168 | + | ||
| 169 | + method = "get" | ||
| 170 | + api_path = "/user/status" | ||
| 171 | + | ||
| 172 | + @urlmatch( | ||
| 173 | + scheme="http", netloc="localhost", path=f"/api/v4{api_path}", method=method | ||
| 174 | + ) | ||
| 175 | + def resp_cont( | ||
| 176 | + url: str, request: requests.models.PreparedRequest | ||
| 177 | + ) -> requests.models.Response: | ||
| 178 | + resp_obj = create_redirect_response( | ||
| 179 | + request=request, http_method=method, api_path=api_path | ||
| 180 | + ) | ||
| 181 | + return resp_obj | ||
| 182 | + | ||
| 183 | + with HTTMock(resp_cont): | ||
| 184 | + gl.http_request(verb=method, path=api_path) | ||
| 185 | + | ||
| 186 | + | ||
| 187 | + def test_http_request_302_put_raises_redirect_error(gl): | ||
| 188 | + """Test to show that a redirect of a PUT will cause an error""" | ||
| 189 | + | ||
| 190 | + method = "put" | ||
| 191 | + api_path = "/user/status" | ||
| 192 | + | ||
| 193 | + @urlmatch( | ||
| 194 | + scheme="http", netloc="localhost", path=f"/api/v4{api_path}", method=method | ||
| 195 | + ) | ||
| 196 | + def resp_cont( | ||
| 197 | + url: str, request: requests.models.PreparedRequest | ||
| 198 | + ) -> requests.models.Response: | ||
| 199 | + resp_obj = create_redirect_response( | ||
| 200 | + request=request, http_method=method, api_path=api_path | ||
| 201 | + ) | ||
| 202 | + return resp_obj | ||
| 203 | + | ||
| 204 | + with HTTMock(resp_cont): | ||
| 205 | + with pytest.raises(RedirectError) as exc: | ||
| 206 | + gl.http_request(verb=method, path=api_path) | ||
| 207 | + error_message = exc.value.error_message | ||
| 208 | + assert "Moved Temporarily" in error_message | ||
| 209 | + assert "http://localhost/api/v4/user/status" in error_message | ||
| 210 | + assert "http://example.com/api/v4/user/status" in error_message | ||
| 211 | + | ||
| 212 | + | ||
| 126 | 213 | def test_get_request(gl): | |
| 127 | 214 | @urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="get") | |
| 128 | - def resp_cont(url, request): | ||
| 215 | + def resp_cont(url: str, request: requests.models.PreparedRequest): | ||
| 129 | 216 | headers = {"content-type": "application/json"} | |
| 130 | 217 | content = '{"name": "project1"}' | |
| 131 | 218 | return response(200, content, headers, None, 5, request) | |
| Back | FazBrowse Home | New Git URL |
0 commit comments