~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/plugins/launchpad/lp_registration.py

  • Committer: Andrew Bennetts
  • Date: 2008-09-05 10:48:03 UTC
  • mto: This revision was merged to the branch mainline in revision 3693.
  • Revision ID: andrew.bennetts@canonical.com-20080905104803-6g72dz6wcldosfs2
Remove monkey-patching of branch._ensure_real from test_remote.py.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 by Canonical Ltd
 
1
# Copyright (C) 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
7
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
11
 
# # GNU General Public License for more details.
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
21
21
import urllib
22
22
import xmlrpclib
23
23
 
24
 
import bzrlib.config
25
 
import bzrlib.errors as errors
 
24
from bzrlib import (
 
25
    config,
 
26
    errors,
 
27
    __version__ as _bzrlib_version,
 
28
    )
26
29
 
27
30
# for testing, do
28
31
'''
29
32
export BZR_LP_XMLRPC_URL=http://xmlrpc.staging.launchpad.net/bazaar/
30
33
'''
31
34
 
 
35
class InvalidLaunchpadInstance(errors.BzrError):
 
36
 
 
37
    _fmt = "%(lp_instance)s is not a valid Launchpad instance."
 
38
 
 
39
    def __init__(self, lp_instance):
 
40
        errors.BzrError.__init__(self, lp_instance=lp_instance)
 
41
 
 
42
 
32
43
class LaunchpadService(object):
33
44
    """A service to talk to Launchpad via XMLRPC.
34
 
    
 
45
 
35
46
    See http://bazaar-vcs.org/Specs/LaunchpadRpc for the methods we can call.
36
47
    """
37
48
 
38
 
    # NB: this should always end in a slash to avoid xmlrpclib appending
 
49
    # NB: these should always end in a slash to avoid xmlrpclib appending
39
50
    # '/RPC2'
40
 
    DEFAULT_SERVICE_URL = 'https://xmlrpc.launchpad.net/bazaar/'
 
51
    # We use edge as the default because:
 
52
    # Beta users get redirected to it
 
53
    # All users can use it
 
54
    # There is a bug in the launchpad side where redirection causes an OOPS.
 
55
    LAUNCHPAD_INSTANCE = {
 
56
        'production': 'https://xmlrpc.launchpad.net/bazaar/',
 
57
        'edge': 'https://xmlrpc.edge.launchpad.net/bazaar/',
 
58
        'staging': 'https://xmlrpc.staging.launchpad.net/bazaar/',
 
59
        'demo': 'https://xmlrpc.demo.launchpad.net/bazaar/',
 
60
        'dev': 'http://xmlrpc.launchpad.dev/bazaar/',
 
61
        }
 
62
    DEFAULT_SERVICE_URL = LAUNCHPAD_INSTANCE['edge']
41
63
 
42
64
    transport = None
43
65
    registrant_email = None
44
66
    registrant_password = None
45
67
 
46
68
 
47
 
    def __init__(self, transport=None):
 
69
    def __init__(self, transport=None, lp_instance=None):
48
70
        """Construct a new service talking to the launchpad rpc server"""
 
71
        self._lp_instance = lp_instance
49
72
        if transport is None:
50
73
            uri_type = urllib.splittype(self.service_url)[0]
51
74
            if uri_type == 'https':
53
76
            else:
54
77
                transport = xmlrpclib.Transport()
55
78
            transport.user_agent = 'bzr/%s (xmlrpclib/%s)' \
56
 
                    % (bzrlib.__version__, xmlrpclib.__version__)
 
79
                    % (_bzrlib_version, xmlrpclib.__version__)
57
80
        self.transport = transport
58
81
 
59
82
 
66
89
        key = 'BZR_LP_XMLRPC_URL'
67
90
        if key in os.environ:
68
91
            return os.environ[key]
 
92
        elif self._lp_instance is not None:
 
93
            try:
 
94
                return self.LAUNCHPAD_INSTANCE[self._lp_instance]
 
95
            except KeyError:
 
96
                raise InvalidLaunchpadInstance(self._lp_instance)
69
97
        else:
70
98
            return self.DEFAULT_SERVICE_URL
71
99
 
72
 
    def get_proxy(self):
 
100
    def get_proxy(self, authenticated):
73
101
        """Return the proxy for XMLRPC requests."""
74
 
        # auth info must be in url
75
 
        # TODO: if there's no registrant email perhaps we should just connect
76
 
        # anonymously?
77
 
        scheme, hostinfo, path = urlsplit(self.service_url)[:3]
78
 
        assert '@' not in hostinfo
79
 
        assert self.registrant_email is not None
80
 
        assert self.registrant_password is not None
81
 
        # TODO: perhaps fully quote the password to make it very slightly
82
 
        # obscured
83
 
        # TODO: can we perhaps add extra Authorization headers directly to the 
84
 
        # request, rather than putting this into the url?  perhaps a bit more 
85
 
        # secure against accidentally revealing it.  std66 s3.2.1 discourages putting
86
 
        # the password in the url.
