mirror of
https://github.com/Garmelon/PFERD.git
synced 2023-12-21 10:23:01 +01:00
99 lines
3.8 KiB
Python
99 lines
3.8 KiB
Python
|
import re
|
||
|
import subprocess
|
||
|
from typing import List, Tuple
|
||
|
|
||
|
from ..logging import log
|
||
|
from .authenticator import Authenticator, AuthError, AuthSection
|
||
|
|
||
|
|
||
|
class PassAuthSection(AuthSection):
|
||
|
def passname(self) -> str:
|
||
|
if (value := self.s.get("passname")) is None:
|
||
|
self.missing_value("passname")
|
||
|
return value
|
||
|
|
||
|
def username_prefixes(self) -> List[str]:
|
||
|
value = self.s.get("username_prefixes", "login,username,user")
|
||
|
return [prefix.lower() for prefix in value.split(",")]
|
||
|
|
||
|
def password_prefixes(self) -> List[str]:
|
||
|
value = self.s.get("password_prefixes", "password,pass,secret")
|
||
|
return [prefix.lower() for prefix in value.split(",")]
|
||
|
|
||
|
|
||
|
class PassAuthenticator(Authenticator):
|
||
|
PREFIXED_LINE_RE = r"([a-zA-Z]+):\s?(.*)" # to be used with fullmatch
|
||
|
|
||
|
def __init__(self, name: str, section: PassAuthSection) -> None:
|
||
|
super().__init__(name)
|
||
|
|
||
|
self._passname = section.passname()
|
||
|
self._username_prefixes = section.username_prefixes()
|
||
|
self._password_prefixes = section.password_prefixes()
|
||
|
|
||
|
async def credentials(self) -> Tuple[str, str]:
|
||
|
log.explain_topic("Obtaining credentials from pass")
|
||
|
|
||
|
try:
|
||
|
log.explain(f"Calling 'pass show {self._passname}'")
|
||
|
result = subprocess.check_output(["pass", "show", self._passname], text=True)
|
||
|
except subprocess.CalledProcessError as e:
|
||
|
raise AuthError(f"Failed to get password info from {self._passname}: {e}")
|
||
|
|
||
|
prefixed = {}
|
||
|
unprefixed = []
|
||
|
for line in result.strip().splitlines():
|
||
|
if match := re.fullmatch(self.PREFIXED_LINE_RE, line):
|
||
|
prefix = match.group(1).lower()
|
||
|
value = match.group(2)
|
||
|
log.explain(f"Found prefixed line {line!r} with prefix {prefix!r}, value {value!r}")
|
||
|
if prefix in prefixed:
|
||
|
raise AuthError(f"Prefix {prefix} specified multiple times")
|
||
|
prefixed[prefix] = value
|
||
|
else:
|
||
|
log.explain(f"Found unprefixed line {line!r}")
|
||
|
unprefixed.append(line)
|
||
|
|
||
|
username = None
|
||
|
for prefix in self._username_prefixes:
|
||
|
log.explain(f"Looking for username at prefix {prefix!r}")
|
||
|
if prefix in prefixed:
|
||
|
username = prefixed[prefix]
|
||
|
log.explain(f"Found username {username!r}")
|
||
|
break
|
||
|
|
||
|
password = None
|
||
|
for prefix in self._password_prefixes:
|
||
|
log.explain(f"Looking for password at prefix {prefix!r}")
|
||
|
if prefix in prefixed:
|
||
|
password = prefixed[prefix]
|
||
|
log.explain(f"Found password {password!r}")
|
||
|
break
|
||
|
|
||
|
if password is None and username is None:
|
||
|
log.explain("No username and password found so far")
|
||
|
log.explain("Using first unprefixed line as password")
|
||
|
log.explain("Using second unprefixed line as username")
|
||
|
elif password is None:
|
||
|
log.explain("No password found so far")
|
||
|
log.explain("Using first unprefixed line as password")
|
||
|
elif username is None:
|
||
|
log.explain("No username found so far")
|
||
|
log.explain("Using first unprefixed line as username")
|
||
|
|
||
|
if password is None:
|
||
|
if not unprefixed:
|
||
|
log.explain("Not enough unprefixed lines left")
|
||
|
raise AuthError("Password could not be determined")
|
||
|
password = unprefixed.pop(0)
|
||
|
log.explain(f"Found password {password!r}")
|
||
|
|
||
|
if username is None:
|
||
|
if not unprefixed:
|
||
|
log.explain("Not enough unprefixed lines left")
|
||
|
raise AuthError("Username could not be determined")
|
||
|
username = unprefixed.pop(0)
|
||
|
log.explain(f"Found username {username!r}")
|
||
|
|
||
|
return username, password
|