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:
Jan Dittberner 2023-06-03 17:56:08 +02:00
parent 362b6dff35
commit 29b05952d7
5 changed files with 297 additions and 245 deletions

View file

@ -2,6 +2,8 @@
* add updated translations from Weblate * add updated translations from Weblate
* switch to Poetry for dependency management * switch to Poetry for dependency management
* describe translation workflow in developer documentation * 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> 2022-09-24 Jan Dittberner <jan@dittberner.info>
* add updated translations from Weblate * add updated translations from Weblate

View file

@ -3,7 +3,7 @@
# #
# Debian Member Portfolio Service key finder module # 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. # This file is part of the Debian Member Portfolio Service.
# #
@ -26,37 +26,34 @@ given keyring.
""" """
import logging import logging
import time
import sys import sys
import time
from importlib import resources
db = None db = None
cachetimestamp = 0 cache_timestamp = 0
def _get_keyring_cache(): def _get_keyring_cache():
global db, cachetimestamp global db, cache_timestamp
if db is None or (time.time() - cachetimestamp) > 86300: if db is None or (time.time() - cache_timestamp) > 86300:
import dbm import dbm
import pkg_resources
import os.path import os.path
filename = pkg_resources.resource_filename(__name__,
'keyringcache') dbm_filename = str(resources.files(__package__).joinpath("keyringcache.db"))
logging.debug('reading cache data from %s', filename) logging.debug("reading cache data from %s", dbm_filename)
assert ( assert os.path.exists(dbm_filename) and os.path.isfile(dbm_filename)
os.path.exists(filename + '.db') and db = dbm.open(dbm_filename[: -len(".db")], "r")
os.path.isfile(filename + '.db') cache_timestamp = time.time()
)
db = dbm.open(filename, 'r')
cachetimestamp = time.time()
return db return db
def _get_cached(cachekey): def _get_cached(cachekey):
cache = _get_keyring_cache() cache = _get_keyring_cache()
logging.debug('cache lookup for %s', cachekey) logging.debug("cache lookup for %s", cachekey)
if cachekey in cache: if cachekey in cache:
logging.debug('found entry %s', cache[cachekey]) logging.debug("found entry %s", cache[cachekey])
return cache[cachekey].decode('utf8') return cache[cachekey].decode("utf8")
return None return None
@ -65,7 +62,7 @@ def getFingerprintByEmail(email):
Gets the fingerprints associated with the given email address if Gets the fingerprints associated with the given email address if
available. available.
""" """
return _get_cached('fpr:email:%s' % email) return _get_cached("fpr:email:%s" % email)
def getRealnameByEmail(email): def getRealnameByEmail(email):
@ -73,7 +70,7 @@ def getRealnameByEmail(email):
Gets the real names associated with the given email address if Gets the real names associated with the given email address if
available. available.
""" """
return _get_cached('name:email:%s' % email) return _get_cached("name:email:%s" % email)
def getLoginByEmail(email): def getLoginByEmail(email):
@ -81,34 +78,34 @@ def getLoginByEmail(email):
Gets the logins associated with the given email address if Gets the logins associated with the given email address if
available. available.
""" """
return _get_cached('login:email:%s' % email) return _get_cached("login:email:%s" % email)
def getLoginByFingerprint(fpr): def getLoginByFingerprint(fpr):
""" """
Gets the login associated with the given fingerprint if available. 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(): def _dump_cache():
cache = _get_keyring_cache() cache = _get_keyring_cache()
fprs = [] fprs = []
for key in [key.decode('utf8') for key in list(cache.keys())]: for key in [key.decode("utf8") for key in list(cache.keys())]:
if key.startswith('email:fpr:'): if key.startswith("email:fpr:"):
fpr = key.replace('email:fpr:', '') fpr = key.replace("email:fpr:", "")
if not fpr in fprs: if not fpr in fprs:
fprs.append(fpr) fprs.append(fpr)
for fpr in fprs: for fpr in fprs:
login = getLoginByFingerprint(fpr) login = getLoginByFingerprint(fpr)
email = _get_cached('email:fpr:%s' % fpr) email = _get_cached("email:fpr:%s" % fpr)
name = _get_cached('name:fpr:%s' % fpr) name = _get_cached("name:fpr:%s" % fpr)
print(fpr, login, ':') print(fpr, login, ":")
print(' ', name, email) print(" ", name, email)
if __name__ == '__main__': if __name__ == "__main__":
logging.basicConfig(stream=sys.stderr, level=logging.WARNING) logging.basicConfig(stream=sys.stderr, level=logging.WARNING)
_dump_cache() _dump_cache()

