Browse Source

Merge "Support authentication in run_http_exc"

tags/1.0.0
Jenkins 4 years ago
parent
commit
79262a5230
4 changed files with 83 additions and 7 deletions
  1. 5
    0
      doc/source/installation.rst
  2. 7
    0
      git-review.1
  3. 25
    7
      git_review/cmd.py
  4. 46
    0
      git_review/tests/test_unit.py

+ 5
- 0
doc/source/installation.rst View File

@@ -60,6 +60,11 @@ defaultremote (default: gerrit).
60 60
 * You can specify different values to be used as defaults in
61 61
   ~/.config/git-review/git-review.conf or /etc/git-review/git-review.conf.
62 62
 
63
+* Git-review will query git credential system for gerrit user/password when
64
+  authentication failed over http(s). Unlike git, git-review does not persist
65
+  gerrit user/password in git credential system for security purposes and git
66
+  credential system configuration stays under user responsibility.
67
+
63 68
 Hooks
64 69
 =====
65 70
 

+ 7
- 0
git-review.1 View File

@@ -235,6 +235,13 @@ If you want all output to use color
235 235
 If you wish not to use color for any output. (default with Git older than 1.8.4)
236 236
 .El
237 237
 .El
238
+.Pp
239
+.Nm
240
+will query git credential system for gerrit user/password when
241
+authentication failed over http(s). Unlike git,
242
+.Nm
243
+does not persist gerrit user/password in git credential system for security
244
+purposes and git credential system configuration stays under user responsibility.
238 245
 .Sh FILES
239 246
 To use
240 247
 .Nm

+ 25
- 7
git_review/cmd.py View File

@@ -121,7 +121,7 @@ def build_review_number(review, patchset):
121 121
     return review
122 122
 
123 123
 
124
-def run_command_status(*argv, **env):
124
+def run_command_status(*argv, **kwargs):
125 125
     if VERBOSE:
126 126
         print(datetime.datetime.now(), "Running:", " ".join(argv))
127 127
     if len(argv) == 1:
@@ -130,17 +130,21 @@ def run_command_status(*argv, **env):
130 130
             argv = shlex.split(argv[0].encode('utf-8'))
131 131
         else:
132 132
             argv = shlex.split(str(argv[0]))
133
+    stdin = kwargs.pop('stdin', None)
133 134
     newenv = os.environ.copy()
134
-    newenv.update(env)
135
-    p = subprocess.Popen(argv, stdout=subprocess.PIPE,
136
-                         stderr=subprocess.STDOUT, env=newenv)
137
-    (out, nothing) = p.communicate()
135
+    newenv.update(kwargs)
136
+    p = subprocess.Popen(argv,
137
+                         stdin=subprocess.PIPE if stdin else None,
138
+                         stdout=subprocess.PIPE,
139
+                         stderr=subprocess.STDOUT,
140
+                         env=newenv)
141
+    (out, nothing) = p.communicate(stdin)
138 142
     out = out.decode('utf-8', 'replace')
139 143
     return (p.returncode, out.strip())
140 144
 
141 145
 
142
-def run_command(*argv, **env):
143
-    (rc, output) = run_command_status(*argv, **env)
146
+def run_command(*argv, **kwargs):
147
+    (rc, output) = run_command_status(*argv, **kwargs)
144 148
     return output
145 149
 
146 150
 
@@ -155,6 +159,15 @@ def run_command_exc(klazz, *argv, **env):
155 159
     return output
156 160
 
157 161
 
162
+def git_credentials(klazz, url):
163
+    """Get credentials using git credential."""
164
+    cmd = 'git', 'credential', 'fill'
165
+    stdin = 'url=%s' % url
166
+    out = run_command_exc(klazz, *cmd, stdin=stdin)
167
+    data = dict(l.split('=', 1) for l in out.splitlines())
168
+    return data['username'], data['password']
169
+
170
+
158 171
 def http_code_2_return_code(code):
159 172
     """Tranform http status code to system return code."""
160 173
     return (code - 301) % 255 + 1
@@ -174,6 +187,11 @@ def run_http_exc(klazz, url, **env):
174 187
 
175 188
     try:
176 189
         res = requests.get(url, **env)
190
+        if res.status_code == 401:
191
+            env['auth'] = git_credentials(klazz, url)
192
+            res = requests.get(url, **env)
193
+    except klazz:
194
+        raise
177 195
     except Exception as err:
178 196
         raise klazz(255, str(err), ('GET', url), env)
179 197
     if not 200 <= res.status_code < 300:

+ 46
- 0
git_review/tests/test_unit.py View File

@@ -168,6 +168,14 @@ class FakeException(Exception):
168 168
         self.code = code
169 169
 
170 170
 
171
+FAKE_GIT_CREDENTIAL_FILL = """\
172
+protocol=http
173
+host=gerrit.example.com
174
+username=user
175
+password=pass
176
+"""
177
+
178
+
171 179
 class GitReviewUnitTest(testtools.TestCase):
172 180
     """Class for misc unit tests."""
173 181
 
@@ -190,3 +198,41 @@ class GitReviewUnitTest(testtools.TestCase):
190 198
         except FakeException as err:
191 199
             self.assertEqual(255, err.code)
192 200
             mock_get.assert_called_once_with(url)
201
+
202
+    @mock.patch('git_review.cmd.run_command_exc')
203
+    @mock.patch('requests.get', return_value=FakeResponse(200))
204
+    def test_run_http_exc_without_auth(self, mock_get, mock_run):
205
+        url = 'http://user@gerrit.example.com'
206
+
207
+        cmd.run_http_exc(FakeException, url)
208
+        self.assertFalse(mock_run.called)
209
+        mock_get.assert_called_once_with(url)
210
+
211
+    @mock.patch('git_review.cmd.run_command_exc',
212
+                return_value=FAKE_GIT_CREDENTIAL_FILL)
213
+    @mock.patch('requests.get',
214
+                side_effect=[FakeResponse(401), FakeResponse(200)])
215
+    def test_run_http_exc_with_auth(self, mock_get, mock_run):
216
+        url = 'http://user@gerrit.example.com'
217
+
218
+        cmd.run_http_exc(FakeException, url)
219
+        mock_run.assert_called_once_with(mock.ANY, 'git', 'credential', 'fill',
220
+                                         stdin='url=%s' % url)
221
+        calls = [mock.call(url), mock.call(url, auth=('user', 'pass'))]
222
+        mock_get.assert_has_calls(calls)
223
+
224
+    @mock.patch('git_review.cmd.run_command_exc',
225
+                return_value=FAKE_GIT_CREDENTIAL_FILL)
226
+    @mock.patch('requests.get', return_value=FakeResponse(401))
227
+    def test_run_http_exc_with_failing_auth(self, mock_get, mock_run):
228
+        url = 'http://user@gerrit.example.com'
229
+
230
+        try:
231
+            cmd.run_http_exc(FakeException, url)
232
+            self.fails('Exception expected')
233
+        except FakeException as err:
234
+            self.assertEqual(cmd.http_code_2_return_code(401), err.code)
235
+        mock_run.assert_called_once_with(mock.ANY, 'git', 'credential', 'fill',
236
+                                         stdin='url=%s' % url)
237
+        calls = [mock.call(url), mock.call(url, auth=('user', 'pass'))]
238
+        mock_get.assert_has_calls(calls)

Loading…
Cancel
Save