Fix bugs reported by Paul Wise
- fix internal server error when name is missing for non Debian member - fix unicode handling in urlbuilder
This commit is contained in:
		
							parent
							
								
									362b6dff35
								
							
						
					
					
						commit
						29b05952d7
					
				
					 5 changed files with 297 additions and 245 deletions
				
			
		|  | @ -2,6 +2,8 @@ | |||
| 	* add updated translations from Weblate | ||||
| 	* switch to Poetry for dependency management | ||||
| 	* describe translation workflow in developer documentation | ||||
| 	* fix internal server error when name is missing for non Debian member (thanks Paul Wise for the report) | ||||
| 	* fix unicode handling in urlbuilder (thanks Paul Wise for the report) | ||||
| 
 | ||||
| 2022-09-24  Jan Dittberner  <jan@dittberner.info> | ||||
| 	* add updated translations from Weblate | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
| # | ||||
| # Debian Member Portfolio Service key finder module | ||||
| # | ||||
| # Copyright © 2009-2022 Jan Dittberner <jan@dittberner.info> | ||||
| # Copyright © 2009-2023 Jan Dittberner <jan@dittberner.info> | ||||
| # | ||||
| # This file is part of the Debian Member Portfolio Service. | ||||
| # | ||||
|  | @ -26,37 +26,34 @@ given keyring. | |||
| """ | ||||
| 
 | ||||
| import logging | ||||
| import time | ||||
| import sys | ||||
| import time | ||||
| from importlib import resources | ||||
| 
 | ||||
| db = None | ||||
| cachetimestamp = 0 | ||||
| cache_timestamp = 0 | ||||
| 
 | ||||
| 
 | ||||
| def _get_keyring_cache(): | ||||
|     global db, cachetimestamp | ||||
|     if db is None or (time.time() - cachetimestamp) > 86300: | ||||
|     global db, cache_timestamp | ||||
|     if db is None or (time.time() - cache_timestamp) > 86300: | ||||
|         import dbm | ||||
|         import pkg_resources | ||||
|         import os.path | ||||
|         filename = pkg_resources.resource_filename(__name__, | ||||
|                                                    'keyringcache') | ||||
|         logging.debug('reading cache data from %s', filename) | ||||
|         assert ( | ||||
|             os.path.exists(filename + '.db') and | ||||
|             os.path.isfile(filename + '.db') | ||||
|         ) | ||||
|         db = dbm.open(filename, 'r') | ||||
|         cachetimestamp = time.time() | ||||
| 
 | ||||
|         dbm_filename = str(resources.files(__package__).joinpath("keyringcache.db")) | ||||
|         logging.debug("reading cache data from %s", dbm_filename) | ||||
|         assert os.path.exists(dbm_filename) and os.path.isfile(dbm_filename) | ||||
|         db = dbm.open(dbm_filename[: -len(".db")], "r") | ||||
|         cache_timestamp = time.time() | ||||
|     return db | ||||
| 
 | ||||
| 
 | ||||
| def _get_cached(cachekey): | ||||
|     cache = _get_keyring_cache() | ||||
|     logging.debug('cache lookup for %s', cachekey) | ||||
|     logging.debug("cache lookup for %s", cachekey) | ||||
|     if cachekey in cache: | ||||
|         logging.debug('found entry %s', cache[cachekey]) | ||||
|         return cache[cachekey].decode('utf8') | ||||
|         logging.debug("found entry %s", cache[cachekey]) | ||||
|         return cache[cachekey].decode("utf8") | ||||
|     return None | ||||
| 
 | ||||
| 
 | ||||
|  | @ -65,7 +62,7 @@ def getFingerprintByEmail(email): | |||
|     Gets the fingerprints associated with the given email address if | ||||
|     available. | ||||
|     """ | ||||
|     return _get_cached('fpr:email:%s' % email) | ||||
|     return _get_cached("fpr:email:%s" % email) | ||||
| 
 | ||||
| 
 | ||||
| def getRealnameByEmail(email): | ||||
|  | @ -73,7 +70,7 @@ def getRealnameByEmail(email): | |||
|     Gets the real names associated with the given email address if | ||||
|     available. | ||||
|     """ | ||||
|     return _get_cached('name:email:%s' % email) | ||||
|     return _get_cached("name:email:%s" % email) | ||||
| 
 | ||||
| 
 | ||||
| def getLoginByEmail(email): | ||||
|  | @ -81,34 +78,34 @@ def getLoginByEmail(email): | |||
|     Gets the logins associated with the given email address if | ||||
|     available. | ||||
|     """ | ||||
|     return _get_cached('login:email:%s' % email) | ||||
|     return _get_cached("login:email:%s" % email) | ||||
| 
 | ||||
| 
 | ||||
| def getLoginByFingerprint(fpr): | ||||
|     """ | ||||
|     Gets the login associated with the given fingerprint if available. | ||||
|     """ | ||||
|     return _get_cached('login:fpr:%s' % fpr) | ||||
|     return _get_cached("login:fpr:%s" % fpr) | ||||
| 
 | ||||
| 
 | ||||
