~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: 2007-03-26 06:24:01 UTC
  • mto: This revision was merged to the branch mainline in revision 2376.
  • Revision ID: andrew.bennetts@canonical.com-20070326062401-k3nbefzje5332jaf
Deal with review comments from Robert:

  * Add my name to the NEWS file
  * Move the test case to a new module in branch_implementations
  * Remove revision_history cruft from identitymap and test_identitymap
  * Improve some docstrings

Also, this fixes a bug where revision_history was not returning a copy of the
cached data, allowing the cache to be corrupted.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
import urllib
22
22
import xmlrpclib
23
23
 
24
 
from bzrlib import (
25
 
    config,
26
 
    errors,
27
 
    __version__ as _bzrlib_version,
28
 
    )
 
24
import bzrlib.config
 
25
import bzrlib.errors as errors
29
26
 
30
27
# for testing, do
31
28
'''
32
29
export BZR_LP_XMLRPC_URL=http://xmlrpc.staging.launchpad.net/bazaar/
33
30
'''
34
31
 
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
 
 
43
32
class LaunchpadService(object):
44
33
    """A service to talk to Launchpad via XMLRPC.
45
 
 
 
34
    
46
35
    See http://bazaar-vcs.org/Specs/LaunchpadRpc for the methods we can call.
47
36
    """
48
37
 
49
 
    # 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
50
39
    # '/RPC2'
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']
 
40
    DEFAULT_SERVICE_URL = 'https://xmlrpc.launchpad.net/bazaar/'
63
41
 
64
42
    transport = None
65
43
    registrant_email = None
66
44
    registrant_password = None
67
45
 
68
46
 
69
 
    def __init__(self, transport=None, lp_instance=None):
 
47
    def __init__(self, transport=None):
70
48
        """Construct a new service talking to the launchpad rpc server"""
71
 
        self._lp_instance = lp_instance
72
49
        if transport is None:
73
50
            uri_type = urllib.splittype(self.service_url)[0]
74
51
            if uri_type == 'https':
76
53
            else:
77
54
                transport = xmlrpclib.Transport()
78
55
            transport.user_agent = 'bzr/%s (xmlrpclib/%s)' \
79
 
                    % (_bzrlib_version, xmlrpclib.__version__)
 
56
                    % (bzrlib.__version__, xmlrpclib.__version__)
80
57
        self.transport = transport
81
58
 
82
59
 
89
66
        key = 'BZR_LP_XMLRPC_URL'
90
67
        if key in os.environ:
91
68
            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)
97
69
        else:
98
70
            return self.DEFAULT_SERVICE_URL
99
71
 
100
 
    def get_proxy(self, authenticated):
 
72
    def get_proxy(self):
101
73
        """Return the proxy for XMLRPC requests."""
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
 
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, '', ''))
126
91
        return xmlrpclib.ServerProxy(url, transport=self.transport)
127
92
 
128
93
    def gather_user_credentials(self):
129
94
        """Get the password from the user."""
130
 
        the_config = config.GlobalConfig()
131
 
        self.registrant_email = the_config.user_email()
 
95
        config = bzrlib.config.GlobalConfig()
 
96
        self.registrant_email = config.user_email()
132
97
        if self.registrant_password is None:
133
 
            auth = config.AuthenticationConfig()
134
 
            scheme, hostinfo = urlsplit(self.service_url)[:2]
135
98
            prompt = 'launchpad.net password for %s: ' % \
136
99
                    self.registrant_email
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)
 
100
            self.registrant_password = getpass(prompt)
142
101
 
143
 
    def send_request(self, method_name, method_params, authenticated):
144
 
        proxy = self.get_proxy(authenticated)
 
102
    def send_request(self, method_name, method_params):
 
103
        proxy = self.get_proxy()
 
104
        assert method_name
145
105
        method = getattr(proxy, method_name)
146
106
        try:
147
107
            result = method(*method_params)
166
126
 
167
127
    # Set this to the XMLRPC method name.
168
128
    _methodname = None
169
 
    _authenticated = True
170
129
 
171
130
    def _request_params(self):
172
131
        """Return the arguments to pass to the method"""
178
137
        :param service: LaunchpadService indicating where to send
179
138
            the request and the authentication credentials.
180
139
        """
181
 
        return service.send_request(self._methodname, self._request_params(),
182
 
                                    self._authenticated)
 
140
        return service.send_request(self._methodname, self._request_params())
183
141
 
184
142
 
185
143
class DryRunLaunchpadService(LaunchpadService):
188
146
    The dummy service does not need authentication.
189
147
    """
190
148
 
191
 
    def send_request(self, method_name, method_params, authenticated):
 
149
    def send_request(self, method_name, method_params):
192
150
        pass
193
151
 
194
152
    def gather_user_credentials(self):
207
165
                 author_email='',
208
166
                 product_name='',
209
167
                 ):
210
 
        if not branch_url:
211
 
            raise errors.InvalidURL(branch_url, "You need to specify a non-empty branch URL.")
 
168
        assert branch_url
212
169
        self.branch_url = branch_url
213
170
        if branch_name:
214
171
            self.branch_name = branch_name
242
199
    _methodname = 'link_branch_to_bug'
243
200
 
244
201
    def __init__(self, branch_url, bug_id):
 
202
        assert branch_url
245
203
        self.bug_id = bug_id
246
204
        self.branch_url = branch_url
247
205
 
250
208
        # This must match the parameter tuple expected by Launchpad for this
251
209
        # method
252
210
        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,)