mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-03-12 01:19:02 +00:00
Initial Commit
This commit is contained in:
87
unshackle/core/credential.py
Normal file
87
unshackle/core/credential.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
|
||||
|
||||
class Credential:
|
||||
"""Username (or Email) and Password Credential."""
|
||||
|
||||
def __init__(self, username: str, password: str, extra: Optional[str] = None):
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.extra = extra
|
||||
self.sha1 = hashlib.sha1(self.dumps().encode()).hexdigest()
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return bool(self.username) and bool(self.password)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.dumps()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{name}({items})".format(
|
||||
name=self.__class__.__name__, items=", ".join([f"{k}={repr(v)}" for k, v in self.__dict__.items()])
|
||||
)
|
||||
|
||||
def dumps(self) -> str:
|
||||
"""Return credential data as a string."""
|
||||
return f"{self.username}:{self.password}" + (f":{self.extra}" if self.extra else "")
|
||||
|
||||
def dump(self, path: Union[Path, str]) -> int:
|
||||
"""Write credential data to a file."""
|
||||
if isinstance(path, str):
|
||||
path = Path(path)
|
||||
return path.write_text(self.dumps(), encoding="utf8")
|
||||
|
||||
def as_base64(self, with_extra: bool = False, encode_password: bool = False, encode_extra: bool = False) -> str:
|
||||
"""
|
||||
Dump Credential as a Base64-encoded string in Basic Authorization style.
|
||||
encode_password and encode_extra will also Base64-encode the password and extra respectively.
|
||||
"""
|
||||
value = f"{self.username}:"
|
||||
if encode_password:
|
||||
value += base64.b64encode(self.password.encode()).decode()
|
||||
else:
|
||||
value += self.password
|
||||
if with_extra and self.extra:
|
||||
if encode_extra:
|
||||
value += f":{base64.b64encode(self.extra.encode()).decode()}"
|
||||
else:
|
||||
value += f":{self.extra}"
|
||||
return base64.b64encode(value.encode()).decode()
|
||||
|
||||
@classmethod
|
||||
def loads(cls, text: str) -> Credential:
|
||||
"""
|
||||
Load credential from a text string.
|
||||
|
||||
Format: {username}:{password}
|
||||
Rules:
|
||||
Only one Credential must be in this text contents.
|
||||
All whitespace before and after all text will be removed.
|
||||
Any whitespace between text will be kept and used.
|
||||
The credential can be spanned across one or multiple lines as long as it
|
||||
abides with all the above rules and the format.
|
||||
|
||||
Example that follows the format and rules:
|
||||
`\tJohnd\noe@gm\n\rail.com\n:Pass1\n23\n\r \t \t`
|
||||
>>>Credential(username='Johndoe@gmail.com', password='Pass123')
|
||||
"""
|
||||
text = "".join([x.strip() for x in text.splitlines(keepends=False)]).strip()
|
||||
credential = re.fullmatch(r"^([^:]+?):([^:]+?)(?::(.+))?$", text)
|
||||
if credential:
|
||||
return cls(*credential.groups())
|
||||
raise ValueError("No credentials found in text string. Expecting the format `username:password`")
|
||||
|
||||
@classmethod
|
||||
def load(cls, path: Path) -> Credential:
|
||||
"""
|
||||
Load Credential from a file path.
|
||||
Use Credential.loads() for loading from text content and seeing the rules and
|
||||
format expected to be found in the URIs contents.
|
||||
"""
|
||||
return cls.loads(path.read_text("utf8"))
|
||||
Reference in New Issue
Block a user