Merge lp:~piastucki/bzr-xmloutput/fix-auth into lp:bzr-xmloutput

Proposed by Piotr Piastucki
Status: Merged
Approved by: Guillermo Gonzalez
Approved revision: 168
Merged at revision: 167
Proposed branch: lp:~piastucki/bzr-xmloutput/fix-auth
Merge into: lp:bzr-xmloutput
Diff against target: 230 lines (+161/-9)
5 files modified
service.py (+17/-9)
tests/__init__.py (+2/-0)
tests/test_auth_service.py (+68/-0)
tests/test_uifactory.py (+23/-0)
uifactory.py (+51/-0)
To merge this branch: bzr merge lp:~piastucki/bzr-xmloutput/fix-auth
Reviewer Review Type Date Requested Status
Guillermo Gonzalez Approve
Review via email: mp+150190@code.launchpad.net

This proposal supersedes a proposal from 2013-02-16.

Description of the change

This commit introduces a custom UIFactory that handles get_password() and get_username() to avoid XMLRPC server hanging on waiting for user's input.
When 'run_bzr' function is invoked ValueError will be thrown whenever get_password() or get_username() is called to avoid server hang and to allow the client to ask the user for credentials.
Additionally, a new function 'run_bzr_auth' was added to make it possible to pass username and password to be used instead of throwing the error.

To post a comment you must log in.
Revision history for this message
Guillermo Gonzalez (verterok) wrote : Posted in a previous version of this proposal

The changes looks good.
Could you add some tests checking the behavior of the UIFactory? Also, please could you add tests for at least the 2 cases with the new "auth" command?

Thanks.

review: Needs Fixing
Revision history for this message
Piotr Piastucki (piastucki) wrote :

The UIFactory tests are pretty straightforward, however, in order to test new authentication mechanism an FTP/SSH account is needed. I used a publicly available test account on a public FTP server - ftp.secureftp-test.com which has been up&running for a couple of year, but the test will stop working when it is shut down.

Revision history for this message
Guillermo Gonzalez (verterok) wrote :

Thanks, but we can't use an external service in the tests.

