~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Robey Pointer
  • Date: 2006-07-12 16:29:39 UTC
  • mto: This revision was merged to the branch mainline in revision 1861.
  • Revision ID: robey@lag.net-20060712162939-7d27143c632e3b3a
change the warning message for a 'whoami' with no email address, on jam's recommendation

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
 
1
# Copyright (C) 2006 by 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
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
 
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
from getpass import getpass
18
19
import os
19
 
import socket
20
20
from urlparse import urlsplit, urlunsplit
21
21
import urllib
22
22
import xmlrpclib
23
23
 
24
 
from bzrlib.lazy_import import lazy_import
25
 
lazy_import(globals(), """
26
 
from bzrlib import urlutils
27
 
""")
28
 
 
29
 
from bzrlib import (
30
 
    config,
31
 
    errors,
32
 
    __version__ as _bzrlib_version,
33
 
    )
 
24
import bzrlib.config
 
25
import bzrlib.errors as errors
34
26
 
35
27
# for testing, do
36
28
'''
37
29
export BZR_LP_XMLRPC_URL=http://xmlrpc.staging.launchpad.net/bazaar/
38
30
'''
39
31
 
40
 
class InvalidLaunchpadInstance(errors.BzrError):
41
 
 
42
 
    _fmt = "%(lp_instance)s is not a valid Launchpad instance."
43
 
 
44
 
    def __init__(self, lp_instance):
45
 
        errors.BzrError.__init__(self, lp_instance=lp_instance)
46
 
 
47
 
 
48
 
class NotLaunchpadBranch(errors.BzrError):
49
 
 
50
 
    _fmt = "%(url)s is not registered on Launchpad."
51
 
 
52
 
    def __init__(self, url):
53
 
        errors.BzrError.__init__(self, url=url)
54
 
 
55
 
 
56
32
class LaunchpadService(object):
57
33
    """A service to talk to Launchpad via XMLRPC.
58
 
 
 
34
    
59
35
    See http://bazaar-vcs.org/Specs/LaunchpadRpc for the methods we can call.
60
36
    """
61
37
 
62
 
    LAUNCHPAD_DOMAINS = {
63
 
        'production': 'launchpad.net',
64
 
        'edge': 'edge.launchpad.net',
65
 
        'staging': 'staging.launchpad.net',
66
 
        'demo': 'demo.launchpad.net',
67
 
        'dev': 'launchpad.dev',
68
 
        }
69
 
 
70
 
    # NB: these should always end in a slash to avoid xmlrpclib appending
 
38
    # NB: this should always end in a slash to avoid xmlrpclib appending
71
39
    # '/RPC2'
72
 
    LAUNCHPAD_INSTANCE = {}
73
 
    for instance, domain in LAUNCHPAD_DOMAINS.iteritems():
74
 
        LAUNCHPAD_INSTANCE[instance] = 'https://xmlrpc.%s/bazaar/' % domain
75
 
 
76
 
    # We use edge as the default because:
77
 
    # Beta users get redirected to it
78
 
    # All users can use it
79
 
    # There is a bug in the launchpad side where redirection causes an OOPS.
80
 
    DEFAULT_INSTANCE = 'edge'
81
 
    DEFAULT_SERVICE_URL = LAUNCHPAD_INSTANCE[DEFAULT_INSTANCE]
 
40
    DEFAULT_SERVICE_URL = 'https://xmlrpc.launchpad.net/bazaar/'
82
41
 
83
42
    transport = None
84
43
    registrant_email = None
85
44
    registrant_password = None
86
45
 
87
46
 
88
 
    def __init__(self, transport=None, lp_instance=None):
 
47
    def __init__(self, transport=None):
89
48
        """Construct a new service talking to the launchpad rpc server"""
90
 
        self._lp_instance = lp_instance
91
49
        if transport is None:
92
50
            uri_type = urllib.splittype(self.service_url)[0]
93
51
            if uri_type == 'https':
95
53
            else:
96
54
                transport = xmlrpclib.Transport()