| def _dump_cache(): | ||||
|     cache = _get_keyring_cache() | ||||
|     fprs = [] | ||||
|     for key in [key.decode('utf8') for key in list(cache.keys())]: | ||||
|         if key.startswith('email:fpr:'): | ||||
|             fpr = key.replace('email:fpr:', '') | ||||
|     for key in [key.decode("utf8") for key in list(cache.keys())]: | ||||
|         if key.startswith("email:fpr:"): | ||||
|             fpr = key.replace("email:fpr:", "") | ||||
|             if not fpr in fprs: | ||||
|                 fprs.append(fpr) | ||||
| 
 | ||||
|     for fpr in fprs: | ||||
|         login = getLoginByFingerprint(fpr) | ||||
|         email = _get_cached('email:fpr:%s' % fpr) | ||||
|         name = _get_cached('name:fpr:%s' % fpr) | ||||
|         email = _get_cached("email:fpr:%s" % fpr) | ||||
|         name = _get_cached("name:fpr:%s" % fpr) | ||||
| 
 | ||||
|         print(fpr, login, ':') | ||||
|         print('   ', name, email) | ||||
|         print(fpr, login, ":") | ||||
|         print("   ", name, email) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
| if __name__ == "__main__": | ||||
|     logging.basicConfig(stream=sys.stderr, level=logging.WARNING) | ||||
|     _dump_cache() | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
| # | ||||
| # Debian Member Portfolio Service application key ring analyzer tool | ||||
| # | ||||
| # Copyright © 2009-2015 Jan Dittberner <jan@dittberner.info> | ||||
| # Copyright © 2009-2023 Jan Dittberner <jan@dittberner.info> | ||||
| # | ||||
| # This file is part of the Debian Member Portfolio Service. | ||||
| # | ||||
|  | @ -21,22 +21,21 @@ | |||
| # along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
| # | ||||
| """ | ||||
| This is a tool that analyzes GPG and PGP keyrings and stores the | ||||
| This is a tool that analyzes GPG and PGP key rings and stores the | ||||
| retrieved data in a file database. The tool was inspired by Debian | ||||
| qa's carnivore. | ||||
| """ | ||||
| 
 | ||||
| import dbm | ||||
| import pkg_resources | ||||
| import glob | ||||
| import configparser | ||||
| import dbm | ||||
| import email.utils | ||||
| import glob | ||||
| import logging | ||||
| import os | ||||
| import os.path | ||||
| import logging | ||||
| import subprocess | ||||
| import sys | ||||
| import email.utils | ||||
| 
 | ||||
| from importlib import resources | ||||
| 
 | ||||
| CONFIG = configparser.ConfigParser() | ||||
| 
 | ||||