I'll mark this as approved and change the tests in a different branch.

Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'service.py'
2--- service.py 2011-12-12 15:11:38 +0000
3+++ service.py 2013-02-23 13:29:21 +0000
4@@ -134,20 +134,27 @@
5 @redirect_output
6 def run_bzr(argv, workdir):
7 """run a regular bzr command"""
8- return _run_bzr(argv, workdir, commands.main)
9+ return _run_bzr(argv, workdir, False)
10
11
12 @redirect_output
13 def run_bzr_xml(argv, workdir):
14 """run a bzr command, but handle errors using XMLError"""
15- return _run_bzr(argv, workdir, custom_commands_main)
16-
17-
18-def _run_bzr(argv, workdir, func):
19+ return _run_bzr(argv, workdir, True)
20+
21+@redirect_output
22+def run_bzr_xml_auth(argv, workdir, username=None, password=None):
23+ """run a bzr command, but handle errors using XMLError"""
24+ return _run_bzr(argv, workdir, True, username, password)
25+
26+def _run_bzr(argv, workdir, iscustom, username=None, password=None):
27 """Actually executes the command and build the response."""
28 try:
29 os.chdir(workdir)
30- exitval = func(argv)
31+ if iscustom:
32+ exitval = custom_commands_main(argv, username, password)
33+ else:
34+ exitval = commands.main(argv)
35 sys.stderr.flush()
36 sys.stdout.flush()
37 if isinstance(exitval, Fault):
38@@ -165,11 +172,11 @@
39 raise
40
41
42-def custom_commands_main(argv):
43+def custom_commands_main(argv, username=None, password=None):
44 """custom commands.main that handle errors using XMLError"""
45 import bzrlib.ui
46- bzrlib.ui.ui_factory = bzrlib.ui.make_ui_for_terminal(
47- sys.stdin, sys.stdout, sys.stderr)
48+ from uifactory import AuthenticatingUIFactory
49+ bzrlib.ui.ui_factory = AuthenticatingUIFactory(sys.stdin, sys.stdout, sys.stderr, username, password)
50 try:
51 _argv = []
52 for a in argv[1:]:
53@@ -190,6 +197,7 @@
54 """register functions exposed via xmlrpc."""
55 server.register_function(run_bzr, 'run_bzr_command')
56 server.register_function(run_bzr_xml, 'run_bzr')
57+ server.register_function(run_bzr_xml_auth, 'run_bzr_auth')
58 import search
59 if search.is_available:
60 server.register_function(search.search, 'search')
61
62=== modified file 'tests/__init__.py'
63--- tests/__init__.py 2013-02-16 23:15:33 +0000
64+++ tests/__init__.py 2013-02-23 13:29:21 +0000
65@@ -33,6 +33,8 @@
66 'test_info_xml',
67 'test_service',
68 'test_tags_xml',
69+ 'test_auth_service',
70+ 'test_uifactory',
71 ]
72 basic_tests.addTest(loader.loadTestsFromModuleNames(
73 ["%s.%s" % (__name__, tmn) for tmn in testmod_names]))
74
75=== added file 'tests/test_auth_service.py'
76--- tests/test_auth_service.py 1970-01-01 00:00:00 +0000
77+++ tests/test_auth_service.py 2013-02-23 13:29:21 +0000
78@@ -0,0 +1,68 @@
79+# -*- encoding: utf-8 -*-
80+
81+import xmlrpclib
82+import threading
83+
84+from bzrlib import (
85+ tests,
86+ ui,
87+ )
88+
89+from bzrlib.plugins.xmloutput.service import *
90+
91+class TestXmlRpcServerAuth(tests.TestCase):
92+
93+ def setUp(self):
94+ tests.TestCase.setUp(self)
95+ self.host = 'localhost'
96+ self.port = 0
97+ self._start_server()
98+ self.client = xmlrpclib.Server("http://%s:%s" % (self.host,
99+ str(self.port)))
100+ def _start_server(self):
101+ self.server = BzrXMLRPCServer((self.host, self.port))
102+ self.thread = threading.Thread(target=self.server.serve_forever)
103+ self.thread.setDaemon(True)
104+ self.thread.start()
105+ self.host, self.port = self.server.socket.getsockname()
106+
107+ def tearDown(self):
108+ response = self.client.quit()
109+ self.thread.join()
110+ tests.TestCase.tearDown(self)
111+
112+ def test_hello(self):
113+ response = self.client.hello()
114+ self.assertEquals(response, "world!")
115+
116+ def test_run_bzr_auth(self):
117+ self.disable_directory_isolation()
118+ exit, out, err = self.client.run_bzr_auth(['bzr', 'xmlplugins'], '.')
119+ self.assertEquals(exit, 0)
120+ self.assertEquals(err, "")
121+ self.assertNotEquals(out.data, "")
122+
123+ def test_error_instead_of_hanging(self):
124+ self.disable_directory_isolation()
125+ exit, out, err = self.client.run_bzr(['bzr', 'xmlls', 'ftp://test@ftp.secureftp-test.com'], '.')
126+ self.assertNotEquals(exit, 0)
127+ self.assertContainsRe(err, "XMLRPC authentication password:")
128+ self.assertNotEquals(out.data, "")
129+ exit, out, err = self.client.run_bzr_auth(['bzr', 'xmlls', 'ftp://test@ftp.secureftp-test.com'], '.')
130+ self.assertNotEquals(exit, 0)
131+ self.assertContainsRe(err, "XMLRPC authentication password:")
132+ self.assertNotEquals(out.data, "")
133+
134+ def test_successful_authentication_flow(self):
135+ self.disable_directory_isolation()
136+ exit, out, err = self.client.run_bzr(['bzr', 'xmlls', 'ftp://test@ftp.secureftp-test.com'], '.')
137+ self.assertNotEquals(exit, 0)
138+ self.assertContainsRe(err, "XMLRPC authentication password:")
139+ self.assertNotEquals(out.data, "")
140+ exit, out, err = self.client.run_bzr_auth(['bzr', 'xmlls', 'ftp://test@ftp.secureftp-test.com'], '.',\
141+ 'test', 'test')
142+ self.assertNotEquals(exit, 0)
143+ self.assertContainsRe(err, "Not a branch")
144+ self.assertNotEquals(out.data, "")
145+
146+
147
148=== added file 'tests/test_uifactory.py'
149--- tests/test_uifactory.py 1970-01-01 00:00:00 +0000
150+++ tests/test_uifactory.py 2013-02-23 13:29:21 +0000
151@@ -0,0 +1,23 @@
152+# -*- encoding: utf-8 -*-
153+
154+import sys
155+from bzrlib import (
156+ tests,
157+ ui,
158+ )
159+from bzrlib.plugins.xmloutput.uifactory import AuthenticatingUIFactory
160+
161+class TestAuthenticatingUIFactory(tests.TestCase):
162+
163+ def test_error_throwing(self):
164+ uif = AuthenticatingUIFactory(sys.stdin, sys.stdout, sys.stderr)
165+ self.assertRaises(ValueError, uif.get_username, ('login'))
166+ self.assertRaises(ValueError, uif.get_password, ('password'))
167+
168+ def test_canned_values(self):
169+ uif = AuthenticatingUIFactory(sys.stdin, sys.stdout, sys.stderr, username='user123', password='pass123')
170+ t = uif.get_username('login')
171+ self.assertEquals(t, 'user123')
172+ t = uif.get_password('password')
173+ self.assertEquals(t, 'pass123')
174+
175
176=== added file 'uifactory.py'
177--- uifactory.py 1970-01-01 00:00:00 +0000
178+++ uifactory.py 2013-02-23 13:29:21 +0000
179@@ -0,0 +1,51 @@
180+#!/usr/bin/env python
181+# -*- coding: utf-8 -*-
182+#
183+# Copyright (C) 2013 Piotr Piastucki
184+#
185+# This program is free software; you can redistribute it and/or
186+# modify it under the terms of the GNU General Public License
187+# as published by the Free Software Foundation; either version 2
188+# of the License, or (at your option) any later version.
189+#
190+# This program is distributed in the hope that it will be useful,
191+# but WITHOUT ANY WARRANTY; without even the implied warranty of
192+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
193+# GNU General Public License for more details.
194+#
195+# You should have received a copy of the GNU General Public License
196+# along with this program; if not, write to the Free Software
197+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
198+
199+from bzrlib import ui
200+from bzrlib.ui.text import TextUIFactory
201+
202+class AuthenticatingUIFactory(TextUIFactory):
203+
204+ def __init__(self,
205+ stdin=None,
206+ stdout=None,
207+ stderr=None,
208+ username=None,
209+ password=None):
210+ super(AuthenticatingUIFactory, self).__init__(stdin, stdout, stderr)
211+ self.username = username
212+ self.password = password
213+
214+ def get_prompt_text(self, prompt=u'', **kwargs):
215+ if type(prompt) != unicode:
216+ raise ValueError("prompt %r not a unicode string" % prompt)
217+ if kwargs:
218+ prompt = prompt % kwargs
219+ return prompt
220+
221+ def get_password(self, prompt=u'', **kwargs):
222+ if self.password is None:
223+ raise ValueError('XMLRPC authentication password:' + self.get_prompt_text(prompt, **kwargs))
224+ return self.password
225+
226+ def get_username(self, prompt, **kwargs):
227+ if self.username is None:
228+ raise ValueError('XMLRPC authentication username:' + self.get_prompt_text(prompt, **kwargs))
229+ return self.username
230+

Subscribers

People subscribed via source and target branches

to all changes: