feat: password service

This commit is contained in:
Ruidy 2021-08-03 20:44:27 +02:00
parent 668ac34dbc
commit d3116898e6
10 changed files with 90 additions and 66 deletions

19
Pipfile
View file

@ -4,17 +4,18 @@ verify_ssl = true
name = "pypi" name = "pypi"
[packages] [packages]
pydantic = "==1.8.2" pydantic = "~=1.8.2"
typer = "==0.3.2" typer = "~=0.3.2"
[dev-packages] [dev-packages]
bandit = "==1.7.0" bandit = "~=1.7.0"
black = "==21.6b0" black = "~=21.6b0"
flake8 = "==3.9.2" faker = "~=8.11.0"
mypy = "==0.910" flake8 = "~=3.9.2"
pytest = "==6.2.4" mypy = "~=0.910"
pytest-cov = "==2.12.1" pytest = "~=6.2.4"
vulture = "==2.3" pytest-cov = "~=2.12.1"
vulture = "~=2.3"
[scripts] [scripts]
passgen = "python -m app" passgen = "python -m app"

101
Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "b1bba35cf3809622ba68e5c6d3e45856f177c2de92180eba2caf325ff287149c" "sha256": "e020691d3e4002190842b05b21ca5fcdc744e8b61f204413510cb82fd0aebdac"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -145,6 +145,14 @@
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==6.0b1" "version": "==6.0b1"
}, },
"faker": {
"hashes": [
"sha256:3e737576ff50cd98dfed643d6b3fd63194eca9df00e7f595960fe7da5220723d",
"sha256:b9e81e9da3dda3ac54189e034cfb943de576a259caeb226ccab43fcbcf6a7891"
],
"index": "pypi",
"version": "==8.11.0"
},
"flake8": { "flake8": {
"hashes": [ "hashes": [
"sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b", "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b",
@ -298,6 +306,14 @@
"index": "pypi", "index": "pypi",
"version": "==2.12.1" "version": "==2.12.1"
}, },
"python-dateutil": {
"hashes": [
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.2"
},
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [
"sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf",
@ -335,49 +351,41 @@
}, },
"regex": { "regex": {
"hashes": [ "hashes": [
"sha256:0eb2c6e0fcec5e0f1d3bcc1133556563222a2ffd2211945d7b1480c1b1a42a6f", "sha256:026beb631097a4a3def7299aa5825e05e057de3c6d72b139c37813bfa351274b",
"sha256:15dddb19823f5147e7517bb12635b3c82e6f2a3a6b696cc3e321522e8b9308ad", "sha256:14caacd1853e40103f59571f169704367e79fb78fac3d6d09ac84d9197cadd16",
"sha256:173bc44ff95bc1e96398c38f3629d86fa72e539c79900283afa895694229fe6a", "sha256:16d9eaa8c7e91537516c20da37db975f09ac2e7772a0694b245076c6d68f85da",
"sha256:1c78780bf46d620ff4fff40728f98b8afd8b8e35c3efd638c7df67be2d5cddbf", "sha256:18fdc51458abc0a974822333bd3a932d4e06ba2a3243e9a1da305668bd62ec6d",
"sha256:2366fe0479ca0e9afa534174faa2beae87847d208d457d200183f28c74eaea59", "sha256:28e8af338240b6f39713a34e337c3813047896ace09d51593d6907c66c0708ba",
"sha256:2bceeb491b38225b1fee4517107b8491ba54fba77cf22a12e996d96a3c55613d", "sha256:3835de96524a7b6869a6c710b26c90e94558c31006e96ca3cf6af6751b27dca1",
"sha256:2ddeabc7652024803666ea09f32dd1ed40a0579b6fbb2a213eba590683025895", "sha256:3905c86cc4ab6d71635d6419a6f8d972cab7c634539bba6053c47354fd04452c",
"sha256:2fe5e71e11a54e3355fa272137d521a40aace5d937d08b494bed4529964c19c4", "sha256:3c09d88a07483231119f5017904db8f60ad67906efac3f1baa31b9b7f7cca281",
"sha256:319eb2a8d0888fa6f1d9177705f341bc9455a2c8aca130016e52c7fe8d6c37a3", "sha256:4551728b767f35f86b8e5ec19a363df87450c7376d7419c3cac5b9ceb4bce576",
"sha256:3f5716923d3d0bfb27048242a6e0f14eecdb2e2a7fac47eda1d055288595f222", "sha256:459bbe342c5b2dec5c5223e7c363f291558bc27982ef39ffd6569e8c082bdc83",
"sha256:422dec1e7cbb2efbbe50e3f1de36b82906def93ed48da12d1714cabcd993d7f0", "sha256:4f421e3cdd3a273bace013751c345f4ebeef08f05e8c10757533ada360b51a39",
"sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c", "sha256:577737ec3d4c195c4aef01b757905779a9e9aee608fa1cf0aec16b5576c893d3",
"sha256:4f64fc59fd5b10557f6cd0937e1597af022ad9b27d454e182485f1db3008f417", "sha256:57fece29f7cc55d882fe282d9de52f2f522bb85290555b49394102f3621751ee",
"sha256:564a4c8a29435d1f2256ba247a0315325ea63335508ad8ed938a4f14c4116a5d", "sha256:7976d410e42be9ae7458c1816a416218364e06e162b82e42f7060737e711d9ce",
"sha256:59506c6e8bd9306cd8a41511e32d16d5d1194110b8cfe5a11d102d8b63cf945d", "sha256:85f568892422a0e96235eb8ea6c5a41c8ccbf55576a2260c0160800dbd7c4f20",
"sha256:598c0a79b4b851b922f504f9f39a863d83ebdfff787261a5ed061c21e67dd761", "sha256:8764a78c5464ac6bde91a8c87dd718c27c1cabb7ed2b4beaf36d3e8e390567f9",
"sha256:59c00bb8dd8775473cbfb967925ad2c3ecc8886b3b2d0c90a8e2707e06c743f0", "sha256:8935937dad2c9b369c3d932b0edbc52a62647c2afb2fafc0c280f14a8bf56a6a",
"sha256:6110bab7eab6566492618540c70edd4d2a18f40ca1d51d704f1d81c52d245026", "sha256:8fe58d9f6e3d1abf690174fd75800fda9bdc23d2a287e77758dc0e8567e38ce6",
"sha256:6afe6a627888c9a6cfbb603d1d017ce204cebd589d66e0703309b8048c3b0854", "sha256:937b20955806381e08e54bd9d71f83276d1f883264808521b70b33d98e4dec5d",
"sha256:791aa1b300e5b6e5d597c37c346fb4d66422178566bbb426dd87eaae475053fb", "sha256:9569da9e78f0947b249370cb8fadf1015a193c359e7e442ac9ecc585d937f08d",
"sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d", "sha256:a3b73390511edd2db2d34ff09aa0b2c08be974c71b4c0505b4a048d5dc128c2b",
"sha256:875c355360d0f8d3d827e462b29ea7682bf52327d500a4f837e934e9e4656068", "sha256:a4eddbe2a715b2dd3849afbdeacf1cc283160b24e09baf64fa5675f51940419d",
"sha256:89e5528803566af4df368df2d6f503c84fbfb8249e6631c7b025fe23e6bd0cde", "sha256:a5c6dbe09aff091adfa8c7cfc1a0e83fdb8021ddb2c183512775a14f1435fe16",
"sha256:99d8ab206a5270c1002bfcf25c51bf329ca951e5a169f3b43214fdda1f0b5f0d", "sha256:b63e3571b24a7959017573b6455e05b675050bbbea69408f35f3cb984ec54363",
"sha256:9a854b916806c7e3b40e6616ac9e85d3cdb7649d9e6590653deb5b341a736cec", "sha256:bb350eb1060591d8e89d6bac4713d41006cd4d479f5e11db334a48ff8999512f",
"sha256:b85ac458354165405c8a84725de7bbd07b00d9f72c31a60ffbf96bb38d3e25fa", "sha256:bf6d987edd4a44dd2fa2723fca2790f9442ae4de2c8438e53fcb1befdf5d823a",
"sha256:bc84fb254a875a9f66616ed4538542fb7965db6356f3df571d783f7c8d256edd", "sha256:bfa6a679410b394600eafd16336b2ce8de43e9b13f7fb9247d84ef5ad2b45e91",
"sha256:c92831dac113a6e0ab28bc98f33781383fe294df1a2c3dfd1e850114da35fd5b", "sha256:c856ec9b42e5af4fe2d8e75970fcc3a2c15925cbcc6e7a9bcb44583b10b95e80",
"sha256:cbe23b323988a04c3e5b0c387fe3f8f363bf06c0680daf775875d979e376bd26", "sha256:cea56288eeda8b7511d507bbe7790d89ae7049daa5f51ae31a35ae3c05408531",
"sha256:ccb3d2190476d00414aab36cca453e4596e8f70a206e2aa8db3d495a109153d2", "sha256:ea212df6e5d3f60341aef46401d32fcfded85593af1d82b8b4a7a68cd67fdd6b",
"sha256:d8bbce0c96462dbceaa7ac4a7dfbbee92745b801b24bce10a98d2f2b1ea9432f", "sha256:f35567470ee6dbfb946f069ed5f5615b40edcbb5f1e6e1d3d2b114468d505fc6",
"sha256:db2b7df831c3187a37f3bb80ec095f249fa276dbe09abd3d35297fc250385694", "sha256:fbc20975eee093efa2071de80df7f972b7b35e560b213aafabcec7c0bd00bd8c",
"sha256:e586f448df2bbc37dfadccdb7ccd125c62b4348cb90c10840d695592aa1b29e0", "sha256:ff4a8ad9638b7ca52313d8732f37ecd5fd3c8e3aff10a8ccb93176fd5b3812f6"
"sha256:e5983c19d0beb6af88cb4d47afb92d96751fb3fa1784d8785b1cdf14c6519407",
"sha256:e6a1e5ca97d411a461041d057348e578dc344ecd2add3555aedba3b408c9f874",
"sha256:eaf58b9e30e0e546cdc3ac06cf9165a1ca5b3de8221e9df679416ca667972035",
"sha256:ed693137a9187052fc46eedfafdcb74e09917166362af4cc4fddc3b31560e93d",
"sha256:edd1a68f79b89b0c57339bce297ad5d5ffcc6ae7e1afdb10f1947706ed066c9c",
"sha256:f080248b3e029d052bf74a897b9d74cfb7643537fbde97fe8225a6467fb559b5",
"sha256:f9392a4555f3e4cb45310a65b403d86b589adc773898c25a39184b1ba4db8985",
"sha256:f98dc35ab9a749276f1a4a38ab3e0e2ba1662ce710f6530f5b0a6656f1c32b58"
], ],
"version": "==2021.7.6" "version": "==2021.8.3"
}, },
"six": { "six": {
"hashes": [ "hashes": [
@ -403,6 +411,13 @@
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==3.3.0" "version": "==3.3.0"
}, },
"text-unidecode": {
"hashes": [
"sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8",
"sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"
],
"version": "==1.3"
},
"toml": { "toml": {
"hashes": [ "hashes": [
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",

View file

@ -7,8 +7,8 @@ class DB:
self.connection = sqlite3.connect(db_str) self.connection = sqlite3.connect(db_str)
self.cursor = self.connection.cursor() self.cursor = self.connection.cursor()
self.execute( self.execute(
"CREATE TABLE IF NOT EXISTS passwords (id integer PRIMARY KEY , service text UNIQUE NOT NULL, " "CREATE TABLE IF NOT EXISTS passwords (id integer PRIMARY KEY, "
"password text NOT NULL)" "service text UNIQUE NOT NULL, password text NOT NULL)"
) )
def commit(self) -> None: def commit(self) -> None:

View file

@ -12,6 +12,7 @@ app = typer.Typer()
@app.command() @app.command()
def save( def save(
service: str = typer.Argument(..., help="Name of the service associated to the password"),
length: int = typer.Option( length: int = typer.Option(
8, 8,
"--length", "--length",
@ -41,6 +42,7 @@ def save(
sqlite_repo = sqlite.get_instance() sqlite_repo = sqlite.get_instance()
seed = r.randint(0, 100) if random else 0 seed = r.randint(0, 100) if random else 0
options = pass_gen.PassGenOptions( options = pass_gen.PassGenOptions(
service=service,
seed=seed, seed=seed,
length=length, length=length,
symbols=symbols, symbols=symbols,
@ -63,4 +65,4 @@ def save(
def read() -> None: def read() -> None:
sqlite_repo = sqlite.get_instance() sqlite_repo = sqlite.get_instance()
stored_passwords = sqlite_repo.list_all() stored_passwords = sqlite_repo.list_all()
typer.echo(*[f"{p.service}: {p.password.get_secret_value()}" for p in stored_passwords]) typer.echo([f"{p.service}: {p.password.get_secret_value()}" for p in stored_passwords])

View file

@ -6,7 +6,7 @@ from app.models.password import Password
class FakeRepository: class FakeRepository:
def save(self, password: str) -> None: def save(self, service: str, password: str) -> None:
... ...
def list_all(self) -> list[Password]: def list_all(self) -> list[Password]:

View file

@ -7,11 +7,11 @@ class PasswordRepository:
def __init__(self, db: DBConnector) -> None: def __init__(self, db: DBConnector) -> None:
self.db = db self.db = db
def save(self, password: str) -> None: def save(self, service: str, password: str) -> None:
try: try:
self.db.execute( self.db.execute(
"INSERT INTO passwords VALUES (null, :service, :password)", "INSERT INTO passwords VALUES (null, :service, :password)",
{"service": "service", "password": password}, {"service": service, "password": password},
) )
self.db.commit() self.db.commit()

View file

@ -4,7 +4,7 @@ from app.models.password import Password
class Repository(Protocol): class Repository(Protocol):
def save(self, password: str) -> None: def save(self, service: str, password: str) -> None:
... ...
def list_all(self) -> list[Password]: def list_all(self) -> list[Password]:

View file

@ -4,10 +4,12 @@ from typing import Protocol
from pydantic import BaseModel from pydantic import BaseModel
from app.models.password import Password
from app.repositories.type import Repository from app.repositories.type import Repository
class PassGenOptions(BaseModel): class PassGenOptions(BaseModel):
service: str
seed: int seed: int
length: int = 8 length: int = 8
symbols: bool = False symbols: bool = False
@ -18,7 +20,7 @@ def generate_password(repo: Repository, options: PassGenOptions) -> str:
characters = _build_characters(symbols=options.symbols, numbers=options.numbers) characters = _build_characters(symbols=options.symbols, numbers=options.numbers)
random_generator = _new_random_generator(options.seed) random_generator = _new_random_generator(options.seed)
password = "".join(random_generator.sample(characters, options.length)) password = "".join(random_generator.sample(characters, options.length))
repo.save(password) repo.save(options.service, password)
return password return password
@ -41,5 +43,5 @@ def _build_characters(symbols: bool, numbers: bool) -> str:
) )
def list_all_saved_passwords(repo: Repository) -> list[str]: def list_all_saved_passwords(repo: Repository) -> list[Password]:
return repo.list_all() return repo.list_all()

View file

@ -1,10 +1,13 @@
from typing import Any from typing import Any
from faker import Factory
from app.main import app from app.main import app
from typer.testing import CliRunner, Result from typer.testing import CliRunner, Result
runner = CliRunner() runner = CliRunner()
faker = Factory.create()
def test_cli_print_password() -> None: def test_cli_print_password() -> None:
@ -47,7 +50,7 @@ def test_cli_can_save_to_db() -> None:
def _run_cli(*args: Any) -> Result: def _run_cli(*args: Any) -> Result:
result = runner.invoke(app, ["save", "--no-random", *args]) result = runner.invoke(app, ["save", faker.pystr(), "--no-random", *args])
assert result.exit_code == 0 assert result.exit_code == 0
return result return result

View file

@ -6,6 +6,7 @@ from app.repositories.fake import FakeRepository
from app.usecases.pass_gen import PassGenOptions, generate_password, list_all_saved_passwords from app.usecases.pass_gen import PassGenOptions, generate_password, list_all_saved_passwords
fake_repo = FakeRepository.get_instance() fake_repo = FakeRepository.get_instance()
service = "service_name"
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -16,7 +17,7 @@ fake_repo = FakeRepository.get_instance()
], ],
) )
def test_can_generate_random_password(seed: int, expected: str) -> None: def test_can_generate_random_password(seed: int, expected: str) -> None:
options = PassGenOptions(seed=seed) options = PassGenOptions(seed=seed, service=service)
assert generate_password(fake_repo, options) == expected assert generate_password(fake_repo, options) == expected
@ -28,7 +29,7 @@ def test_can_generate_random_password(seed: int, expected: str) -> None:
], ],
) )
def test_control_password_length(seed: int, length: int, expected: str) -> None: def test_control_password_length(seed: int, length: int, expected: str) -> None:
options = PassGenOptions(seed=seed, length=length) options = PassGenOptions(seed=seed, length=length, service=service)
assert generate_password(fake_repo, options) == expected assert generate_password(fake_repo, options) == expected
@ -40,7 +41,7 @@ def test_control_password_length(seed: int, length: int, expected: str) -> None:
], ],
) )
def test_password_can_contain_symbols(seed: int, symbols: bool, expected: str) -> None: def test_password_can_contain_symbols(seed: int, symbols: bool, expected: str) -> None:
options = PassGenOptions(seed=seed, symbols=symbols) options = PassGenOptions(seed=seed, symbols=symbols, service=service)
assert generate_password(fake_repo, options) == expected assert generate_password(fake_repo, options) == expected
@ -52,7 +53,7 @@ def test_password_can_contain_symbols(seed: int, symbols: bool, expected: str) -
], ],
) )
def test_password_can_contain_numbers(seed: int, numbers: bool, expected: str) -> None: def test_password_can_contain_numbers(seed: int, numbers: bool, expected: str) -> None:
options = PassGenOptions(seed=seed, numbers=numbers) options = PassGenOptions(seed=seed, numbers=numbers, service=service)
assert generate_password(fake_repo, options) == expected assert generate_password(fake_repo, options) == expected