View file

@ -3,7 +3,7 @@
# #
# Debian Member Portfolio Service application key ring analyzer tool # 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. # 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/>. # 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 retrieved data in a file database. The tool was inspired by Debian
qa's carnivore. qa's carnivore.
""" """
import dbm
import pkg_resources
import glob
import configparser import configparser
import dbm
import email.utils
import glob
import logging
import os import os
import os.path import os.path
import logging
import subprocess import subprocess
import sys import sys
import email.utils from importlib import resources
CONFIG = configparser.ConfigParser() CONFIG = configparser.ConfigParser()
@ -46,18 +45,18 @@ def _get_keyrings():
Gets the available keyring files from the keyring directory Gets the available keyring files from the keyring directory
configured in portfolio.ini. configured in portfolio.ini.
""" """
keyringdir = os.path.expanduser(CONFIG.get('DEFAULT', 'keyring.dir')) keyring_dir = os.path.expanduser(CONFIG.get("DEFAULT", "keyring.dir"))
logging.debug("keyring dir is %s", keyringdir) logging.debug("keyring dir is %s", keyring_dir)
keyrings = glob.glob(os.path.join(keyringdir, '*.gpg')) keyrings = glob.glob(os.path.join(keyring_dir, "*.gpg"))
keyrings.extend(glob.glob(os.path.join(keyringdir, '*.pgp'))) keyrings.extend(glob.glob(os.path.join(keyring_dir, "*.pgp")))
keyrings.sort() keyrings.sort()
return keyrings return keyrings
def _parse_uid(uid): def _parse_uid(uid):
""" """
Parse a uid of the form 'Real Name <email@example.com>' into email Parse an uid of the form 'Real Name <email@example.com>' into email
and realname parts. and real name parts.
""" """
# First try with the Python library, but it doesn't always catch everything # 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): if (not name) or (not mail):
logging.debug("strange uid %s: '%s' - <%s>", uid, name, mail) logging.debug("strange uid %s: '%s' - <%s>", uid, name, mail)
# Try and do better than the python library # Try and do better than the python library
if not '@' in mail: if "@" not in mail:
uid = uid.strip() uid = uid.strip()
# First, strip comment # First, strip comment
s = uid.find('(') s = uid.find("(")
e = uid.find(')') e = uid.find(")")
if s >= 0 and e >= 0: if s >= 0 and e >= 0:
uid = uid[:s] + uid[e + 1:] uid = uid[:s] + uid[e + 1 :]
s = uid.find('<') s = uid.find("<")
e = uid.find('>') e = uid.find(">")
mail = None mail = None
if s >= 0 and e >= 0: if s >= 0 and e >= 0:
mail = uid[s + 1:e] mail = uid[s + 1 : e]
uid = uid[:s] + uid[e + 1:] uid = uid[:s] + uid[e + 1 :]
uid = uid.strip() uid = uid.strip()
if not mail and uid.find('@') >= 0: if not mail and uid.find("@") >= 0:
mail, uid = uid, mail mail, uid = uid, mail
name = uid name = uid
logging.debug("corrected: '%s' - <%s>", name, mail) logging.debug("corrected: '%s' - <%s>", name, mail)
return (name, mail) return name, mail
resultdict = {}
result_dict = {}
def _get_canonical(key): def _get_canonical(key):
if not key in resultdict: if key not in result_dict:
resultdict[key] = [] result_dict[key] = []
return key return key
def _add_to_result(key, newvalue): def _add_to_result(key, new_value):
logging.debug("adding %s: %s", key, newvalue) logging.debug("adding %s: %s", key, new_value)
thekey = _get_canonical(key) the_key = _get_canonical(key)
if newvalue not in resultdict[thekey]: if new_value not in result_dict[the_key]:
resultdict[thekey].append(newvalue) result_dict[the_key].append(new_value)
def _handle_mail(mail, fpr): def _handle_mail(mail, fpr):
if mail.endswith('@debian.org'): if mail.endswith("@debian.org"):
login = mail[0:-len('@debian.org')] login = mail[0 : -len("@debian.org")]
_add_to_result('login:email:%s' % mail, login) _add_to_result("login:email:%s" % mail, login)
_add_to_result('login:fpr:%s' % fpr, login) _add_to_result("login:fpr:%s" % fpr, login)
_add_to_result('fpr:login:%s' % login, fpr) _add_to_result("fpr:login:%s" % login, fpr)
_add_to_result('fpr:email:%s' % mail, fpr) _add_to_result("fpr:email:%s" % mail, fpr)
_add_to_result('email:fpr:%s' % fpr, mail) _add_to_result("email:fpr:%s" % fpr, mail)
def _handle_uid(uid, fpr): def _handle_uid(uid, fpr):
mail = None
# Do stuff with 'uid' # Do stuff with 'uid'
if uid: if uid:
(uid, mail) = _parse_uid(uid) (uid, mail) = _parse_uid(uid)
if mail: if mail:
_handle_mail(mail, fpr) _handle_mail(mail, fpr)
if uid: if uid:
_add_to_result('name:fpr:%s' % fpr, uid) _add_to_result("name:fpr:%s" % fpr, uid)
if mail: if mail:
_add_to_result('name:email:%s' % mail, uid) _add_to_result("name:email:%s" % mail, uid)
return fpr return fpr
@ -131,13 +132,13 @@ def process_gpg_list_keys_line(line, fpr):
""" """
Process a line of gpg --list-keys --with-colon output. Process a line of gpg --list-keys --with-colon output.
""" """
items = line.split(':') items = line.split(":")
if items[0] == 'pub': if items[0] == "pub":
return None return None
if items[0] == 'fpr': if items[0] == "fpr":
return items[9].strip() return items[9].strip()
if items[0] == 'uid': if items[0] == "uid":
if items[1] == 'r': if items[1] == "r":
return fpr return fpr
return _handle_uid(items[9].strip(), fpr) return _handle_uid(items[9].strip(), fpr)
else: else:
@ -145,41 +146,54 @@ def process_gpg_list_keys_line(line, fpr):
def process_keyrings(): def process_keyrings():
"""Process the keyrings and store the extracted data in an anydbm """Process the keyrings and store the extracted data in an anydbm file."""
file."""
for keyring in _get_keyrings(): for keyring in _get_keyrings():
logging.debug("get data from %s", keyring) logging.debug("get data from %s", keyring)
proc = subprocess.Popen([ proc = subprocess.Popen(
"gpg", "--no-options", "--no-default-keyring", [
"--homedir", os.path.expanduser( "gpg",
CONFIG.get('DEFAULT', 'gnupghome')), "--no-options",
"--no-expensive-trust-checks", "--no-default-keyring",
"--keyring", keyring, "--list-keys", "--homedir",
"--with-colons", "--fixed-list-mode", "--with-fingerprint", os.path.expanduser(CONFIG.get("DEFAULT", "gnupghome")),
"--with-fingerprint"], "--no-expensive-trust-checks",
stdout=subprocess.PIPE) "--keyring",
keyring,
"--list-keys",
"--with-colons",
"--fixed-list-mode",
"--with-fingerprint",
"--with-fingerprint",
],
stdout=subprocess.PIPE,
)
fpr = None fpr = None
for line in proc.stdout.readlines(): for line in proc.stdout.readlines():
try: try:
line = line.decode('utf8') line = line.decode("utf8")
except UnicodeDecodeError: except UnicodeDecodeError:
line = line.decode('iso8859-1') line = line.decode("iso8859-1")
fpr = process_gpg_list_keys_line(line, fpr) fpr = process_gpg_list_keys_line(line, fpr)
retcode = proc.wait() ret_code = proc.wait()
if retcode != 0: if ret_code != 0:
logging.error("subprocess ended with return code %d", retcode) logging.error("subprocess ended with return code %d", ret_code)
db = dbm.open(pkg_resources.resource_filename(__name__, dbm_filename = str(
'keyringcache'), 'c') resources.files("debianmemberportfolio.model").joinpath("keyringcache")
for key in resultdict: )
db[key] = ":".join(resultdict[key]) db = dbm.open(dbm_filename, "c")
for key in result_dict:
db[key] = ":".join(result_dict[key])
db.close() db.close()
if __name__ == '__main__': if __name__ == "__main__":
logging.basicConfig(stream=sys.stderr, level=logging.WARNING) logging.basicConfig(stream=sys.stderr, level=logging.WARNING)
CONFIG.read_string(pkg_resources.resource_string( CONFIG.read_string(
__name__, 'portfolio.ini').decode('utf8')) resources.files("debianmemberportfolio.model")
gpghome = os.path.expanduser(CONFIG.get('DEFAULT', 'gnupghome')) .joinpath("portfolio.ini")
if not os.path.isdir(gpghome): .read_text("utf8")
os.makedirs(gpghome, 0o700) )
gpg_home = os.path.expanduser(CONFIG.get("DEFAULT", "gnupghome"))
if not os.path.isdir(gpg_home):
os.makedirs(gpg_home, 0o700)
process_keyrings() process_keyrings()

View file

@ -3,7 +3,7 @@
# #
# Debian Member Portfolio Service url builder # 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. # This file is part of the Debian Member Portfolio Service.
# #
@ -28,36 +28,40 @@ portfolio.ini.
from configparser import ConfigParser, InterpolationMissingOptionError from configparser import ConfigParser, InterpolationMissingOptionError
from encodings.utf_8 import StreamReader as UTF8StreamReader from encodings.utf_8 import StreamReader as UTF8StreamReader
from importlib import resources
import pkg_resources
from debianmemberportfolio.model import keyfinder
from urllib.parse import quote_plus 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 = ConfigParser()
my_config.read_file(UTF8StreamReader( ref = resources.files("debianmemberportfolio.model").joinpath("portfolio.ini")
pkg_resources.resource_stream(__name__, 'portfolio.ini'))) with ref.open("rb") as fp:
my_config.read_file(UTF8StreamReader(fp))
_FIELDNAMES_MAP = { _FIELDNAMES_MAP = {
'email': N_('Email address'), "email": N_("Email address"),
'name': N_('Name'), "name": N_("Name"),
'openpgpfp': N_('OpenPGP fingerprint'), "openpgpfp": N_("OpenPGP fingerprint"),
'username': N_('Debian user name'), "username": N_("Debian user name"),
'nonddemail': N_('Non Debian email address'), "nonddemail": N_("Non Debian email address"),
'salsausername': N_('Salsa user name'), "salsausername": N_("Salsa user name"),
} }
class DDPortfolioEntry(object): class DDPortfolioEntry(object):
def __init__(self, config, section, key): def __init__(self, config, section, key):
self.name = key self.name = key
self.optional = config.has_option(section, key + '.optional') and \ self.optional = (
config.getboolean(section, key + '.optional') or False config.has_option(section, key + ".optional")
if config.has_option(section, key + '.type'): and config.getboolean(section, key + ".optional")
self.type = config.get(section, key + '.type') or False
)
if config.has_option(section, key + ".type"):
self.type = config.get(section, key + ".type")
else: else:
self.type = 'url' self.type = "url"
def _build_quoted_fields(fields): def _build_quoted_fields(fields):
@ -68,19 +72,19 @@ def _build_quoted_fields(fields):
for key, value in fields.items(): for key, value in fields.items():
if value is not None: if value is not None:
if isinstance(value, str): if isinstance(value, str):
qfields[key] = quote_plus(value.encode('utf8')) qfields[key] = quote_plus(value.encode("utf8"))
elif isinstance(value, str): elif isinstance(value, str):
qfields[key] = quote_plus(value) qfields[key] = quote_plus(value)
else: else:
qfields[key] = value qfields[key] = value
qfields[key] = str(qfields[key]).replace('%', '%%') qfields[key] = str(qfields[key]).replace("%", "%%")
if 'openpgpfp' not in qfields: if "openpgpfp" not in qfields:
fpr = keyfinder.getFingerprintByEmail(fields['email'].encode('utf8')) fpr = keyfinder.getFingerprintByEmail(fields["email"])
if fpr: if fpr:
qfields['openpgpfp'] = fpr[0] qfields["openpgpfp"] = fpr[0]
qfields['firstchar'] = fields['email'][0].encode('utf8') qfields["firstchar"] = fields["email"][0]
qfields['emailnoq'] = fields['email'].encode('utf8') qfields["emailnoq"] = fields["email"]
return qfields return qfields
@ -88,27 +92,50 @@ def build_urls(fields):
"""Build personalized URLs using the developer information in """Build personalized URLs using the developer information in
fields.""" fields."""
data = [] data = []
qfields = _build_quoted_fields(fields) quoted_fields = _build_quoted_fields(fields)
for section in [section.strip() for section in for section in [
my_config.get('DEFAULT', section.strip()
'urlbuilder.sections').split(',')]: for section in my_config.get("DEFAULT", "urlbuilder.sections").split(",")
data.append(['section', section]) ]:
if my_config.has_option(section, 'urls'): data.append(["section", section])
for entry in ([ if my_config.has_option(section, "urls"):
DDPortfolioEntry(my_config, section, url) for url in for entry in [
my_config.get(section, 'urls').split(',')]): DDPortfolioEntry(my_config, section, url)
for url in my_config.get(section, "urls").split(",")
]:
try: try:
data.append( data.append(
['url', section, entry, [
my_config.get(section, entry.name + '.pattern', "url",
raw=False, vars=qfields)]) section,
entry,
my_config.get(
section,
entry.name + ".pattern",
raw=False,
vars=quoted_fields,
),
]
)
except InterpolationMissingOptionError as e: except InterpolationMissingOptionError as e:
if not entry.optional: if not entry.optional:
if e.reference in _FIELDNAMES_MAP: if e.reference in _FIELDNAMES_MAP:
data.append(['error', section, entry, data.append(
_('Missing input: %s') % [
_(_FIELDNAMES_MAP[e.reference])]) "error",
section,
entry,
_("Missing input: %s")
% _(_FIELDNAMES_MAP[e.reference]),
]
)
else: else:
data.append(['error', section, entry, data.append(
_('Missing input: %s') % e.reference]) [
"error",
section,
entry,
_("Missing input: %s") % e.reference,
]
)
return data return data

