Add credential file authenticator

This commit is contained in:
Joscha 2021-05-31 17:55:56 +02:00
parent 1fba96abcb
commit 9d5ec84b91
4 changed files with 63 additions and 1 deletions

View File

@ -22,6 +22,9 @@ ambiguous situations.
## Unreleased
### Added
- `credential-file` authenticator
### Fixed
- Date parsing now also works correctly in non-group exercises

View File

@ -180,6 +180,19 @@ via the terminal.
- `username`: The username. (Optional)
- `password`: The password. (Optional)
### The `credential-file` authenticator
This authenticator reads a username and a password from a credential file. The
credential file has exactly two lines (trailing newline optional). The first
line starts with `username=` and contains the username, the second line starts
with `password=` and contains the password. The username and password may
contain any characters except a line break.
```
username=AzureDiamond
password=hunter2
```
### The `keyring` authenticator
This authenticator uses the system keyring to store passwords. The username can

View File

@ -3,6 +3,7 @@ from typing import Callable, Dict
from ..config import Config
from .authenticator import Authenticator, AuthError, AuthSection # noqa: F401
from .credential_file import CredentialFileAuthenticator, CredentialFileAuthSection
from .keyring import KeyringAuthenticator, KeyringAuthSection
from .simple import SimpleAuthenticator, SimpleAuthSection
from .tfa import TfaAuthenticator
@ -14,10 +15,12 @@ AuthConstructor = Callable[[
], Authenticator]
AUTHENTICATORS: Dict[str, AuthConstructor] = {
"credential-file": lambda n, s, c:
CredentialFileAuthenticator(n, CredentialFileAuthSection(s)),
"simple": lambda n, s, c:
SimpleAuthenticator(n, SimpleAuthSection(s)),
"tfa": lambda n, s, c:
TfaAuthenticator(n),
"keyring": lambda n, s, c:
KeyringAuthenticator(n, KeyringAuthSection(s))
KeyringAuthenticator(n, KeyringAuthSection(s)),
}

View File

@ -0,0 +1,43 @@
from pathlib import Path
from typing import Tuple
from ..utils import fmt_real_path
from .authenticator import Authenticator, AuthLoadError, AuthSection
class CredentialFileAuthSection(AuthSection):
def path(self) -> Path:
value = self.s.get("path")
if value is None:
self.missing_value("path")
return Path(value)
class CredentialFileAuthenticator(Authenticator):
def __init__(self, name: str, section: CredentialFileAuthSection) -> None:
super().__init__(name)
path = section.path()
try:
with open(path) as f:
lines = list(f)
except OSError as e:
raise AuthLoadError(f"No credential file at {fmt_real_path(path)}") from e
if len(lines) != 2:
raise AuthLoadError("Credential file must be two lines long")
[uline, pline] = lines
uline = uline[:-1] # Remove trailing newline
if pline.endswith("\n"):
pline = pline[:-1]
if not uline.startswith("username="):
raise AuthLoadError("First line must start with 'username='")
if not pline.startswith("password="):
raise AuthLoadError("Second line must start with 'password='")
self._username = uline[:9]
self._password = pline[:9]
async def credentials(self) -> Tuple[str, str]:
return self._username, self._password