|  | @ -46,18 +45,18 @@ def _get_keyrings(): | |||
|     Gets the available keyring files from the keyring directory | ||||
|     configured in portfolio.ini. | ||||
|     """ | ||||
|     keyringdir = os.path.expanduser(CONFIG.get('DEFAULT', 'keyring.dir')) | ||||
|     logging.debug("keyring dir is %s", keyringdir) | ||||
|     keyrings = glob.glob(os.path.join(keyringdir, '*.gpg')) | ||||
|     keyrings.extend(glob.glob(os.path.join(keyringdir, '*.pgp'))) | ||||
|     keyring_dir = os.path.expanduser(CONFIG.get("DEFAULT", "keyring.dir")) | ||||
|     logging.debug("keyring dir is %s", keyring_dir) | ||||
|     keyrings = glob.glob(os.path.join(keyring_dir, "*.gpg")) | ||||
|     keyrings.extend(glob.glob(os.path.join(keyring_dir, "*.pgp"))) | ||||
|     keyrings.sort() | ||||
|     return keyrings | ||||
| 
 | ||||
| 
 | ||||
| def _parse_uid(uid): | ||||
|     """ | ||||
|     Parse a uid of the form 'Real Name <email@example.com>' into email | ||||
|     and realname parts. | ||||
|     Parse an uid of the form 'Real Name <email@example.com>' into email | ||||
|     and real name parts. | ||||
|     """ | ||||
| 
 | ||||
|     # First try with the Python library, but it doesn't always catch everything | ||||
|  | @ -67,63 +66,65 @@ def _parse_uid(uid): | |||
|     if (not name) or (not mail): | ||||
|         logging.debug("strange uid %s: '%s' - <%s>", uid, name, mail) | ||||
|         # Try and do better than the python library | ||||
|         if not '@' in mail: | ||||
|         if "@" not in mail: | ||||
|             uid = uid.strip() | ||||
|             # First, strip comment | ||||
|             s = uid.find('(') | ||||
|             e = uid.find(')') | ||||
|             s = uid.find("(") | ||||
|             e = uid.find(")") | ||||
|             if s >= 0 and e >= 0: | ||||
|                 uid = uid[:s] + uid[e + 1:] | ||||
|             s = uid.find('<') | ||||
|             e = uid.find('>') | ||||
|                 uid = uid[:s] + uid[e + 1 :] | ||||
|             s = uid.find("<") | ||||
|             e = uid.find(">") | ||||
|             mail = None | ||||
|             if s >= 0 and e >= 0: | ||||
|                 mail = uid[s + 1:e] | ||||
|                 uid = uid[:s] + uid[e + 1:] | ||||
|                 mail = uid[s + 1 : e] | ||||
|                 uid = uid[:s] + uid[e + 1 :] | ||||
|             uid = uid.strip() | ||||
|             if not mail and uid.find('@') >= 0: | ||||
|             if not mail and uid.find("@") >= 0: | ||||
|                 mail, uid = uid, mail | ||||
| 
 | ||||
|             name = uid | ||||
|             logging.debug("corrected: '%s' - <%s>", name, mail) | ||||
|     return (name, mail) | ||||
|     return name, mail | ||||
| 
 | ||||
| resultdict = {} | ||||
| 
 | ||||
| result_dict = {} | ||||
| 
 | ||||
| 
 | ||||
| def _get_canonical(key): | ||||
|     if not key in resultdict: | ||||
|         resultdict[key] = [] | ||||
|     if key not in result_dict: | ||||
|         result_dict[key] = [] | ||||
|     return key | ||||
| 
 | ||||
| 
 | ||||
| def _add_to_result(key, newvalue): | ||||
|     logging.debug("adding %s: %s", key, newvalue) | ||||
|     thekey = _get_canonical(key) | ||||
|     if newvalue not in resultdict[thekey]: | ||||
|         resultdict[thekey].append(newvalue) | ||||
| def _add_to_result(key, new_value): | ||||
|     logging.debug("adding %s: %s", key, new_value) | ||||
|     the_key = _get_canonical(key) | ||||
|     if new_value not in result_dict[the_key]: | ||||
|         result_dict[the_key].append(new_value) | ||||
| 
 | ||||
| 
 | ||||
| def _handle_mail(mail, fpr): | ||||
|     if mail.endswith('@debian.org'): | ||||
|         login = mail[0:-len('@debian.org')] | ||||
|         _add_to_result('login:email:%s' % mail, login) | ||||
|         _add_to_result('login:fpr:%s' % fpr, login) | ||||
|         _add_to_result('fpr:login:%s' % login, fpr) | ||||
|     _add_to_result('fpr:email:%s' % mail, fpr) | ||||
|     _add_to_result('email:fpr:%s' % fpr, mail) | ||||
|     if mail.endswith("@debian.org"): | ||||
|         login = mail[0 : -len("@debian.org")] | ||||
|         _add_to_result("login:email:%s" % mail, login) | ||||
|         _add_to_result("login:fpr:%s" % fpr, login) | ||||
|         _add_to_result("fpr:login:%s" % login, fpr) | ||||
|     _add_to_result("fpr:email:%s" % mail, fpr) | ||||
|     _add_to_result("email:fpr:%s" % fpr, mail) | ||||
| 
 | ||||
| 
 | ||||
| def _handle_uid(uid, fpr): | ||||
|     mail = None | ||||
|     # Do stuff with 'uid' | ||||
|     if uid: | ||||
|         (uid, mail) = _parse_uid(uid) | ||||
|         if mail: | ||||
|             _handle_mail(mail, fpr) | ||||
|     if uid: | ||||
|         _add_to_result('name:fpr:%s' % fpr, uid) | ||||
|         _add_to_result("name:fpr:%s" % fpr, uid) | ||||
|         if mail: | ||||
|             _add_to_result('name:email:%s' % mail, uid) | ||||
|             _add_to_result("name:email:%s" % mail, uid) | ||||
|     return fpr | ||||
| 
 | ||||
| 
 | ||||
|  | @ -131,13 +132,13 @@ def process_gpg_list_keys_line(line, fpr): | |||
|     """ | ||||
|     Process a line of gpg --list-keys --with-colon output. | ||||
|     """ | ||||
|     items = line.split(':') | ||||
|     if items[0] == 'pub': | ||||
|     items = line.split(":") | ||||
|     if items[0] == "pub": | ||||
|         return None | ||||
|     if items[0] == 'fpr': | ||||
|     if items[0] == "fpr": | ||||
|         return items[9].strip() | ||||
|     if items[0] == 'uid': | ||||
|         if items[1] == 'r': | ||||
|     if items[0] == "uid": | ||||
|         if items[1] == "r": | ||||
|             return fpr | ||||
|         return _handle_uid(items[9].strip(), fpr) | ||||
|     else: | ||||
|  | @ -145,41 +146,54 @@ def process_gpg_list_keys_line(line, fpr): | |||
| 
 | ||||
| 
 | ||||
| def process_keyrings(): | ||||
|     """Process the keyrings and store the extracted data in an anydbm | ||||
|     file.""" | ||||
|     """Process the keyrings and store the extracted data in an anydbm file.""" | ||||
|     for keyring in _get_keyrings(): | ||||
|         logging.debug("get data from %s", keyring) | ||||
|         proc = subprocess.Popen([ | ||||
|             "gpg", "--no-options", "--no-default-keyring", | ||||
|             "--homedir", os.path.expanduser( | ||||
|                 CONFIG.get('DEFAULT', 'gnupghome')), | ||||
|         proc = subprocess.Popen( | ||||
|             [ | ||||
|                 "gpg", | ||||
|                 "--no-options", | ||||
|                 "--no-default-keyring", | ||||
|                 "--homedir", | ||||
|                 os.path.expanduser(CONFIG.get("DEFAULT", "gnupghome")), | ||||
|                 "--no-expensive-trust-checks", | ||||
|             "--keyring", keyring, "--list-keys", | ||||
|             "--with-colons", "--fixed-list-mode", "--with-fingerprint", | ||||
|             "--with-fingerprint"], | ||||
|             stdout=subprocess.PIPE) | ||||
|                 "--keyring", | ||||
|                 keyring, | ||||
|                 "--list-keys", | ||||
|                 "--with-colons", | ||||
|                 "--fixed-list-mode", | ||||
|                 "--with-fingerprint", | ||||
|                 "--with-fingerprint", | ||||
|             ], | ||||
|             stdout=subprocess.PIPE, | ||||
|         ) | ||||
|         fpr = None | ||||
|         for line in proc.stdout.readlines(): | ||||
|             try: | ||||
|                 line = line.decode('utf8') | ||||
|                 line = line.decode("utf8") | ||||
|             except UnicodeDecodeError: | ||||
|                 line = line.decode('iso8859-1') | ||||
|                 line = line.decode("iso8859-1") | ||||
|             fpr = process_gpg_list_keys_line(line, fpr) | ||||
|         retcode = proc.wait() | ||||
|         if retcode != 0: | ||||
|             logging.error("subprocess ended with return code %d", retcode) | ||||
|     db = dbm.open(pkg_resources.resource_filename(__name__, | ||||
|                                                      'keyringcache'), 'c') | ||||
|     for key in resultdict: | ||||
|         db[key] = ":".join(resultdict[key]) | ||||
|         ret_code = proc.wait() | ||||
|         if ret_code != 0: | ||||
|             logging.error("subprocess ended with return code %d", ret_code) | ||||
|     dbm_filename = str( | ||||
|         resources.files("debianmemberportfolio.model").joinpath("keyringcache") | ||||
|     ) | ||||
|     db = dbm.open(dbm_filename, "c") | ||||
|     for key in result_dict: | ||||
|         db[key] = ":".join(result_dict[key]) | ||||
|     db.close() | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
| if __name__ == "__main__": | ||||
|     logging.basicConfig(stream=sys.stderr, level=logging.WARNING) | ||||
|     CONFIG.read_string(pkg_resources.resource_string( | ||||
|         __name__, 'portfolio.ini').decode('utf8')) | ||||
|     gpghome = os.path.expanduser(CONFIG.get('DEFAULT', 'gnupghome')) | ||||
|     if not os.path.isdir(gpghome): | ||||
|         os.makedirs(gpghome, 0o700) | ||||
|     CONFIG.read_string( | ||||
|         resources.files("debianmemberportfolio.model") | ||||
|         .joinpath("portfolio.ini") | ||||
|         .read_text("utf8") | ||||
|     ) | ||||
|     gpg_home = os.path.expanduser(CONFIG.get("DEFAULT", "gnupghome")) | ||||
|     if not os.path.isdir(gpg_home): | ||||
|         os.makedirs(gpg_home, 0o700) | ||||
|     process_keyrings() | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
| # | ||||
| # Debian Member Portfolio Service url builder | ||||
| # | ||||
| # Copyright © 2009-2022 Jan Dittberner <jan@dittberner.info> | ||||
| # Copyright © 2009-2023 Jan Dittberner <jan@dittberner.info> | ||||
| # | ||||
| # This file is part of the Debian Member Portfolio Service. | ||||
| # | ||||
|  | @ -28,36 +28,40 @@ portfolio.ini. | |||
| 
 | ||||
| from configparser import ConfigParser, InterpolationMissingOptionError | ||||
| from encodings.utf_8 import StreamReader as UTF8StreamReader | ||||
| 
 | ||||
| import pkg_resources | ||||
| from debianmemberportfolio.model import keyfinder | ||||
| from importlib import resources | ||||
| from urllib.parse import quote_plus | ||||
| from flask_babel import gettext as _, lazy_gettext as N_ | ||||
| 
 | ||||
| from debianmemberportfolio.model import keyfinder | ||||
| from flask_babel import gettext as _ | ||||
| from flask_babel import lazy_gettext as N_ | ||||
| 
 | ||||
| my_config = ConfigParser() | ||||
| my_config.read_file(UTF8StreamReader( | ||||
|     pkg_resources.resource_stream(__name__, 'portfolio.ini'))) | ||||
| ref = resources.files("debianmemberportfolio.model").joinpath("portfolio.ini") | ||||
| with ref.open("rb") as fp: | ||||
|     my_config.read_file(UTF8StreamReader(fp)) | ||||
| 
 | ||||
| _FIELDNAMES_MAP = { | ||||
|     'email': N_('Email address'), | ||||
|     'name': N_('Name'), | ||||
|     'openpgpfp': N_('OpenPGP fingerprint'), | ||||
|     'username': N_('Debian user name'), | ||||
|     'nonddemail': N_('Non Debian email address'), | ||||
|     'salsausername': N_('Salsa user name'), | ||||
|     "email": N_("Email address"), | ||||
|     "name": N_("Name"), | ||||
|     "openpgpfp": N_("OpenPGP fingerprint"), | ||||
|     "username": N_("Debian user name"), | ||||
|     "nonddemail": N_("Non Debian email address"), | ||||
|     "salsausername": N_("Salsa user name"), | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| class DDPortfolioEntry(object): | ||||
|     def __init__(self, config, section, key): | ||||
|         self.name = key | ||||
|         self.optional = config.has_option(section, key + '.optional') and \ | ||||
|             config.getboolean(section, key + '.optional') or False | ||||
|         if config.has_option(section, key + '.type'): | ||||
|             self.type = config.get(section, key + '.type') | ||||
|         self.optional = ( | ||||
|             config.has_option(section, key + ".optional") | ||||
|             and config.getboolean(section, key + ".optional") | ||||
|             or False | ||||
|         ) | ||||
|         if config.has_option(section, key + ".type"): | ||||
|             self.type = config.get(section, key + ".type") | ||||
|         else: | ||||
|             self.type = 'url' | ||||
|             self.type = "url" | ||||
| 
 | ||||
| 
 | ||||
| def _build_quoted_fields(fields): | ||||
|  | @ -68,19 +72,19 @@ def _build_quoted_fields(fields): | |||
|     for key, value in fields.items(): | ||||
|         if value is not None: | ||||
|             if isinstance(value, str): | ||||
|                 qfields[key] = quote_plus(value.encode('utf8')) | ||||
|                 qfields[key] = quote_plus(value.encode("utf8")) | ||||
|             elif isinstance(value, str): | ||||
|                 qfields[key] = quote_plus(value) | ||||
|             else: | ||||
|                 qfields[key] = value | ||||
|             qfields[key] = str(qfields[key]).replace('%', '%%') | ||||
|             qfields[key] = str(qfields[key]).replace("%", "%%") | ||||
| 
 | ||||
|     if 'openpgpfp' not in qfields: | ||||
|         fpr = keyfinder.getFingerprintByEmail(fields['email'].encode('utf8')) | ||||
|     if "openpgpfp" not in qfields: | ||||
|         fpr = keyfinder.getFingerprintByEmail(fields["email"]) | ||||
|         if fpr: | ||||
|             qfields['openpgpfp'] = fpr[0] | ||||
|     qfields['firstchar'] = fields['email'][0].encode('utf8') | ||||
|     qfields['emailnoq'] = fields['email'].encode('utf8') | ||||
|             qfields["openpgpfp"] = fpr[0] | ||||
|     qfields["firstchar"] = fields["email"][0] | ||||
|     qfields["emailnoq"] = fields["email"] | ||||
|     return qfields | ||||
| 
 | ||||
| 
 | ||||
|  | @ -88,27 +92,50 @@ def build_urls(fields): | |||
|     """Build personalized URLs using the developer information in | ||||
|     fields.""" | ||||
|     data = [] | ||||
|     qfields = _build_quoted_fields(fields) | ||||
|     for section in [section.strip() for section in | ||||
|                     my_config.get('DEFAULT', | ||||
|                                   'urlbuilder.sections').split(',')]: | ||||
|         data.append(['section', section]) | ||||
|         if my_config.has_option(section, 'urls'): | ||||
|             for entry in ([ | ||||
|                 DDPortfolioEntry(my_config, section, url) for url in | ||||
|                     my_config.get(section, 'urls').split(',')]): | ||||
|     quoted_fields = _build_quoted_fields(fields) | ||||
|     for section in [ | ||||
|         section.strip() | ||||
|         for section in my_config.get("DEFAULT", "urlbuilder.sections").split(",") | ||||
|     ]: | ||||
|         data.append(["section", section]) | ||||
|         if my_config.has_option(section, "urls"): | ||||
|             for entry in [ | ||||
|                 DDPortfolioEntry(my_config, section, url) | ||||
|                 for url in my_config.get(section, "urls").split(",") | ||||
|             ]: | ||||
|                 try: | ||||
|                     data.append( | ||||
|                         ['url', section, entry, | ||||
|                          my_config.get(section, entry.name + '.pattern', | ||||
|                                        raw=False, vars=qfields)]) | ||||
|                         [ | ||||
|                             "url", | ||||
|                             section, | ||||
|                             entry, | ||||
|                             my_config.get( | ||||
|                                 section, | ||||
|                                 entry.name + ".pattern", | ||||
|                                 raw=False, | ||||
|                                 vars=quoted_fields, | ||||
|                             ), | ||||
|                         ] | ||||
|                     ) | ||||
|                 except InterpolationMissingOptionError as e: | ||||
|                     if not entry.optional: | ||||
|                         if e.reference in _FIELDNAMES_MAP: | ||||
|                             data.append(['error', section, entry, | ||||
|                                          _('Missing input: %s') % | ||||
|                                          _(_FIELDNAMES_MAP[e.reference])]) | ||||
|                             data.append( | ||||
|                                 [ | ||||
|                                     "error", | ||||
|                                     section, | ||||
|                                     entry, | ||||
|                                     _("Missing input: %s") | ||||
|                                     % _(_FIELDNAMES_MAP[e.reference]), | ||||
|                                 ] | ||||
|                             ) | ||||
|                         else: | ||||
|                             data.append(['error', section, entry, | ||||
|                                          _('Missing input: %s') % e.reference]) | ||||
|                             data.append( | ||||
|                                 [ | ||||
|                                     "error", | ||||
|                                     section, | ||||
|                                     entry, | ||||
|                                     _("Missing input: %s") % e.reference, | ||||
|                                 ] | ||||
|                             ) | ||||
|     return data | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
| # | ||||
| # Debian Member Portfolio Service views | ||||
| # | ||||
| # Copyright © 2015-2022 Jan Dittberner <jan@dittberner.info> | ||||
| # Copyright © 2015-2023 Jan Dittberner <jan@dittberner.info> | ||||
| # | ||||
| # This file is part of the Debian Member Portfolio Service. | ||||
| # | ||||
|  | @ -23,11 +23,13 @@ | |||
| import json | ||||
| import logging | ||||
| 
 | ||||
| from config import LANGUAGES | ||||
| from debianmemberportfolio import app, babel | ||||
| from flask import g, make_response, request, render_template, abort | ||||
| from flask import abort, g, make_response, render_template, request | ||||
| 
 | ||||
| # noinspection PyPep8Naming | ||||
| from flask_babel import lazy_gettext as N_ | ||||
| from config import LANGUAGES | ||||
| 
 | ||||
| from .forms import DeveloperData, DeveloperDataRequest | ||||
| from .model import dddatabuilder | ||||
| from .model.urlbuilder import build_urls | ||||
|  | @ -36,88 +38,93 @@ log = logging.getLogger(__name__) | |||
| 
 | ||||
| #: This dictionary defines groups of labeled portfolio items. | ||||
| _LABELS = { | ||||
|     'overview': { | ||||
|         'label': N_('Overview'), | ||||
|         'ddpo': N_("Debian Member's Package Overview"), | ||||
|         'alladdresses': N_("""Debian Member's Package Overview | ||||
| ... showing all email addresses"""), | ||||
|     "overview": { | ||||
|         "label": N_("Overview"), | ||||
|         "ddpo": N_("Debian Member's Package Overview"), | ||||
|         "alladdresses": N_( | ||||
|             """Debian Member's Package Overview | ||||
| ... showing all email addresses""" | ||||
|         ), | ||||
|     }, | ||||
|     'bugs': { | ||||
|         'label': N_('Bugs'), | ||||
|         'received': N_('''bugs received | ||||
|     "bugs": { | ||||
|         "label": N_("Bugs"), | ||||
|         "received": N_( | ||||
|             """bugs received | ||||
| (note: co-maintainers not listed, see \ | ||||
| <a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?\ | ||||
| bug=430986">#430986</a>)'''), | ||||
|         'reported': N_('bugs reported'), | ||||
|         'usertags': N_('user tags'), | ||||
|         'wnpp': N_('<a href="https://wiki.debian.org/WNPP">WNPP</a>'), | ||||
|         'correspondent': N_('correspondent for bugs'), | ||||
|         'graph': N_('one year open bug history graph'), | ||||
| bug=430986">#430986</a>)""" | ||||
|         ), | ||||
|         "reported": N_("bugs reported"), | ||||
|         "usertags": N_("user tags"), | ||||
|         "wnpp": N_('<a href="https://wiki.debian.org/WNPP">WNPP</a>'), | ||||
|         "correspondent": N_("correspondent for bugs"), | ||||
|         "graph": N_("one year open bug history graph"), | ||||
|     }, | ||||
|     'build': { | ||||
|         'label': N_('Build'), | ||||
|         'buildd': N_('buildd.d.o'), | ||||
|         'igloo': N_('igloo'), | ||||
|     "build": { | ||||
|         "label": N_("Build"), | ||||
|         "buildd": N_("buildd.d.o"), | ||||
|         "igloo": N_("igloo"), | ||||
|     }, | ||||
|     'qa': { | ||||
|         'label': N_('Quality Assurance'), | ||||
|         'dmd': N_('maintainer dashboard'), | ||||
|         'lintian': N_('lintian reports'), | ||||
|         'lintianfull': N_('full lintian reports (i.e. including \ | ||||
| "info"-level messages)'), | ||||
|         'piuparts': N_('piuparts'), | ||||
|         'janitor': N_('Debian Janitor'), | ||||
|     "qa": { | ||||
|         "label": N_("Quality Assurance"), | ||||
|         "dmd": N_("maintainer dashboard"), | ||||
|         "lintian": N_("lintian reports"), | ||||
|         "lintianfull": N_( | ||||
|             'full lintian reports (i.e. including \ | ||||
| "info"-level messages)' | ||||
|         ), | ||||
|         "piuparts": N_("piuparts"), | ||||
|         "janitor": N_("Debian Janitor"), | ||||
|     }, | ||||
|     'lists': { | ||||
|         'label': N_('Mailing Lists'), | ||||
|         'dolists': N_('lists.d.o'), | ||||
|         'adolists': N_('lists.a.d.o'), | ||||
|     "lists": { | ||||
|         "label": N_("Mailing Lists"), | ||||
|         "dolists": N_("lists.d.o"), | ||||
|         "adolists": N_("lists.a.d.o"), | ||||
|     }, | ||||
|     'files': { | ||||
|         'label': N_('Files'), | ||||
|         'people': N_('people.d.o'), | ||||
|         'oldpeople': N_('oldpeople'), | ||||
|     "files": { | ||||
|         "label": N_("Files"), | ||||
|         "people": N_("people.d.o"), | ||||
|         "oldpeople": N_("oldpeople"), | ||||
|     }, | ||||
|     'membership': { | ||||
|         'label': N_('Membership'), | ||||
|         'nm': N_('NM'), | ||||
|         'dbfinger': N_('DB information via finger'), | ||||
|         'db': N_('DB information via HTTP'), | ||||
|         'salsa': N_('Salsa'), | ||||
|         'wiki': N_('Wiki'), | ||||
|         'forum': N_('Forum'), | ||||
|     "membership": { | ||||
|         "label": N_("Membership"), | ||||
|         "nm": N_("NM"), | ||||
|         "dbfinger": N_("DB information via finger"), | ||||
|         "db": N_("DB information via HTTP"), | ||||
|         "salsa": N_("Salsa"), | ||||
|         "wiki": N_("Wiki"), | ||||
|         "forum": N_("Forum"), | ||||
|     }, | ||||
|     'miscellaneous': { | ||||
|         'label': N_('Miscellaneous'), | ||||
|         'debtags': N_('debtags'), | ||||
|         'planetname': N_('Planet Debian (name)'), | ||||
|         'planetuser': N_('Planet Debian (username)'), | ||||
|         'links': N_('links'), | ||||
|         'website': N_('Debian website'), | ||||
|         'search': N_('Debian search'), | ||||
|         'gpgfinger': N_('OpenPGP public key via finger'), | ||||
|         'gpgweb': N_('OpenPGP public key via HTTP'), | ||||
|         'nm': N_('NM, AM participation'), | ||||
|         'contrib': N_('Contribution information'), | ||||
|         'repology': N_('Repology information'), | ||||
|     "miscellaneous": { | ||||
|         "label": N_("Miscellaneous"), | ||||
|         "debtags": N_("debtags"), | ||||
|         "planetname": N_("Planet Debian (name)"), | ||||
|         "planetuser": N_("Planet Debian (username)"), | ||||
|         "links": N_("links"), | ||||
|         "website": N_("Debian website"), | ||||
|         "search": N_("Debian search"), | ||||
|         "gpgfinger": N_("OpenPGP public key via finger"), | ||||
|         "gpgweb": N_("OpenPGP public key via HTTP"), | ||||
|         "nm": N_("NM, AM participation"), | ||||
|         "contrib": N_("Contribution information"), | ||||
|         "repology": N_("Repology information"), | ||||
|     }, | ||||
|     'ssh': { | ||||
|         'label': N_('Information reachable via ssh (for Debian Members)'), | ||||
|         'owndndoms': N_('owned debian.net domains'), | ||||
|         'miainfo': N_('<a href="https://wiki.debian.org/qa.debian.org/' | ||||
|                       'MIATeam">MIA</a> database information'), | ||||
|         'groupinfo': N_('Group membership information'), | ||||
|     "ssh": { | ||||
|         "label": N_("Information reachable via ssh (for Debian Members)"), | ||||
|         "owndndoms": N_("owned debian.net domains"), | ||||
|         "miainfo": N_( | ||||
|             '<a href="https://wiki.debian.org/qa.debian.org/' | ||||
|             'MIATeam">MIA</a> database information' | ||||
|         ), | ||||
|         "groupinfo": N_("Group membership information"), | ||||
|     }, | ||||
| } | ||||
| 
 | ||||
| #: list of field name tuples for Debian Maintainers | ||||
| DM_TUPLES = (('name', 'name'), | ||||
|              ('openpgpfp', 'openpgpfp'), | ||||
|              ('nonddemail', 'email')) | ||||
| DM_TUPLES = (("name", "name"), ("openpgpfp", "openpgpfp"), ("nonddemail", "email")) | ||||
| 
 | ||||
| #: list of field name tuples for Debian Developers | ||||
| DD_TUPLES = (('username', 'username'), | ||||
|              ('salsausername', 'username')) | ||||
| DD_TUPLES = (("username", "username"), ("salsausername", "username")) | ||||
| 
 | ||||
| 
 | ||||
| def _get_label(section, url=None): | ||||
|  | @ -125,8 +132,8 @@ def _get_label(section, url=None): | |||
|         if url: | ||||
|             if url in _LABELS[section]: | ||||
|                 return _LABELS[section][url] | ||||
|         elif 'label' in _LABELS[section]: | ||||
|             return _LABELS[section]['label'] | ||||
|         elif "label" in _LABELS[section]: | ||||
|             return _LABELS[section]["label"] | ||||
|     if url: | ||||
|         return "%s.%s" % (section, url) | ||||
|     return section | ||||
|  | @ -142,70 +149,75 @@ def before_request(): | |||
|     g.locale = get_locale() | ||||
| 
 | ||||
| 
 | ||||
| @app.route('/') | ||||
| @app.route("/") | ||||
| def index(): | ||||
|     form = DeveloperData() | ||||
|     return render_template('showform.html', form=form) | ||||
|     return render_template("showform.html", form=form) | ||||
| 
 | ||||
| 
 | ||||
| @app.route('/result') | ||||
| @app.route("/result") | ||||
| def urllist(): | ||||
|     form = DeveloperData(request.values) | ||||
|     if form.validate(): | ||||
|         fields = dddatabuilder.build_data(form.data['email']) | ||||
|         fields = dddatabuilder.build_data(form.data["email"]) | ||||
| 
 | ||||
|         form_data = form.data.copy() | ||||
| 
 | ||||
|         if fields['type'] in (dddatabuilder.TYPE_DD, dddatabuilder.TYPE_DM): | ||||
|         if fields["type"] in (dddatabuilder.TYPE_DD, dddatabuilder.TYPE_DM): | ||||
|             for dmtuple in DM_TUPLES: | ||||
|                 if not form_data[dmtuple[0]]: | ||||
|                     form_data[dmtuple[0]] = fields[dmtuple[1]] | ||||
|         if fields['type'] == dddatabuilder.TYPE_DD: | ||||
|         if fields["type"] == dddatabuilder.TYPE_DD: | ||||
|             for ddtuple in DD_TUPLES: | ||||
|                 if not form_data[ddtuple[0]]: | ||||
|                     form_data[ddtuple[0]] = fields[ddtuple[1]] | ||||
|         if not form_data['wikihomepage']: | ||||
|             log.debug('generate wikihomepage from name') | ||||
|             form_data['wikihomepage'] = "".join([ | ||||
|                 part.capitalize() for part in form_data['name'].split() | ||||
|             ]) | ||||
|         if not form_data["wikihomepage"] and form_data["name"]: | ||||
|             log.debug("generate wikihomepage from name") | ||||
|             form_data["wikihomepage"] = "".join( | ||||
|                 [part.capitalize() for part in form_data["name"].split()] | ||||
|             ) | ||||
| 
 | ||||
|         data = build_urls(form_data) | ||||
| 
 | ||||
|         if form_data['mode'] == 'json': | ||||
|             response = make_response(json.dumps(dict( | ||||
|                 [("{}.{}".format(entry[1], entry[2].name), entry[3]) | ||||
|                  for entry in data if entry[0] == 'url']))) | ||||
|             response.headers['Content-Type'] = 'application/json' | ||||
|         if form_data["mode"] == "json": | ||||
|             response = make_response( | ||||
|                 json.dumps( | ||||
|                     dict( | ||||
|                         [ | ||||
|                             ("{}.{}".format(entry[1], entry[2].name), entry[3]) | ||||
|                             for entry in data | ||||
|                             if entry[0] == "url" | ||||
|                         ] | ||||
|                     ) | ||||
|                 ) | ||||
|             ) | ||||
|             response.headers["Content-Type"] = "application/json" | ||||
|             return response | ||||
| 
 | ||||
|         for entry in data: | ||||
|             if entry[0] in ('url', 'error'): | ||||
|             if entry[0] in ("url", "error"): | ||||
|                 entry.append(_get_label(entry[1], entry[2].name)) | ||||
|             elif entry[0] == 'section': | ||||
|             elif entry[0] == "section": | ||||
|                 entry.append(_get_label(entry[1])) | ||||
| 
 | ||||
|         return render_template('showurls.html', urldata=data) | ||||
|     return render_template('showform.html', form=form) | ||||
|         return render_template("showurls.html", urldata=data) | ||||
|     return render_template("showform.html", form=form) | ||||
| 
 | ||||
| 
 | ||||
| @app.route('/htmlformhelper.js') | ||||
| @app.route("/htmlformhelper.js") | ||||
| def formhelper_js(): | ||||
|     response = make_response(render_template('showformscript.js')) | ||||
|     response.headers['Content-Type'] = 'text/javascript; charset=utf-8' | ||||
|     response = make_response(render_template("showformscript.js")) | ||||
|     response.headers["Content-Type"] = "text/javascript; charset=utf-8" | ||||
|     return response | ||||
| 
 | ||||
| 
 | ||||
| @app.route('/showformscripts/fetchdddata/') | ||||
| @app.route("/showformscripts/fetchdddata/") | ||||
| def fetchdddata(): | ||||
|     form = DeveloperDataRequest(request.values) | ||||
|     if form.validate(): | ||||
|         fields = dddatabuilder.build_data(form.data['email']) | ||||
|         fields = dddatabuilder.build_data(form.data["email"]) | ||||
|         log.debug(fields) | ||||
|         response = make_response(json.dumps(fields)) | ||||
|         response.headers['Content-Type'] = 'application/json' | ||||
|         response.headers["Content-Type"] = "application/json" | ||||
|         return response | ||||
|     abort( | ||||
|         400, | ||||
|         "\n".join(["%s: %s" % (key, form.errors[key]) for key in form.errors]) | ||||
|     ) | ||||
|     abort(400, "\n".join(["%s: %s" % (key, form.errors[key]) for key in form.errors])) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue