~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Naoki INADA
  • Date: 2009-10-29 10:01:19 UTC
  • mto: (4634.97.3 2.0)
  • mto: This revision was merged to the branch mainline in revision 4798.
  • Revision ID: inada-n@klab.jp-20091029100119-uckv9t7ej2qrghw3
import doc-ja rev90

Show diffs side-by-side

added added

removed removed

Lines of Context:
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
 
18
 
from getpass import getpass
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
 
19
18
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
 
24
29
from bzrlib import (
25
30
    config,
26
31
    errors,
32
37
export BZR_LP_XMLRPC_URL=http://xmlrpc.staging.launchpad.net/bazaar/
33
38
'''
34
39
 
 
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
 
35
56
class LaunchpadService(object):
36
57
    """A service to talk to Launchpad via XMLRPC.
37
 
    
 
58
 
38
59
    See http://bazaar-vcs.org/Specs/LaunchpadRpc for the methods we can call.
39
60
    """
40
61
 
41
 
    # NB: this should always end in a slash to avoid xmlrpclib appending
 
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
42
71
    # '/RPC2'
43
 
    DEFAULT_SERVICE_URL = 'https://xmlrpc.launchpad.net/bazaar/'
 
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]
44
82
 
45
83
    transport = None
46
84
    registrant_email = None
47
85
    registrant_password = None
48
86
 
49
87
 
50
 
    def __init__(self, transport=None):
 
88
    def __init__(self, transport=None, lp_instance=None):
51
89
        """Construct a new service talking to the launchpad rpc server"""
 
90
        self._lp_instance = lp_instance
52
91
        if transport is None:
53
92
            uri_type = urllib.splittype(self.service_url)[0]
54
93
            if uri_type == 'https':
69
108
        key = 'BZR_LP_XMLRPC_URL'
70
109
        if key in os.environ:
71
110
            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)
72
116
        else:
73
117
            return self.DEFAULT_SERVICE_URL
74
118
 
75
 
    def get_proxy(self):
 
119
    def get_proxy(self, authenticated):
76
120
        """Return the proxy for XMLRPC requests."""
77
 
        # auth info must be in url
78
 
        # TODO: if there's no registrant email perhaps we should just connect
79
 
        # anonymously?
80
 
        scheme, hostinfo, path = urlsplit(self.service_url)[:3]
81
 
        assert '@' not in hostinfo
82
 
        assert self.registrant_email is not None
83
 
        assert self.registrant_password is not None
84
 
        # TODO: perhaps fully quote the password to make it very slightly
85
 
        # obscured
86
 
        # TODO: can we perhaps add extra Authorization headers directly to the 
87
 
        # request, rather than putting this into the url?  perhaps a bit more 
88
 
        # secure against accidentally revealing it.  std66 s3.2.1 discourages putting
89
 
        # the password in the url.
90
 
        hostinfo = '%s:%s@%s' % (urllib.quote(self.registrant_email),
91
 
                                 urllib.quote(self.registrant_password),
92
 
                                 hostinfo)
93
 
        url = urlunsplit((scheme, hostinfo, path, '', ''))
 
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
94
145
        return xmlrpclib.ServerProxy(url, transport=self.transport)
95
146
 
96
147
    def gather_user_credentials(self):
97
148
        """Get the password from the user."""
98
 
        config = config.GlobalConfig()
99
 
        self.registrant_email = config.user_email()
 
149
        the_config = config.GlobalConfig()
 
150
        self.registrant_email = the_config.user_email()
100
151
        if self.registrant_password is None:
101
152
            auth = config.AuthenticationConfig()
102
153
            scheme, hostinfo = urlsplit(self.service_url)[:2]
105
156
            # We will reuse http[s] credentials if we can, prompt user
106
157
            # otherwise
107
158
            self.registrant_password = auth.get_password(scheme, hostinfo,
 
159
                                                         self.registrant_email,
108
160
                                                         prompt=prompt)
109
161
 
110
 
    def send_request(self, method_name, method_params):
111
 
        proxy = self.get_proxy()
112
 
        assert method_name
 
162
    def send_request(self, method_name, method_params, authenticated):
 
163
        proxy = self.get_proxy(authenticated)
113
164
        method = getattr(proxy, method_name)
114
165
        try:
115
166
            result = method(*method_params)
126
177
                # TODO: print more headers to help in tracking down failures
127
178
                raise errors.BzrError("xmlrpc protocol error connecting to %s: %s %s"
128
179
                        % (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)
129
184
        return result
130
185
 
 
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
 
131
220
 
132
221
class BaseRequest(object):
133
222
    """Base request for talking to a XMLRPC server."""
134
223
 
135
224
    # Set this to the XMLRPC method name.
136
225
    _methodname = None
 
226
    _authenticated = True
137
227
 
138
228
    def _request_params(self):
139
229
        """Return the arguments to pass to the method"""
145
235
        :param service: LaunchpadService indicating where to send
146
236
            the request and the authentication credentials.
147
237
        """
148
 
        return service.send_request(self._methodname, self._request_params())
 
238
        return service.send_request(self._methodname, self._request_params(),
 
239
                                    self._authenticated)
149
240
 
150
241
 
151
242
class DryRunLaunchpadService(LaunchpadService):
152
243
    """Service that just absorbs requests without sending to server.
153
 
    
 
244
 
154
245
    The dummy service does not need authentication.
155
246
    """
156
247
 
157
 
    def send_request(self, method_name, method_params):
 
248
    def send_request(self, method_name, method_params, authenticated):
158
249
        pass
159
250
 
160
251
    def gather_user_credentials(self):
173
264
                 author_email='',
174
265
                 product_name='',
175
266
                 ):
176
 
        assert branch_url
 
267
        if not branch_url:
 
268
            raise errors.InvalidURL(branch_url, "You need to specify a non-empty branch URL.")
177
269
        self.branch_url = branch_url
178
270
        if branch_name:
179
271
            self.branch_name = branch_name
207
299
    _methodname = 'link_branch_to_bug'
208
300
 
209
301
    def __init__(self, branch_url, bug_id):
210
 
        assert branch_url
211
302
        self.bug_id = bug_id
212
303
        self.branch_url = branch_url
213
304
 
216
307
        # This must match the parameter tuple expected by Launchpad for this
217
308
        # method
218
309
        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,)