97
55
            transport.user_agent = 'bzr/%s (xmlrpclib/%s)' \
98
 
                    % (_bzrlib_version, xmlrpclib.__version__)
 
56
                    % (bzrlib.__version__, xmlrpclib.__version__)
99
57
        self.transport = transport
100
58
 
101
59
 
108
66
        key = 'BZR_LP_XMLRPC_URL'
109
67
        if key in os.environ:
110
68
            return os.environ[key]
111
 
        elif self._lp_instance is not None:
112
 
            try:
113
 
                return self.LAUNCHPAD_INSTANCE[self._lp_instance]
114
 
            except KeyError:
115
 
                raise InvalidLaunchpadInstance(self._lp_instance)
116
69
        else:
117
70
            return self.DEFAULT_SERVICE_URL
118
71
 
119
 
    def get_proxy(self, authenticated):
 
72
    def get_proxy(self):
120
73
        """Return the proxy for XMLRPC requests."""
121
 
        if authenticated:
122
 
            # auth info must be in url
123
 
            # TODO: if there's no registrant email perhaps we should
124
 
            # just connect anonymously?
125
 
            scheme, hostinfo, path = urlsplit(self.service_url)[:3]
126
 
            if '@' in hostinfo:
127
 
                raise AssertionError(hostinfo)
128
 
            if self.registrant_email is None:
129
 
                raise AssertionError()
130
 
            if self.registrant_password is None:
131
 
                raise AssertionError()
132
 
            # TODO: perhaps fully quote the password to make it very slightly
133
 
            # obscured
134
 
            # TODO: can we perhaps add extra Authorization headers
135
 
            # directly to the request, rather than putting this into
136
 
            # the url?  perhaps a bit more secure against accidentally
137
 
            # revealing it.  std66 s3.2.1 discourages putting the
138
 
            # password in the url.
139
 
            hostinfo = '%s:%s@%s' % (urllib.quote(self.registrant_email),
140
 
                                     urllib.quote(self.registrant_password),
141
 
                                     hostinfo)
142
 
            url = urlunsplit((scheme, hostinfo, path, '', ''))
143
 
        else:
144
 
            url = self.service_url
 
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, '', ''))
145
91
        return xmlrpclib.ServerProxy(url, transport=self.transport)
146
92
 
147
93
    def gather_user_credentials(self):
148
94
        """Get the password from the user."""
149
 
        the_config = config.GlobalConfig()
150
 
        self.registrant_email = the_config.user_email()
 
95
        config = bzrlib.config.GlobalConfig()
 
96
        self.registrant_email = config.user_email()
151
97
        if self.registrant_password is None:
152
 
            auth = config.AuthenticationConfig()
153
 
            scheme, hostinfo = urlsplit(self.service_url)[:2]
154
98
            prompt = 'launchpad.net password for %s: ' % \
155
99
                    self.registrant_email
156
 
            # We will reuse http[s] credentials if we can, prompt user
157
 
            # otherwise
158
 
            self.registrant_password = auth.get_password(scheme, hostinfo,
159
 
                                                         self.registrant_email,
160
 
                                                         prompt=prompt)
 
100
            self.registrant_password = getpass(prompt)
161
101
 
162
 
    def send_request(self, method_name, method_params, authenticated):
163
 
        proxy = self.get_proxy(authenticated)
 
102
    def send_request(self, method_name, method_params):
 
103
        proxy = self.get_proxy()
 
104
        assert method_name
164
105
        method = getattr(proxy, method_name)
165
106
        try:
166
107
            result = method(*method_params)
177
118
                # TODO: print more headers to help in tracking down failures
178
119
                raise errors.BzrError("xmlrpc protocol error connecting to %s: %s %s"
179
120
                        % (self.service_url, e.errcode, e.errmsg))
180
 
        except socket.gaierror, e:
181
 
            raise errors.ConnectionError(
182
 
                "Could not resolve '%s'" % self.domain,
183
 
                orig_error=e)
184
121
        return result
185
122
 
186
 
    @property
187
 
    def domain(self):
