File 0002-NovellBugzilla-implementation.patch of Package python-bugzilla
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