87
 
        hostinfo = '%s:%s@%s' % (urllib.quote(self.registrant_email),
88
 
                                 urllib.quote(self.registrant_password),
89
 
                                 hostinfo)
90
 
        url = urlunsplit((scheme, hostinfo, path, '', ''))
 
102
        if authenticated:
 
103
            # auth info must be in url
 
104
            # TODO: if there's no registrant email perhaps we should
 
105
            # just connect anonymously?
 
106
            scheme, hostinfo, path = urlsplit(self.service_url)[:3]
 
107
            if '@' in hostinfo:
 
108
                raise AssertionError(hostinfo)
 
109
            if self.registrant_email is None:
 
110
                raise AssertionError()
 
111
            if self.registrant_password is None:
 
112
                raise AssertionError()
 
113
            # TODO: perhaps fully quote the password to make it very slightly
 
114
            # obscured
 
115
            # TODO: can we perhaps add extra Authorization headers
 
116
            # directly to the request, rather than putting this into
 
117
            # the url?  perhaps a bit more secure against accidentally
 
118
            # revealing it.  std66 s3.2.1 discourages putting the
 
119
            # password in the url.
 
120
            hostinfo = '%s:%s@%s' % (urllib.quote(self.registrant_email),
 
121
                                     urllib.quote(self.registrant_password),
 
122
                                     hostinfo)
 
123
            url = urlunsplit((scheme, hostinfo, path, '', ''))
 
124
        else:
 
125
            url = self.service_url
91
126
        return xmlrpclib.ServerProxy(url, transport=self.transport)
92
127
 
93
128
    def gather_user_credentials(self):
94
129
        """Get the password from the user."""
95
 
        config = bzrlib.config.GlobalConfig()
96
 
        self.registrant_email = config.user_email()
 
130
        the_config = config.GlobalConfig()
 
131
        self.registrant_email = the_config.user_email()
97
132
        if self.registrant_password is None:
 
133
            auth = config.AuthenticationConfig()
 
134
            scheme, hostinfo = urlsplit(self.service_url)[:2]
98
135
            prompt = 'launchpad.net password for %s: ' % \
99
136
                    self.registrant_email
100
 
            self.registrant_password = getpass(prompt)
 
137
            # We will reuse http[s] credentials if we can, prompt user
 
138
            # otherwise
 
139
            self.registrant_password = auth.get_password(scheme, hostinfo,
 
140
                                                         self.registrant_email,
 
141
                                                         prompt=prompt)
101
142
 
102
 
    def send_request(self, method_name, method_params):
103
 
        proxy = self.get_proxy()
104
 
        assert method_name
 
143
    def send_request(self, method_name, method_params, authenticated):
 
144
        proxy = self.get_proxy(authenticated)
105
145
        method = getattr(proxy, method_name)
106
146
        try:
107
147
            result = method(*method_params)
126
166
 
127
167
    # Set this to the XMLRPC method name.
128
168
    _methodname = None
 
169
    _authenticated = True
129
170
 
130
171
    def _request_params(self):
131
172
        """Return the arguments to pass to the method"""
137
178
        :param service: LaunchpadService indicating where to send
138
179
            the request and the authentication credentials.
139
180
        """
140
 
        return service.send_request(self._methodname, self._request_params())
 
181
        return service.send_request(self._methodname, self._request_params(),
 
182
                                    self._authenticated)
141
183
 
142
184
 
143
185
class DryRunLaunchpadService(LaunchpadService):
146
188
    The dummy service does not need authentication.
147
189
    """
148
190
 
149
 
    def send_request(self, method_name, method_params):
 
191
    def send_request(self, method_name, method_params, authenticated):
150
192
        pass
151
193
 
152
194
    def gather_user_credentials(self):
165
207
                 author_email='',
166
208
                 product_name='',
167
209
                 ):
168
 
        assert branch_url
 
210
        if not branch_url:
 
211
            raise errors.InvalidURL(branch_url, "You need to specify a non-empty branch URL.")
169
212
        self.branch_url = branch_url
170
213
        if branch_name:
171
214
            self.branch_name = branch_name
199
242
    _methodname = 'link_branch_to_bug'
200
243
 
201
244
    def __init__(self, branch_url, bug_id):
202
 
        assert branch_url
203
245
        self.bug_id = bug_id
204
246
        self.branch_url = branch_url
205
247
 
208
250
        # This must match the parameter tuple expected by Launchpad for this
209
251
        # method
210
252
        return (self.branch_url, self.bug_id, '')
 
253
 
 
254
 
 
255
class ResolveLaunchpadPathRequest(BaseRequest):
 
256
    """Request to resolve the path component of an lp: URL."""
 
257
 
 
258
    _methodname = 'resolve_lp_path'
 
259
    _authenticated = False
 
260
 
 
261
    def __init__(self, path):
 
262
        if not path:
 
263
            raise errors.InvalidURL(path=path,
 
264
                                    extra="You must specify a product.")
 
265
        self.path = path
 
266
 
 
267
    def _request_params(self):
 
268
        """Return xmlrpc request parameters"""
 
269
        return (self.path,)