188
 
        if self._lp_instance is None:
189
 
            instance = self.DEFAULT_INSTANCE
190
 
        else:
191
 
            instance = self._lp_instance
192
 
        return self.LAUNCHPAD_DOMAINS[instance]
193
 
 
194
 
    def get_web_url_from_branch_url(self, branch_url, _request_factory=None):
195
 
        """Get the Launchpad web URL for the given branch URL.
196
 
 
197
 
        :raise errors.InvalidURL: if 'branch_url' cannot be identified as a
198
 
            Launchpad branch URL.
199
 
        :return: The URL of the branch on Launchpad.
200
 
        """
201
 
        scheme, hostinfo, path = urlsplit(branch_url)[:3]
202
 
        if _request_factory is None:
203
 
            _request_factory = ResolveLaunchpadPathRequest
204
 
        if scheme == 'lp':
205
 
            resolve = _request_factory(path)
206
 
            try:
207
 
                result = resolve.submit(self)
208
 
            except xmlrpclib.Fault, fault:
209
 
                raise errors.InvalidURL(branch_url, str(fault))
210
 
            branch_url = result['urls'][0]
211
 
            path = urlsplit(branch_url)[2]
212
 
        else:
213
 
            domains = (
214
 
                'bazaar.%s' % domain
215
 
                for domain in self.LAUNCHPAD_DOMAINS.itervalues())
216
 
            if hostinfo not in domains:
217
 
                raise NotLaunchpadBranch(branch_url)
218
 
        return urlutils.join('https://code.%s' % self.domain, path)
219
 
 
220
123
 
221
124
class BaseRequest(object):
222
125
    """Base request for talking to a XMLRPC server."""
223
126
 
224
127
    # Set this to the XMLRPC method name.
225
128
    _methodname = None
226
 
    _authenticated = True
227
129
 
228
130
    def _request_params(self):
229
131
        """Return the arguments to pass to the method"""
235
137
        :param service: LaunchpadService indicating where to send
236
138
            the request and the authentication credentials.
237
139
        """
238
 
        return service.send_request(self._methodname, self._request_params(),
239
 
                                    self._authenticated)
 
140
        return service.send_request(self._methodname, self._request_params())
240
141
 
241
142
 
242
143
class DryRunLaunchpadService(LaunchpadService):
243
144
    """Service that just absorbs requests without sending to server.
244
 
 
 
145
    
245
146
    The dummy service does not need authentication.
246
147
    """
247
148
 
248
 
    def send_request(self, method_name, method_params, authenticated):
 
149
    def send_request(self, method_name, method_params):
249
150
        pass
250
151
 
251
152
    def gather_user_credentials(self):
264
165
                 author_email='',
265
166
                 product_name='',
266
167
                 ):
267
 
        if not branch_url:
268
 
            raise errors.InvalidURL(branch_url, "You need to specify a non-empty branch URL.")
 
168
        assert branch_url
269
169
        self.branch_url = branch_url
270
170
        if branch_name:
271
171
            self.branch_name = branch_name
299
199
    _methodname = 'link_branch_to_bug'
300
200
 
301
201
    def __init__(self, branch_url, bug_id):
 
202
        assert branch_url
302
203
        self.bug_id = bug_id
303
204
        self.branch_url = branch_url
304
205
 
307
208
        # This must match the parameter tuple expected by Launchpad for this
308
209
        # method
309
210
        return (self.branch_url, self.bug_id, '')
310
 
 
311
 
 
312
 
class ResolveLaunchpadPathRequest(BaseRequest):
313
 
    """Request to resolve the path component of an lp: URL."""
314
 
 
315
 
    _methodname = 'resolve_lp_path'
316
 
    _authenticated = False
317
 
 
318
 
    def __init__(self, path):
319
 
        if not path:
320
 
            raise errors.InvalidURL(path=path,
321
 
                                    extra="You must specify a project.")
322
 
        self.path = path
323
 
 
324
 
    def _request_params(self):
325
 
        """Return xmlrpc request parameters"""
326
 
        return (self.path,)