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
=== modified file 'service.py'
--- service.py 2011-12-12 15:11:38 +0000
+++ service.py 2013-02-23 13:29:21 +0000
@@ -134,20 +134,27 @@
134@redirect_output134@redirect_output
135def run_bzr(argv, workdir):135def run_bzr(argv, workdir):
136 """run a regular bzr command"""136 """run a regular bzr command"""
137 return _run_bzr(argv, workdir, commands.main)137 return _run_bzr(argv, workdir, False)
138138
139139
140@redirect_output140@redirect_output
141def run_bzr_xml(argv, workdir):141def run_bzr_xml(argv, workdir):
142 """run a bzr command, but handle errors using XMLError"""142 """run a bzr command, but handle errors using XMLError"""
143 return _run_bzr(argv, workdir, custom_commands_main)143 return _run_bzr(argv, workdir, True)
144144
145145@redirect_output
146def _run_bzr(argv, workdir, func):146def run_bzr_xml_auth(argv, workdir, username=None, password=None):
147 """run a bzr command, but handle errors using XMLError"""
148 return _run_bzr(argv, workdir, True, username, password)
149
150def _run_bzr(argv, workdir, iscustom, username=None, password=None):
147 """Actually executes the command and build the response."""151 """Actually executes the command and build the response."""
148 try:152 try:
149 os.chdir(workdir)153 os.chdir(workdir)
150 exitval = func(argv)154 if iscustom:
155 exitval = custom_commands_main(argv, username, password)
156 else:
157 exitval = commands.main(argv)
151 sys.stderr.flush()158 sys.stderr.flush()
152 sys.stdout.flush()159 sys.stdout.flush()
153 if isinstance(exitval, Fault):160 if isinstance(exitval, Fault):
@@ -165,11 +172,11 @@
165 raise172 raise
166173
167174
168def custom_commands_main(argv):175def custom_commands_main(argv, username=None, password=None):
169 """custom commands.main that handle errors using XMLError"""176 """custom commands.main that handle errors using XMLError"""
170 import bzrlib.ui177 import bzrlib.ui
171 bzrlib.ui.ui_factory = bzrlib.ui.make_ui_for_terminal(178 from uifactory import AuthenticatingUIFactory
172 sys.stdin, sys.stdout, sys.stderr)179 bzrlib.ui.ui_factory = AuthenticatingUIFactory(sys.stdin, sys.stdout, sys.stderr, username, password)
173 try:180 try:
174 _argv = []181 _argv = []
175 for a in argv[1:]:182 for a in argv[1:]:
@@ -190,6 +197,7 @@
190 """register functions exposed via xmlrpc."""197 """register functions exposed via xmlrpc."""
191 server.register_function(run_bzr, 'run_bzr_command')198 server.register_function(run_bzr, 'run_bzr_command')
192 server.register_function(run_bzr_xml, 'run_bzr')199 server.register_function(run_bzr_xml, 'run_bzr')
200 server.register_function(run_bzr_xml_auth, 'run_bzr_auth')
193 import search201 import search
194 if search.is_available:202 if search.is_available:
195 server.register_function(search.search, 'search')203 server.register_function(search.search, 'search')
196204
=== modified file 'tests/__init__.py'
--- tests/__init__.py 2013-02-16 23:15:33 +0000
+++ tests/__init__.py 2013-02-23 13:29:21 +0000
@@ -33,6 +33,8 @@
33 'test_info_xml',33 'test_info_xml',
34 'test_service',34 'test_service',
35 'test_tags_xml',35 'test_tags_xml',
36 'test_auth_service',
37 'test_uifactory',
36 ]38 ]
37 basic_tests.addTest(loader.loadTestsFromModuleNames(39 basic_tests.addTest(loader.loadTestsFromModuleNames(
38 ["%s.%s" % (__name__, tmn) for tmn in testmod_names]))40 ["%s.%s" % (__name__, tmn) for tmn in testmod_names]))
3941
=== added file 'tests/test_auth_service.py'
--- tests/test_auth_service.py 1970-01-01 00:00:00 +0000
+++ tests/test_auth_service.py 2013-02-23 13:29:21 +0000
@@ -0,0 +1,68 @@
1# -*- encoding: utf-8 -*-
2
3import xmlrpclib
4import threading
5
6from bzrlib import (
7 tests,
8 ui,
9 )
10
11from bzrlib.plugins.xmloutput.service import *
12
13class TestXmlRpcServerAuth(tests.TestCase):
14
15 def setUp(self):
16 tests.TestCase.setUp(self)
17 self.host = 'localhost'
18 self.port = 0
19 self._start_server()
20 self.client = xmlrpclib.Server("http://%s:%s" % (self.host,
21 str(self.port)))
22 def _start_server(self):
23 self.server = BzrXMLRPCServer((self.host, self.port))
24 self.thread = threading.Thread(target=self.server.serve_forever)
25 self.thread.setDaemon(True)
26 self.thread.start()
27 self.host, self.port = self.server.socket.getsockname()
28
29 def tearDown(self):
30 response = self.client.quit()
31 self.thread.join()
32 tests.TestCase.tearDown(self)
33
34 def test_hello(self):
35 response = self.client.hello()
36 self.assertEquals(response, "world!")
37
38 def test_run_bzr_auth(self):
39 self.disable_directory_isolation()
40 exit, out, err = self.client.run_bzr_auth(['bzr', 'xmlplugins'], '.')
41 self.assertEquals(exit, 0)
42 self.assertEquals(err, "")
43 self.assertNotEquals(out.data, "")
44
45 def test_error_instead_of_hanging(self):
46 self.disable_directory_isolation()
47 exit, out, err = self.client.run_bzr(['bzr', 'xmlls', 'ftp://test@ftp.secureftp-test.com'], '.')
48 self.assertNotEquals(exit, 0)
49 self.assertContainsRe(err, "XMLRPC authentication password:")
50 self.assertNotEquals(out.data, "")
51 exit, out, err = self.client.run_bzr_auth(['bzr', 'xmlls', 'ftp://test@ftp.secureftp-test.com'], '.')
52 self.assertNotEquals(exit, 0)
53 self.assertContainsRe(err, "XMLRPC authentication password:")
54 self.assertNotEquals(out.data, "")
55
56 def test_successful_authentication_flow(self):
57 self.disable_directory_isolation()
58 exit, out, err = self.client.run_bzr(['bzr', 'xmlls', 'ftp://test@ftp.secureftp-test.com'], '.')
59 self.assertNotEquals(exit, 0)
60 self.assertContainsRe(err, "XMLRPC authentication password:")
61 self.assertNotEquals(out.data, "")
62 exit, out, err = self.client.run_bzr_auth(['bzr', 'xmlls', 'ftp://test@ftp.secureftp-test.com'], '.',\
63 'test', 'test')
64 self.assertNotEquals(exit, 0)
65 self.assertContainsRe(err, "Not a branch")
66 self.assertNotEquals(out.data, "")
67
68
069
=== added file 'tests/test_uifactory.py'
--- tests/test_uifactory.py 1970-01-01 00:00:00 +0000
+++ tests/test_uifactory.py 2013-02-23 13:29:21 +0000
@@ -0,0 +1,23 @@
1# -*- encoding: utf-8 -*-
2
3import sys
4from bzrlib import (
5 tests,
6 ui,
7 )
8from bzrlib.plugins.xmloutput.uifactory import AuthenticatingUIFactory
9
10class TestAuthenticatingUIFactory(tests.TestCase):
11
12 def test_error_throwing(self):
13 uif = AuthenticatingUIFactory(sys.stdin, sys.stdout, sys.stderr)
14 self.assertRaises(ValueError, uif.get_username, ('login'))
15 self.assertRaises(ValueError, uif.get_password, ('password'))
16
17 def test_canned_values(self):
18 uif = AuthenticatingUIFactory(sys.stdin, sys.stdout, sys.stderr, username='user123', password='pass123')
19 t = uif.get_username('login')
20 self.assertEquals(t, 'user123')
21 t = uif.get_password('password')
22 self.assertEquals(t, 'pass123')
23
024
=== added file 'uifactory.py'
--- uifactory.py 1970-01-01 00:00:00 +0000
+++ uifactory.py 2013-02-23 13:29:21 +0000
@@ -0,0 +1,51 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Copyright (C) 2013 Piotr Piastucki
5#
6# This program is free software; you can redistribute it and/or
7# modify it under the terms of the GNU General Public License
8# as published by the Free Software Foundation; either version 2
9# of the License, or (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
20from bzrlib import ui
21from bzrlib.ui.text import TextUIFactory
22
23class AuthenticatingUIFactory(TextUIFactory):
24
25 def __init__(self,
26 stdin=None,
27 stdout=None,
28 stderr=None,
29 username=None,
30 password=None):
31 super(AuthenticatingUIFactory, self).__init__(stdin, stdout, stderr)
32 self.username = username
33 self.password = password
34
35 def get_prompt_text(self, prompt=u'', **kwargs):
36 if type(prompt) != unicode:
37 raise ValueError("prompt %r not a unicode string" % prompt)
38 if kwargs:
39 prompt = prompt % kwargs
40 return prompt
41
42 def get_password(self, prompt=u'', **kwargs):
43 if self.password is None:
44 raise ValueError('XMLRPC authentication password:' + self.get_prompt_text(prompt, **kwargs))
45 return self.password
46
47 def get_username(self, prompt, **kwargs):
48 if self.username is None:
49 raise ValueError('XMLRPC authentication username:' + self.get_prompt_text(prompt, **kwargs))
50 return self.username
51

Subscribers

People subscribed via source and target branches

to all changes: