forked from jan/debianmemberportfolio
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
|
* 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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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])
|
|
||||||
)
|
|
||||||
|
|
Loading…
Reference in a new issue