View file

@ -3,7 +3,7 @@
# #
# Debian Member Portfolio Service views # 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. # This file is part of the Debian Member Portfolio Service.
# #
@ -23,11 +23,13 @@
import json import json
import logging import logging
from config import LANGUAGES
from debianmemberportfolio import app, babel 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 # noinspection PyPep8Naming
from flask_babel import lazy_gettext as N_ from flask_babel import lazy_gettext as N_
from config import LANGUAGES
from .forms import DeveloperData, DeveloperDataRequest from .forms import DeveloperData, DeveloperDataRequest
from .model import dddatabuilder from .model import dddatabuilder
from .model.urlbuilder import build_urls from .model.urlbuilder import build_urls
@ -36,88 +38,93 @@ log = logging.getLogger(__name__)
#: This dictionary defines groups of labeled portfolio items. #: This dictionary defines groups of labeled portfolio items.
_LABELS = { _LABELS = {
'overview': { "overview": {
'label': N_('Overview'), "label": N_("Overview"),
'ddpo': N_("Debian Member's Package Overview"), "ddpo": N_("Debian Member's Package Overview"),
'alladdresses': N_("""Debian Member's Package Overview "alladdresses": N_(
... showing all email addresses"""), """Debian Member's Package Overview
... showing all email addresses"""
),
}, },
'bugs': { "bugs": {
'label': N_('Bugs'), "label": N_("Bugs"),
'received': N_('''bugs received "received": N_(
"""bugs received
(note: co-maintainers not listed, see \ (note: co-maintainers not listed, see \
<a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?\ <a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?\
bug=430986">#430986</a>)'''), bug=430986">#430986</a>)"""
'reported': N_('bugs reported'), ),
'usertags': N_('user tags'), "reported": N_("bugs reported"),
'wnpp': N_('<a href="https://wiki.debian.org/WNPP">WNPP</a>'), "usertags": N_("user tags"),
'correspondent': N_('correspondent for bugs'), "wnpp": N_('<a href="https://wiki.debian.org/WNPP">WNPP</a>'),
'graph': N_('one year open bug history graph'), "correspondent": N_("correspondent for bugs"),
"graph": N_("one year open bug history graph"),
}, },
'build': { "build": {
'label': N_('Build'), "label": N_("Build"),
'buildd': N_('buildd.d.o'), "buildd": N_("buildd.d.o"),
'igloo': N_('igloo'), "igloo": N_("igloo"),
}, },
'qa': { "qa": {
'label': N_('Quality Assurance'), "label": N_("Quality Assurance"),
'dmd': N_('maintainer dashboard'), "dmd": N_("maintainer dashboard"),
'lintian': N_('lintian reports'), "lintian": N_("lintian reports"),
'lintianfull': N_('full lintian reports (i.e. including \ "lintianfull": N_(
"info"-level messages)'), 'full lintian reports (i.e. including \
'piuparts': N_('piuparts'), "info"-level messages)'
'janitor': N_('Debian Janitor'), ),
"piuparts": N_("piuparts"),
"janitor": N_("Debian Janitor"),
}, },
'lists': { "lists": {
'label': N_('Mailing Lists'), "label": N_("Mailing Lists"),
'dolists': N_('lists.d.o'), "dolists": N_("lists.d.o"),
'adolists': N_('lists.a.d.o'), "adolists": N_("lists.a.d.o"),
}, },
'files': { "files": {
'label': N_('Files'), "label": N_("Files"),
'people': N_('people.d.o'), "people": N_("people.d.o"),
'oldpeople': N_('oldpeople'), "oldpeople": N_("oldpeople"),
}, },
'membership': { "membership": {
'label': N_('Membership'), "label": N_("Membership"),
'nm': N_('NM'), "nm": N_("NM"),
'dbfinger': N_('DB information via finger'), "dbfinger": N_("DB information via finger"),
'db': N_('DB information via HTTP'), "db": N_("DB information via HTTP"),
'salsa': N_('Salsa'), "salsa": N_("Salsa"),
'wiki': N_('Wiki'), "wiki": N_("Wiki"),
'forum': N_('Forum'), "forum": N_("Forum"),
}, },
'miscellaneous': { "miscellaneous": {
'label': N_('Miscellaneous'), "label": N_("Miscellaneous"),
'debtags': N_('debtags'), "debtags": N_("debtags"),
'planetname': N_('Planet Debian (name)'), "planetname": N_("Planet Debian (name)"),
'planetuser': N_('Planet Debian (username)'), "planetuser": N_("Planet Debian (username)"),
'links': N_('links'), "links": N_("links"),
'website': N_('Debian website'), "website": N_("Debian website"),
'search': N_('Debian search'), "search": N_("Debian search"),
'gpgfinger': N_('OpenPGP public key via finger'), "gpgfinger": N_("OpenPGP public key via finger"),
'gpgweb': N_('OpenPGP public key via HTTP'), "gpgweb": N_("OpenPGP public key via HTTP"),
'nm': N_('NM, AM participation'), "nm": N_("NM, AM participation"),
'contrib': N_('Contribution information'), "contrib": N_("Contribution information"),
'repology': N_('Repology information'), "repology": N_("Repology information"),
}, },
'ssh': { "ssh": {
'label': N_('Information reachable via ssh (for Debian Members)'), "label": N_("Information reachable via ssh (for Debian Members)"),
'owndndoms': N_('owned debian.net domains'), "owndndoms": N_("owned debian.net domains"),
'miainfo': N_('<a href="https://wiki.debian.org/qa.debian.org/' "miainfo": N_(
'MIATeam">MIA</a> database information'), '<a href="https://wiki.debian.org/qa.debian.org/'
'groupinfo': N_('Group membership information'), 'MIATeam">MIA</a> database information'
),
"groupinfo": N_("Group membership information"),
}, },
} }
#: list of field name tuples for Debian Maintainers #: list of field name tuples for Debian Maintainers
DM_TUPLES = (('name', 'name'), DM_TUPLES = (("name", "name"), ("openpgpfp", "openpgpfp"), ("nonddemail", "email"))
('openpgpfp', 'openpgpfp'),
('nonddemail', 'email'))
#: list of field name tuples for Debian Developers #: list of field name tuples for Debian Developers
DD_TUPLES = (('username', 'username'), DD_TUPLES = (("username", "username"), ("salsausername", "username"))
('salsausername', 'username'))
def _get_label(section, url=None): def _get_label(section, url=None):
@ -125,8 +132,8 @@ def _get_label(section, url=None):
if url: if url:
if url in _LABELS[section]: if url in _LABELS[section]:
return _LABELS[section][url] return _LABELS[section][url]
elif 'label' in _LABELS[section]: elif "label" in _LABELS[section]:
return _LABELS[section]['label'] return _LABELS[section]["label"]
if url: if url:
return "%s.%s" % (section, url) return "%s.%s" % (section, url)
return section return section
@ -142,70 +149,75 @@ def before_request():
g.locale = get_locale() g.locale = get_locale()
@app.route('/') @app.route("/")
def index(): def index():
form = DeveloperData() form = DeveloperData()
return render_template('showform.html', form=form) return render_template("showform.html", form=form)
@app.route('/result') @app.route("/result")
def urllist(): def urllist():
form = DeveloperData(request.values) form = DeveloperData(request.values)
if form.validate(): if form.validate():
fields = dddatabuilder.build_data(form.data['email']) fields = dddatabuilder.build_data(form.data["email"])
form_data = form.data.copy() 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: for dmtuple in DM_TUPLES:
if not form_data[dmtuple[0]]: if not form_data[dmtuple[0]]:
form_data[dmtuple[0]] = fields[dmtuple[1]] form_data[dmtuple[0]] = fields[dmtuple[1]]
if fields['type'] == dddatabuilder.TYPE_DD: if fields["type"] == dddatabuilder.TYPE_DD:
for ddtuple in DD_TUPLES: for ddtuple in DD_TUPLES:
if not form_data[ddtuple[0]]: if not form_data[ddtuple[0]]:
form_data[ddtuple[0]] = fields[ddtuple[1]] form_data[ddtuple[0]] = fields[ddtuple[1]]
if not form_data['wikihomepage']: if not form_data["wikihomepage"] and form_data["name"]:
log.debug('generate wikihomepage from name') log.debug("generate wikihomepage from name")
form_data['wikihomepage'] = "".join([ form_data["wikihomepage"] = "".join(
part.capitalize() for part in form_data['name'].split() [part.capitalize() for part in form_data["name"].split()]
]) )
data = build_urls(form_data) data = build_urls(form_data)
if form_data['mode'] == 'json': if form_data["mode"] == "json":
response = make_response(json.dumps(dict( response = make_response(
[("{}.{}".format(entry[1], entry[2].name), entry[3]) json.dumps(
for entry in data if entry[0] == 'url']))) dict(
response.headers['Content-Type'] = 'application/json' [
("{}.{}".format(entry[1], entry[2].name), entry[3])
for entry in data
if entry[0] == "url"
]
)
)
)
response.headers["Content-Type"] = "application/json"
return response return response
for entry in data: 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)) entry.append(_get_label(entry[1], entry[2].name))
elif entry[0] == 'section': elif entry[0] == "section":
entry.append(_get_label(entry[1])) entry.append(_get_label(entry[1]))
return render_template('showurls.html', urldata=data) return render_template("showurls.html", urldata=data)
return render_template('showform.html', form=form) return render_template("showform.html", form=form)
@app.route('/htmlformhelper.js') @app.route("/htmlformhelper.js")
def formhelper_js(): def formhelper_js():
response = make_response(render_template('showformscript.js')) response = make_response(render_template("showformscript.js"))
response.headers['Content-Type'] = 'text/javascript; charset=utf-8' response.headers["Content-Type"] = "text/javascript; charset=utf-8"
return response return response
@app.route('/showformscripts/fetchdddata/') @app.route("/showformscripts/fetchdddata/")
def fetchdddata(): def fetchdddata():
form = DeveloperDataRequest(request.values) form = DeveloperDataRequest(request.values)
if form.validate(): if form.validate():
fields = dddatabuilder.build_data(form.data['email']) fields = dddatabuilder.build_data(form.data["email"])
log.debug(fields) log.debug(fields)
response = make_response(json.dumps(fields)) response = make_response(json.dumps(fields))
response.headers['Content-Type'] = 'application/json' response.headers["Content-Type"] = "application/json"
return response return response
abort( abort(400, "\n".join(["%s: %s" % (key, form.errors[key]) for key in form.errors]))
400,
"\n".join(["%s: %s" % (key, form.errors[key]) for key in form.errors])
)