forked from pool/python-bugzilla
216 lines
8.4 KiB
Diff
216 lines
8.4 KiB
Diff
From 5347c9d17b0f8bb0430611b687b1389d0620c035 Mon Sep 17 00:00:00 2001
|
|
From: Michal Vyskocil <mvyskocil@suse.cz>
|
|
Date: Tue, 21 Jul 2009 16:17:21 +0200
|
|
Subject: [PATCH 2/2] NovellBugzilla implementation.
|
|
|
|
The NovellBugzilla implementation - is a subclass of Bugzilla32 with
|
|
reimplemented _login and _logoout methods compatible with iChain.
|
|
|
|
NovellBugzilla don't allow other self.url than bnc, because it should
|
|
not be used for any other bugzilla. The url parameters in __init__ and
|
|
connect() are overwritten and exists only for compatibility purposes.
|
|
|
|
It can also read the username/password from ~/.oscrc, which is
|
|
common for many SUSE users and contains a same iChain credentials as is
|
|
necessary for bugzilla login. So when user use osc he don't need
|
|
duplicate login informations to ~/.bugzillarc.
|
|
---
|
|
bugzilla/__init__.py | 3 +-
|
|
bugzilla/nvlbugzilla.py | 164 +++++++++++++++++++++++++++++++++++++++++++++++
|
|
2 files changed, 166 insertions(+), 1 deletions(-)
|
|
create mode 100644 bugzilla/nvlbugzilla.py
|
|
|
|
diff --git a/bugzilla/__init__.py b/bugzilla/__init__.py
|
|
index 902c996..a871f26 100644
|
|
--- a/bugzilla/__init__.py
|
|
+++ b/bugzilla/__init__.py
|
|
@@ -11,13 +11,14 @@
|
|
|
|
from bugzilla3 import Bugzilla3, Bugzilla32
|
|
from rhbugzilla import RHBugzilla, RHBugzilla3
|
|
+from nvlbugzilla import NovellBugzilla
|
|
from base import version
|
|
import xmlrpclib
|
|
import logging
|
|
log = logging.getLogger("bugzilla")
|
|
|
|
# advertised class list
|
|
-classlist = ['Bugzilla3', 'Bugzilla32', 'RHBugzilla3']
|
|
+classlist = ['Bugzilla3', 'Bugzilla32', 'RHBugzilla3', 'NovellBugzilla']
|
|
|
|
def getBugzillaClassForURL(url):
|
|
log.debug("Choosing subclass for %s" % url)
|
|
diff --git a/bugzilla/nvlbugzilla.py b/bugzilla/nvlbugzilla.py
|
|
new file mode 100644
|
|
index 0000000..027bb19
|
|
--- /dev/null
|
|
+++ b/bugzilla/nvlbugzilla.py
|
|
@@ -0,0 +1,164 @@
|
|
+# nvlbugzilla.py - a Python interface to Novell Hat Bugzilla using xmlrpclib.
|
|
+#
|
|
+# Copyright (C) 2009 Novell Inc.
|
|
+# Author: Michal Vyskocil <mvyskocil@suse.cz>
|
|
+#
|
|
+# This program is free software; you can redistribute it and/or modify it
|
|
+# under the terms of the GNU General Public License as published by the
|
|
+# Free Software Foundation; either version 2 of the License, or (at your
|
|
+# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
|
|
+# the full text of the license.
|
|
+
|
|
+#from bugzilla.base import BugzillaError, log
|
|
+import bugzilla.base
|
|
+from bugzilla import Bugzilla32
|
|
+
|
|
+import urllib
|
|
+import urllib2
|
|
+import urlparse
|
|
+import cookielib
|
|
+import time
|
|
+import re
|
|
+import os
|
|
+
|
|
+class NovellBugzilla(Bugzilla32):
|
|
+ '''bugzilla.novell.com is a standard bugzilla 3.2 with some extensions, but
|
|
+ it uses an proprietary and non-standard IChain login system. This class
|
|
+ reimplements a login method which is compatible with iChain.
|
|
+
|
|
+ Because login process takes relativelly long time, because it needs several
|
|
+ HTTP requests, NovellBugzilla caches the session cookies of bugzilla
|
|
+ (ZXXXXXXX-bugzilla) and IChain (IPCXXXXXXXXXXXXX) in a self._cookiefile to
|
|
+ speedup a repeated connections. To avoid problems with cookie expiration,
|
|
+ it set the expiration of cookie to 5 minutes. This expects cookies stored
|
|
+ in LWPCookieJar format and login method warn if cookies are in
|
|
+ MozillaCookieJar format.
|
|
+
|
|
+ It can also read a credentials from ~/.oscrc if exists, so it should not
|
|
+ be duplicated in /etc/bugzillarc, or ~/.bugzillarc
|
|
+ '''
|
|
+
|
|
+ version = '0.1'
|
|
+ user_agent = bugzilla.base.user_agent + ' NovellBugzilla/%s' % version
|
|
+
|
|
+ bnc_cookie_re = re.compile('^Z.*-bugzilla')
|
|
+ ichain_cookie_re = re.compile('^IPC.*')
|
|
+ cookie_domain_re = re.compile('.*\.novell\.com$')
|
|
+
|
|
+ bugzilla_url = 'https://bugzilla.novell.com/xmlrpc.cgi'
|
|
+ logout_url = 'https://www.novell.com/cmd/ICSLogout'
|
|
+ obs_url = 'https://api.opensuse.org/'
|
|
+ #FIXME: is it really necessary to use all those paths???
|
|
+ login_path = '/index.cgi?GoAheadAndLogIn=1'
|
|
+ auth_path = '/ICSLogin/auth-up'
|
|
+ ichainlogin_path = '/ichainlogin.cgi'
|
|
+
|
|
+ def __init__(self, expires=300, **kwargs):
|
|
+ self._expires = expires
|
|
+ super(NovellBugzilla, self).__init__(**kwargs)
|
|
+ # url argument exists only for backward compatibility, but is always set to same url
|
|
+ self._url = self.__class__.bugzilla_url
|
|
+
|
|
+ def __get_expiration(self):
|
|
+ return self._expires
|
|
+ def __set_expiration(self, expires):
|
|
+ self._expires = expires
|
|
+ expires = property(__get_expiration, __set_expiration)
|
|
+
|
|
+ def _iter_domain_cookies(self):
|
|
+ '''Return an generator from all cookies matched a self.__class__.cookie_domain_re'''
|
|
+ return (c for c in self._cookiejar if self.__class__.cookie_domain_re.match(c.domain) and not c.is_expired())
|
|
+
|
|
+ def _is_bugzilla_cookie(self):
|
|
+ return len([c for c in self._iter_domain_cookies() if self.__class__.bnc_cookie_re.match(c.name)]) != 0
|
|
+
|
|
+ def _is_ichain_cookie(self):
|
|
+ return len([c for c in self._iter_domain_cookies() if self.__class__.ichain_cookie_re.match(c.name)]) != 0
|
|
+
|
|
+ def _is_lwp_format(self):
|
|
+ return isinstance(self._cookiejar, cookielib.LWPCookieJar)
|
|
+
|
|
+ def _login(self, user, password):
|
|
+ #TODO: IChain is an openID provides - discover an ability of openID login
|
|
+
|
|
+ # init some basic
|
|
+ cls = self.__class__
|
|
+ base_url = self.url[:-11] # remove /xmlrpc.cgi
|
|
+
|
|
+ lwp_format = self._is_lwp_format()
|
|
+ if not lwp_format:
|
|
+ bugzilla.base.log.warn("""File `%s' is not in LWP format required for NovellBugzilla.
|
|
+If you want cache the cookies and speedup the repeated connections, remove it or use an another file for cookies.""" % self._cookiefile)
|
|
+
|
|
+ #TODO: do some testing what will be if the cookie expires
|
|
+ if lwp_format and not self._is_bugzilla_cookie():
|
|
+ login_url = urlparse.urljoin(base_url, cls.login_path)
|
|
+ bugzilla.base.log.info("GET %s" % login_url)
|
|
+ login_resp = self._opener.open(login_url)
|
|
+ if login_resp.code != 200:
|
|
+ raise BugzillaError("The login failed with code %d" % login_resp.core)
|
|
+
|
|
+ params = {
|
|
+ 'url' : urlparse.urljoin(base_url, cls.ichainlogin_path),
|
|
+ 'target' : cls.login_path[1:],
|
|
+ 'context' : 'default',
|
|
+ 'proxypath' : 'reverse',
|
|
+ 'nlogin_submit_btn' : 'Log In',
|
|
+ 'username' : user,
|
|
+ 'password' : password
|
|
+ }
|
|
+
|
|
+ if lwp_format and not self._is_ichain_cookie():
|
|
+ auth_url = urlparse.urljoin(base_url, cls.auth_path)
|
|
+ auth_params = urllib.urlencode(params)
|
|
+ auth_req = urllib2.Request(auth_url, auth_params)
|
|
+ bugzilla.base.log.info("POST %s" % auth_url)
|
|
+ auth_resp = self._opener.open(auth_req)
|
|
+ if auth_resp.code != 200:
|
|
+ raise BugzillaError("The auth failed with code %d" % auth_resp.core)
|
|
+
|
|
+ if lwp_format:
|
|
+ for cookie in self._cookiejar:
|
|
+ cookie.expires = time.time() + self._expires # expires cookie in 15 minutes
|
|
+ cookie.discard = False
|
|
+
|
|
+ return super(NovellBugzilla, self)._login(user, password)
|
|
+
|
|
+ def connect(self, url):
|
|
+ # NovellBugzilla should connect only to bnc,
|
|
+ return super(NovellBugzilla, self).connect(self.__class__.bugzilla_url)
|
|
+
|
|
+ def _logout(self):
|
|
+ '''Novell bugzilla don't support xmlrpc logout, so let's implemtent it.
|
|
+ This method also set all domain cookies as expired.
|
|
+ '''
|
|
+
|
|
+ resp = self._opener.open(self.__class__.logout_url)
|
|
+ # expire cookies
|
|
+ for cookie in self._iter_domain_cookies():
|
|
+ cookie.expires = 0
|
|
+
|
|
+ def readconfig(self, configpath=None):
|
|
+ super(NovellBugzilla, self).readconfig(configpath)
|
|
+
|
|
+ oscrc=os.path.expanduser('~/.oscrc')
|
|
+ if not self.user and not self.password \
|
|
+ and os.path.exists(oscrc):
|
|
+ from ConfigParser import SafeConfigParser, NoOptionError
|
|
+ c = SafeConfigParser()
|
|
+ r = c.read(oscrc)
|
|
+ if not r:
|
|
+ return
|
|
+
|
|
+ obs_url = self.__class__.obs_url
|
|
+ if not c.has_section(obs_url):
|
|
+ return
|
|
+
|
|
+ try:
|
|
+ self.user = c.get(obs_url, 'user')
|
|
+ self.password = c.get(obs_url, 'pass')
|
|
+ bugzilla.base.log.info("Read credentials from ~/.oscrc")
|
|
+ except NoOptionError, ne:
|
|
+ return
|
|
+
|
|
+
|
|
--
|
|
1.6.3.3
|
|
|