diff --git a/bauh/gems/arch/aur.py b/bauh/gems/arch/aur.py
index 884ab71a..d2afd8a5 100644
--- a/bauh/gems/arch/aur.py
+++ b/bauh/gems/arch/aur.py
@@ -1,11 +1,16 @@
import re
from typing import Set, List
+import requests
+
from bauh.api.http import HttpClient
URL_INFO = 'https://aur.archlinux.org/rpc/?v=5&type=info&'
+URL_SRC_INFO = 'https://aur.archlinux.org/cgit/aur.git/plain/.SRCINFO?h='
URL_SEARCH = 'https://aur.archlinux.org/rpc/?v=5&type=search&arg='
+RE_SRCINFO_KEYS = re.compile(r'(\w+)\s+=\s+(.+)\n')
+
def map_pkgbuild(pkgbuild: str) -> dict:
return {attr: val.replace('"', '').replace("'", '').replace('(', '').replace(')', '') for attr, val in re.findall(r'\n(\w+)=(.+)', pkgbuild)}
@@ -24,5 +29,24 @@ class AURClient:
res = self.http_client.get_json(URL_INFO + self._map_names_as_queries(names))
return res['results'] if res and res.get('results') else []
+ def get_src_info(self, name: str) -> dict:
+ res = self.http_client.get(URL_SRC_INFO + name)
+
+ if res and res.text:
+ info = {}
+ for field in RE_SRCINFO_KEYS.findall(res.text):
+ if field[0] not in info:
+ info[field[0]] = field[1]
+ else:
+ if not isinstance(info[field[0]], list):
+ info[field[0]] = [info[field[0]]]
+
+ info[field[0]].append(field[1])
+
+ if info.get('validpgpkeys') and isinstance(info['validpgpkeys'], str):
+ info['validpgpkeys'] = [info['validpgpkeys']]
+
+ return info
+
def _map_names_as_queries(self, names) -> str:
return '&'.join(['arg[{}]={}'.format(i, n) for i, n in enumerate(names)])
diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py
index 09610e21..6d6c9a14 100644
--- a/bauh/gems/arch/controller.py
+++ b/bauh/gems/arch/controller.py
@@ -14,7 +14,7 @@ from bauh.api.abstract.model import PackageUpdate, PackageHistory, SoftwarePacka
from bauh.api.abstract.view import MessageType
from bauh.commons.html import bold
from bauh.commons.system import SystemProcess, ProcessHandler, new_subprocess, run_cmd, new_root_subprocess
-from bauh.gems.arch import BUILD_DIR, aur, pacman, makepkg, pkgbuild, message, confirmation, disk, git, suggestions
+from bauh.gems.arch import BUILD_DIR, aur, pacman, makepkg, pkgbuild, message, confirmation, disk, git, suggestions, gpg
from bauh.gems.arch.aur import AURClient
from bauh.gems.arch.mapper import ArchDataMapper
from bauh.gems.arch.model import ArchPackage
@@ -254,11 +254,14 @@ class ArchManager(SoftwareManager):
'10_url': pkg.url_download,
}
- res_srcinfo = self.context.http_client.get(URL_SRC_INFO + pkg.name)
+ srcinfo = self.aur_client.get_src_info(pkg.name)
- if res_srcinfo and res_srcinfo.text:
- info['11_dependson'] = '\n'.join(pkgbuild.read_depends_on(res_srcinfo.text))
- info['12_optdepends'] = '\n'.join(pkgbuild.read_optdeps(res_srcinfo.text))
+ if srcinfo:
+ if srcinfo.get('depends'):
+ info['11_dependson'] = srcinfo['depends']
+
+ if srcinfo.get('optdepends'):
+ info['12_optdepends'] = srcinfo['optdepends']
if pkg.pkgbuild:
info['13_pkg_build'] = pkg.pkgbuild
@@ -344,7 +347,7 @@ class ArchManager(SoftwareManager):
def _make_pkg(self, pkgname: str, root_password: str, handler: ProcessHandler, build_dir: str, project_dir: str, dependency: bool, skip_optdeps: bool = False, change_progress: bool = True) -> bool:
self._update_progress(handler.watcher, 50, change_progress)
- if not self._install_missings_deps(pkgname, root_password, handler, project_dir):
+ if not self._install_missings_deps_and_keys(pkgname, root_password, handler, project_dir):
return False
# building main package
@@ -373,30 +376,44 @@ class ArchManager(SoftwareManager):
return False
- def _install_missings_deps(self, pkgname: str, root_password: str, handler: ProcessHandler, pkgdir: str) -> bool:
+ def _install_missings_deps_and_keys(self, pkgname: str, root_password: str, handler: ProcessHandler, pkgdir: str) -> bool:
handler.watcher.change_substatus(self.i18n['arch.checking.deps'].format(bold(pkgname)))
- missing_deps = makepkg.check_missing_deps(pkgdir, handler.watcher)
+ check_res = makepkg.check_missing_deps(pkgdir, handler.watcher)
- if missing_deps:
- depnames = {RE_SPLIT_VERSION.split(dep)[0] for dep in missing_deps}
- dep_mirrors = self._map_mirrors(depnames)
+ if check_res:
- for dep in depnames: # cheking if a dependency could not be found in any mirror
- if dep not in dep_mirrors:
- message.show_dep_not_found(dep, self.i18n, handler.watcher)
+ if check_res.get('gpg_key'):
+ if handler.watcher.request_confirmation(title=self.i18n['arch.aur.install.unknown_key.title'],
+ body=self.i18n['arch.install.aur.unknown_key.body'].format(bold(pkgname), bold(check_res['gpg_key']))):
+ handler.watcher.change_substatus(self.i18n['arch.aur.install.unknown_key.status'].format(bold(check_res['gpg_key'])))
+ if not handler.handle(gpg.receive_key(check_res['gpg_key'])):
+ handler.watcher.show_message(title=self.i18n['error'],
+ body=self.i18n['arch.aur.install.unknown_key.receive_error'].format(bold(check_res['gpg_key'])))
+ return False
+ else:
+ handler.watcher.print(self.i18n['action.cancelled'])
return False
- handler.watcher.change_substatus(self.i18n['arch.missing_deps_found'].format(bold(pkgname)))
+ if check_res.get('missing_deps'):
+ depnames = {RE_SPLIT_VERSION.split(dep)[0] for dep in check_res['missing_deps']}
+ dep_mirrors = self._map_mirrors(depnames)
- if not confirmation.request_install_missing_deps(pkgname, dep_mirrors, handler.watcher, self.i18n):
- handler.watcher.print(self.i18n['action.cancelled'])
- return False
+ for dep in depnames: # cheking if a dependency could not be found in any mirror
+ if dep not in dep_mirrors:
+ message.show_dep_not_found(dep, self.i18n, handler.watcher)
+ return False
- dep_not_installed = self._install_deps(depnames, dep_mirrors, root_password, handler, change_progress=False)
+ handler.watcher.change_substatus(self.i18n['arch.missing_deps_found'].format(bold(pkgname)))
- if dep_not_installed:
- message.show_dep_not_installed(handler.watcher, pkgname, dep_not_installed, self.i18n)
- return False
+ if not confirmation.request_install_missing_deps(pkgname, dep_mirrors, handler.watcher, self.i18n):
+ handler.watcher.print(self.i18n['action.cancelled'])
+ return False
+
+ dep_not_installed = self._install_deps(depnames, dep_mirrors, root_password, handler, change_progress=False)
+
+ if dep_not_installed:
+ message.show_dep_not_installed(handler.watcher, pkgname, dep_not_installed, self.i18n)
+ return False
return True
@@ -478,6 +495,41 @@ class ArchManager(SoftwareManager):
if change_progress:
watcher.change_progress(val)
+ def _import_pgp_keys(self, pkgname: str, root_password: str, handler: ProcessHandler):
+ srcinfo = self.aur_client.get_src_info(pkgname)
+
+ if srcinfo.get('validpgpkeys'):
+ handler.watcher.print(self.i18n['arch.aur.install.verifying_pgp'])
+ keys_to_download = [key for key in srcinfo['validpgpkeys'] if not pacman.verify_pgp_key(key)]
+
+ if keys_to_download:
+ keys_str = ''.join(
+ ['
- {}'.format(k) for k in keys_to_download])
+ msg_body = '{}:
{}
{}'.format(self.i18n['arch.aur.install.pgp.body'].format(bold(pkgname)),
+ keys_str, self.i18n['ask.continue'])
+
+ if handler.watcher.request_confirmation(title=self.i18n['arch.aur.install.pgp.title'], body=msg_body):
+ for key in keys_to_download:
+ handler.watcher.change_substatus(self.i18n['arch.aur.install.pgp.substatus'].format(bold(key)))
+ if not handler.handle(pacman.receive_key(key, root_password)):
+ handler.watcher.show_message(title=self.i18n['error'],
+ body=self.i18n['arch.aur.install.pgp.receive_fail'].format(
+ bold(key)),
+ type_=MessageType.ERROR)
+ return False
+
+ if not handler.handle(pacman.sign_key(key, root_password)):
+ handler.watcher.show_message(title=self.i18n['error'],
+ body=self.i18n['arch.aur.install.pgp.sign_fail'].format(
+ bold(key)),
+ type_=MessageType.ERROR)
+ return False
+
+ handler.watcher.change_substatus(self.i18n['arch.aur.install.pgp.success'])
+ else:
+ handler.watcher.print(self.i18n['action.cancelled'])
+ return False
+
def _install_from_aur(self, pkgname: str, root_password: str, handler: ProcessHandler, dependency: bool, skip_optdeps: bool = False, change_progress: bool = True) -> bool:
app_build_dir = '{}/build_{}'.format(BUILD_DIR, int(time.time()))
diff --git a/bauh/gems/arch/gpg.py b/bauh/gems/arch/gpg.py
new file mode 100644
index 00000000..7b9e3378
--- /dev/null
+++ b/bauh/gems/arch/gpg.py
@@ -0,0 +1,5 @@
+from bauh.commons.system import SystemProcess, new_subprocess
+
+
+def receive_key(key: str) -> SystemProcess:
+ return SystemProcess(new_subprocess(['gpg', '--recv-key', key]), check_error_output=False)
diff --git a/bauh/gems/arch/makepkg.py b/bauh/gems/arch/makepkg.py
index 96218c5b..843aac78 100644
--- a/bauh/gems/arch/makepkg.py
+++ b/bauh/gems/arch/makepkg.py
@@ -1,15 +1,15 @@
import re
-from typing import List
from bauh.api.abstract.handler import ProcessWatcher
from bauh.commons.system import new_subprocess
RE_DEPS_PATTERN = re.compile(r'\n?\s+->\s(.+)\n')
+RE_UNKNOWN_GPG_KEY = re.compile(r'\(unknown public key (\w+)\)')
-def check_missing_deps(pkgdir: str, watcher: ProcessWatcher) -> List[str]:
+def check_missing_deps(pkgdir: str, watcher: ProcessWatcher) -> dict:
depcheck = new_subprocess(['makepkg', '-L', '--check'], cwd=pkgdir)
-
+ res = {}
for o in depcheck.stdout:
if o:
line = o.decode().strip()
@@ -33,4 +33,10 @@ def check_missing_deps(pkgdir: str, watcher: ProcessWatcher) -> List[str]:
error_str = ''.join(error_lines)
if 'Missing dependencies' in error_str:
- return RE_DEPS_PATTERN.findall(error_str)
+ res['missing_deps'] = RE_DEPS_PATTERN.findall(error_str)
+
+ gpg_keys = RE_UNKNOWN_GPG_KEY.findall(error_str)
+ if gpg_keys:
+ res['gpg_key'] = gpg_keys[0]
+
+ return res
diff --git a/bauh/gems/arch/pacman.py b/bauh/gems/arch/pacman.py
index 697f6df6..97ef6408 100644
--- a/bauh/gems/arch/pacman.py
+++ b/bauh/gems/arch/pacman.py
@@ -1,7 +1,9 @@
import re
+import subprocess
from typing import List, Set
-from bauh.commons.system import run_cmd, new_subprocess, new_root_subprocess, SystemProcess
+from bauh.api.abstract.handler import ProcessWatcher
+from bauh.commons.system import run_cmd, new_subprocess, new_root_subprocess, SystemProcess, ProcessHandler
RE_DEPS = re.compile(r'[\w\-_]+:[\s\w_\-\.]+\s+\[\w+\]')
@@ -51,7 +53,8 @@ def get_info_dict(pkg_name: str) -> dict:
info_dict = {}
for info_data in info_list:
attr = info_data[0].lower().strip()
- info_dict[attr] = info_data[1] if '\n' not in info_data[1] else ' '.join([l.strip() for l in info_data[1].split('\n')])
+ info_dict[attr] = info_data[1] if '\n' not in info_data[1] else ' '.join(
+ [l.strip() for l in info_data[1].split('\n')])
if info_dict[attr] == 'None':
info_dict[attr] = None
@@ -76,7 +79,8 @@ def list_and_map_installed() -> dict: # returns a dict with with package names
allinfo = new_subprocess(['pacman', '-Qi'], stdin=installed).stdout # retrieving all installed packages info
pkgs, current_pkg = {'mirrors': {}, 'not_signed': {}}, {}
- for out in new_subprocess(['grep', '-E', '(Name|Description|Version|Validated By)'], stdin=allinfo).stdout: # filtering only the Name and Validated By fields:
+ for out in new_subprocess(['grep', '-E', '(Name|Description|Version|Validated By)'],
+ stdin=allinfo).stdout: # filtering only the Name and Validated By fields:
if out:
line = out.decode()
@@ -90,7 +94,8 @@ def list_and_map_installed() -> dict: # returns a dict with with package names
elif line.startswith('Validated'):
if line.split(':')[1].strip().lower() == 'none':
- pkgs['not_signed'][current_pkg['name']] = {'version': current_pkg['version'], 'description': current_pkg['description']}
+ pkgs['not_signed'][current_pkg['name']] = {'version': current_pkg['version'],
+ 'description': current_pkg['description']}
current_pkg = {}
@@ -98,7 +103,6 @@ def list_and_map_installed() -> dict: # returns a dict with with package names
def install_as_process(pkgpath: str, root_password: str, aur: bool, pkgdir: str = '.') -> SystemProcess:
-
if aur:
cmd = ['pacman', '-U', pkgpath, '--noconfirm'] # pkgpath = install file path
else:
@@ -143,3 +147,23 @@ def list_bin_paths(pkgnames: Set[str]) -> List[str]:
bin_paths.append(line)
return bin_paths
+
+
+def verify_pgp_key(key: str) -> bool:
+ list_key = new_subprocess(['pacman-key', '-l']).stdout
+
+ for out in new_subprocess(['grep', " " + key], stdin=list_key).stdout:
+ if out:
+ line = out.decode().strip()
+ if line and key in line:
+ return True
+
+ return False
+
+
+def receive_key(key: str, root_password: str) -> SystemProcess:
+ return SystemProcess(new_root_subprocess(['pacman-key', '-r', key], root_password=root_password), check_error_output=False)
+
+
+def sign_key(key: str, root_password: str) -> SystemProcess:
+ return SystemProcess(new_root_subprocess(['pacman-key', '--lsign-key', key], root_password=root_password), check_error_output=False)
diff --git a/bauh/gems/arch/resources/locale/en b/bauh/gems/arch/resources/locale/en
index a925b205..c3af4bfa 100644
--- a/bauh/gems/arch/resources/locale/en
+++ b/bauh/gems/arch/resources/locale/en
@@ -47,4 +47,15 @@ arch.install.optdeps.request.body={} was succesfully installed ! There are some
arch.install.optdep.error=Could not install the optional package {}
arch.optdeps.checking=Checking {} optional dependencies
arch.warning.disabled={} seems not to be installed. It will not be possible to manage Arch / AUR packages.
-arch.warning.git={} seems not to be installed. It will not be possible to downgrade AUR packages.
\ No newline at end of file
+arch.warning.git={} seems not to be installed. It will not be possible to downgrade AUR packages.
+arch.aur.install.verifying_pgp=Verifying PGP keys
+arch.aur.install.pgp.title=PGP keys required
+arch.aur.install.pgp.body=To install {} is necessary to receive the following PGP keys
+arch.aur.install.pgp.substatus=Receiving PGP key {}
+arch.aur.install.pgp.success=PGP keys received and signed
+arch.aur.install.pgp.sign_fail=Could not sign PGP key {}
+arch.aur.install.pgp.receive_fail=Could not receive PGP key {}
+arch.aur.install.unknown_key.title=Public key required
+arch.install.aur.unknown_key.body=To continue {} installation is necessary to trust the following public key {}
+arch.aur.install.unknown_key.status=Receiving public key {}
+arch.aur.install.unknown_key.receive_error=Could not receive public key {}
\ No newline at end of file
diff --git a/bauh/gems/arch/resources/locale/es b/bauh/gems/arch/resources/locale/es
index b4e81370..5c72e2b2 100644
--- a/bauh/gems/arch/resources/locale/es
+++ b/bauh/gems/arch/resources/locale/es
@@ -79,4 +79,15 @@ arch.install.optdeps.request.body=¡{} se instaló correctamente! También hay a
arch.install.optdep.error=No se pudo instalar el paquete opcional {}
arch.optdeps.checking=Verificando las dependencias opcionales de {}
arch.warning.disabled={} parece no estar instalado. No será posible administrar paquetes Arch / AUR.
-arch.warning.git={} parece no estar instalado. No será posible revertir las versiones de paquetes Arch / AUR.
\ No newline at end of file
+arch.warning.git={} parece no estar instalado. No será posible revertir las versiones de paquetes Arch / AUR.
+arch.aur.install.verifying_pgp=Verificando claves PGP
+arch.aur.install.pgp.title=Claves PGP necesarias
+arch.aur.install.pgp.body=Para instalar {} es necesario recibir las siguientes claves PGP
+arch.aur.install.pgp.substatus=Recibiendo clave PGP {}
+arch.aur.install.pgp.success=Claves PGP recibidas y firmadas
+arch.aur.install.pgp.sign_fail=No fue posible firmar la clave PGP {}
+arch.aur.install.pgp.sign_fail=No fue posible recibir la clave PGP {}
+arch.aur.install.unknown_key.title=Clave pública necesaria
+arch.aur.install.unknown_key.status=Recibiendo la clave pública {}
+arch.aur.install.unknown_key.receive_error=No fue posible recibir la clave pública {}
+arch.install.aur.unknown_key.body=Para continuar la instalación de {} es necesario confiar en la siguiente clave pública {}
\ No newline at end of file
diff --git a/bauh/gems/arch/resources/locale/pt b/bauh/gems/arch/resources/locale/pt
index 803fc61c..03760cc0 100644
--- a/bauh/gems/arch/resources/locale/pt
+++ b/bauh/gems/arch/resources/locale/pt
@@ -79,4 +79,15 @@ arch.install.optdeps.request.body={} foi instalado com sucesso ! Existem alguns
arch.install.optdep.error=Não foi possível instalar o pacote opcional {}
arch.optdeps.checking=Verificando as dependências opcionais de {}
arch.warning.disabled={} parece não estar instalado. Não será possível gerenciar pacotes Arch / AUR.
-arch.warning.git={} parece não estar instalado. Não será possível reverter versões de pacotes Arch / AUR.
\ No newline at end of file
+arch.warning.git={} parece não estar instalado. Não será possível reverter versões de pacotes Arch / AUR.
+arch.aur.install.verifying_pgp=Verificando chaves PGP
+arch.aur.install.pgp.title=Chaves PGP necessárias
+arch.aur.install.pgp.body=Para instalar {} é necessário receber as seguintes chaves PGP
+arch.aur.install.pgp.substatus=Recebendo chave PGP {}
+arch.aur.install.pgp.success=Chaves PGP recebidas e assinadas
+arch.aur.install.pgp.sign_fail=Não foi possível assinar a chave PGP {}
+arch.aur.install.pgp.receive_fail=Não foi possível receber a chave PGP {}
+arch.aur.install.unknown_key.title=Chave pública necessária
+arch.install.aur.unknown_key.body=Para continuar a instalação de {} é ncessário confiar na seguinte chave pública {}
+arch.aur.install.unknown_key.status=Recebendo a chave pública {}
+arch.aur.install.unknown_key.receive_error=Não fui possível receber a chave pública {}
\ No newline at end of file