This commit is contained in:
scito
2022-12-24 01:59:35 +01:00
parent 483fcc0163
commit f4934192ae
9 changed files with 303 additions and 389 deletions

10
Pipfile
View File

@@ -4,15 +4,11 @@ verify_ssl = true
name = "pypi" name = "pypi"
[packages] [packages]
protobuf = "==4.21.12" protobuf = "*"
qrcode = "*" qrcode = "*"
pillow = "*" pillow = "*"
wheel = "==0.38.4" qreader = "*"
pytest = "==7.2.0" opencv-python = "*"
flake8 = "==6.0.0"
pylint = "==2.15.9"
qreader = "==1.3.1"
opencv-python = "==4.6.0.66"
[dev-packages] [dev-packages]
pytest = "*" pytest = "*"

278
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "0aea0eade4cf314b23a91c56582b66626a03b15f8e089b568ad55186cf8e6163" "sha256": "2f4059c8dbac6be85b1e3b2c2032b884d48dc6a7fd520ffdebb951e23246a23e"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -16,102 +16,6 @@
] ]
}, },
"default": { "default": {
"astroid": {
"hashes": [
"sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907",
"sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7"
],
"markers": "python_full_version >= '3.7.2'",
"version": "==2.12.13"
},
"attrs": {
"hashes": [
"sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836",
"sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"
],
"markers": "python_version >= '3.6'",
"version": "==22.2.0"
},
"colorama": {
"hashes": [
"sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
"sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
],
"markers": "sys_platform == 'win32'",
"version": "==0.4.6"
},
"dill": {
"hashes": [
"sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0",
"sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"
],
"markers": "python_version < '3.11'",
"version": "==0.3.6"
},
"exceptiongroup": {
"hashes": [
"sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828",
"sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"
],
"markers": "python_version < '3.11'",
"version": "==1.0.4"
},
"flake8": {
"hashes": [
"sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7",
"sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"
],
"index": "pypi",
"version": "==6.0.0"
},
"iniconfig": {
"hashes": [
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
"sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
],
"version": "==1.1.1"
},
"isort": {
"hashes": [
"sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6",
"sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b"
],
"markers": "python_full_version >= '3.7.0'",
"version": "==5.11.4"
},
"lazy-object-proxy": {
"hashes": [
"sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada",
"sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d",
"sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7",
"sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe",
"sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd",
"sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c",
"sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858",
"sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288",
"sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec",
"sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f",
"sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891",
"sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c",
"sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25",
"sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156",
"sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8",
"sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f",
"sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e",
"sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0",
"sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b"
],
"markers": "python_version >= '3.7'",
"version": "==1.8.0"
},
"mccabe": {
"hashes": [
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
"sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
],
"markers": "python_version >= '3.6'",
"version": "==0.7.0"
},
"numpy": { "numpy": {
"hashes": [ "hashes": [
"sha256:0104d8adaa3a4cc60c2777cab5196593bf8a7f416eda133be1f3803dd0838886", "sha256:0104d8adaa3a4cc60c2777cab5196593bf8a7f416eda133be1f3803dd0838886",
@@ -143,7 +47,7 @@
"sha256:f9168790149f917ad8e3cf5047b353fefef753bd50b07c547da0bdf30bc15d91", "sha256:f9168790149f917ad8e3cf5047b353fefef753bd50b07c547da0bdf30bc15d91",
"sha256:fe44e925c68fb5e8db1334bf30ac1a1b6b963b932a19cf41d2e899cf02f36aab" "sha256:fe44e925c68fb5e8db1334bf30ac1a1b6b963b932a19cf41d2e899cf02f36aab"
], ],
"markers": "python_version >= '3.9'", "markers": "python_version >= '3.10'",
"version": "==1.24.0" "version": "==1.24.0"
}, },
"opencv-python": { "opencv-python": {
@@ -159,14 +63,6 @@
"index": "pypi", "index": "pypi",
"version": "==4.6.0.66" "version": "==4.6.0.66"
}, },
"packaging": {
"hashes": [
"sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3",
"sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"
],
"markers": "python_version >= '3.7'",
"version": "==22.0"
},
"pillow": { "pillow": {
"hashes": [ "hashes": [
"sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040", "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040",
@@ -234,22 +130,6 @@
"index": "pypi", "index": "pypi",
"version": "==9.3.0" "version": "==9.3.0"
}, },
"platformdirs": {
"hashes": [
"sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca",
"sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e"
],
"markers": "python_version >= '3.7'",
"version": "==2.6.0"
},
"pluggy": {
"hashes": [
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
],
"markers": "python_version >= '3.6'",
"version": "==1.0.0"
},
"protobuf": { "protobuf": {
"hashes": [ "hashes": [
"sha256:1f22ac0ca65bb70a876060d96d914dae09ac98d114294f77584b0d2644fa9c30", "sha256:1f22ac0ca65bb70a876060d96d914dae09ac98d114294f77584b0d2644fa9c30",
@@ -270,38 +150,6 @@
"index": "pypi", "index": "pypi",
"version": "==4.21.12" "version": "==4.21.12"
}, },
"pycodestyle": {
"hashes": [
"sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053",
"sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"
],
"markers": "python_version >= '3.6'",
"version": "==2.10.0"
},
"pyflakes": {
"hashes": [
"sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf",
"sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"
],
"markers": "python_version >= '3.6'",
"version": "==3.0.1"
},
"pylint": {
"hashes": [
"sha256:18783cca3cfee5b83c6c5d10b3cdb66c6594520ffae61890858fe8d932e1c6b4",
"sha256:349c8cd36aede4d50a0754a8c0218b43323d13d5d88f4b2952ddfe3e169681eb"
],
"index": "pypi",
"version": "==2.15.9"
},
"pytest": {
"hashes": [
"sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71",
"sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"
],
"index": "pypi",
"version": "==7.2.0"
},
"pyzbar": { "pyzbar": {
"hashes": [ "hashes": [
"sha256:13e3ee5a2f3a545204a285f41814d5c0db571967e8d4af8699a03afc55182a9c", "sha256:13e3ee5a2f3a545204a285f41814d5c0db571967e8d4af8699a03afc55182a9c",
@@ -323,100 +171,6 @@
], ],
"index": "pypi", "index": "pypi",
"version": "==1.3.1" "version": "==1.3.1"
},
"tomli": {
"hashes": [
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
],
"markers": "python_version < '3.11'",
"version": "==2.0.1"
},
"tomlkit": {
"hashes": [
"sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b",
"sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"
],
"markers": "python_version >= '3.6'",
"version": "==0.11.6"
},
"wheel": {
"hashes": [
"sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac",
"sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8"
],
"index": "pypi",
"version": "==0.38.4"
},
"wrapt": {
"hashes": [
"sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3",
"sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b",
"sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4",
"sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2",
"sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656",
"sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3",
"sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff",
"sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310",
"sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a",
"sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57",
"sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069",
"sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383",
"sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe",
"sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87",
"sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d",
"sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b",
"sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907",
"sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f",
"sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0",
"sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28",
"sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1",
"sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853",
"sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc",
"sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3",
"sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3",
"sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164",
"sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1",
"sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c",
"sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1",
"sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7",
"sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1",
"sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320",
"sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed",
"sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1",
"sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248",
"sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c",
"sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456",
"sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77",
"sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef",
"sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1",
"sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7",
"sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86",
"sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4",
"sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d",
"sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d",
"sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8",
"sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5",
"sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471",
"sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00",
"sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68",
"sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3",
"sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d",
"sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735",
"sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d",
"sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569",
"sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7",
"sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59",
"sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5",
"sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb",
"sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b",
"sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f",
"sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462",
"sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015",
"sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"
],
"markers": "python_version < '3.11'",
"version": "==1.14.1"
} }
}, },
"develop": { "develop": {
@@ -436,30 +190,14 @@
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==22.2.0" "version": "==22.2.0"
}, },
"colorama": {
"hashes": [
"sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
"sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
],
"markers": "sys_platform == 'win32'",
"version": "==0.4.6"
},
"dill": { "dill": {
"hashes": [ "hashes": [
"sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0", "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0",
"sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373" "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"
], ],
"markers": "python_version < '3.11'", "markers": "python_version >= '3.11'",
"version": "==0.3.6" "version": "==0.3.6"
}, },
"exceptiongroup": {
"hashes": [
"sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828",
"sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"
],
"markers": "python_version < '3.11'",
"version": "==1.0.4"
},
"flake8": { "flake8": {
"hashes": [ "hashes": [
"sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7", "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7",
@@ -572,14 +310,6 @@
"index": "pypi", "index": "pypi",
"version": "==7.2.0" "version": "==7.2.0"
}, },
"tomli": {
"hashes": [
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
],
"markers": "python_version < '3.11'",
"version": "==2.0.1"
},
"tomlkit": { "tomlkit": {
"hashes": [ "hashes": [
"sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b", "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b",
@@ -663,7 +393,7 @@
"sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015", "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015",
"sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af" "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"
], ],
"markers": "python_version < '3.11'", "markers": "python_version >= '3.11'",
"version": "==1.14.1" "version": "==1.14.1"
} }
} }

View File

@@ -34,7 +34,7 @@ cd extract_otp_secret_keys
<pre>usage: extract_otp_secret_keys.py [-h] [--json FILE] [--csv FILE] [--keepass FILE] [--printqr] [--saveqr DIR] [--verbose | --quiet] infile <pre>usage: extract_otp_secret_keys.py [-h] [--json FILE] [--csv FILE] [--keepass FILE] [--printqr] [--saveqr DIR] [--verbose | --quiet] infile
positional arguments: positional arguments:
infile file or - for stdin with "otpauth-migration://..." URLs separated by newlines, lines starting with # are ignored infile 1) file or - for stdin with "otpauth-migration://..." URLs separated by newlines, lines starting with # are ignored; 2) image file containing a QR code or = for stdin for an image containing a QR code
options: options:
-h, --help show this help message and exit -h, --help show this help message and exit
@@ -57,11 +57,27 @@ Known to work with
For protobuf versions 3.14.0 or similar or Python 3.6, use the extract_otp_secret_keys version 1.4.0. For protobuf versions 3.14.0 or similar or Python 3.6, use the extract_otp_secret_keys version 1.4.0.
### Optional ## Examples
For printing QR codes, the qrcode module is required, otherwise it can be omitted. ### Printing otp secrets form text file
pip install qrcode[pil] python extract_otp_secret_keys.py example_export.txt
### Printing otp secrets from stdin
python extract_otp_secret_keys.py - < example_export.txt
### Printing otp secrets from image
python extract_otp_secret_keys.py test/test_googleauth_export.png
### Printing otp secrets from stdin (image)
python extract_otp_secret_keys.py = < test/test_googleauth_export.png
### Printing otp secrets csv to stdout
python extract_otp_secret_keys.py --csv - example_export.txt
## Features ## Features

View File

@@ -41,24 +41,22 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
# TODO optimze imports
import argparse import argparse
import base64 import base64
import fileinput import fileinput
import sys import sys
import csv import csv
import json import json
import cv2 from cv2 import imread, imdecode, IMREAD_UNCHANGED
from qreader import QReader from qreader import QReader
from urllib.parse import parse_qs, urlencode, urlparse, quote from urllib.parse import parse_qs, urlencode, urlparse, quote
from os import path, makedirs from os import path, makedirs
from re import compile as rcompile from re import compile as rcompile
from numpy import frombuffer
import protobuf_generated_python.google_auth_pb2 import protobuf_generated_python.google_auth_pb2
verbose = False
quiet = True
def sys_main(): def sys_main():
main(sys.argv[1:]) main(sys.argv[1:])
@@ -82,19 +80,12 @@ def main(sys_args):
def parse_args(sys_args): def parse_args(sys_args):
formatter = lambda prog: argparse.HelpFormatter(prog, max_help_position=52) formatter = lambda prog: argparse.HelpFormatter(prog, max_help_position=52)
arg_parser = argparse.ArgumentParser(formatter_class=formatter) arg_parser = argparse.ArgumentParser(formatter_class=formatter)
arg_parser.add_argument('infile', arg_parser.add_argument('infile', help='1) file or - for stdin with "otpauth-migration://..." URLs separated by newlines, lines starting with # are ignored; 2) image file containing a QR code or = for stdin for an image containing a QR code')
help="image file containing a QR code from a Google Authenticator export or a text file "
"or - for stdin with \"otpauth-migration://...\" URLs separated by newlines. Lines "
"starting with # are ignored.")
arg_parser.add_argument('--json', '-j', help='export json file or - for stdout', metavar=('FILE')) arg_parser.add_argument('--json', '-j', help='export json file or - for stdout', metavar=('FILE'))
arg_parser.add_argument('--csv', '-c', help='export csv file or - for stdout', metavar=('FILE')) arg_parser.add_argument('--csv', '-c', help='export csv file or - for stdout', metavar=('FILE'))
arg_parser.add_argument('--keepass', '-k', help='export totp/hotp csv file(s) for KeePass, - for stdout', arg_parser.add_argument('--keepass', '-k', help='export totp/hotp csv file(s) for KeePass, - for stdout', metavar=('FILE'))
metavar=('FILE')) arg_parser.add_argument('--printqr', '-p', help='print QR code(s) as text to the terminal (requires qrcode module)', action='store_true')
arg_parser.add_argument('--printqr', '-p', help='print QR code(s) as text to the terminal (requires qrcode module)', arg_parser.add_argument('--saveqr', '-s', help='save QR code(s) as images to the given folder (requires qrcode module)', metavar=('DIR'))
action='store_true')
arg_parser.add_argument('--saveqr', '-s',
help='save QR code(s) as images to the given folder (requires qrcode module)',
metavar=('DIR'))
output_group = arg_parser.add_mutually_exclusive_group() output_group = arg_parser.add_mutually_exclusive_group()
output_group.add_argument('--verbose', '-v', help='verbose output', action='count') output_group.add_argument('--verbose', '-v', help='verbose output', action='count')
output_group.add_argument('--quiet', '-q', help='no stdout output, except output set by -', action='store_true') output_group.add_argument('--quiet', '-q', help='no stdout output, except output set by -', action='store_true')
@@ -110,23 +101,18 @@ def extract_otps(args):
otps = [] otps = []
lines = get_lines_from_file(args.infile)
i = j = 0 i = j = 0
for line in lines: for line in get_lines_from_file(args.infile):
if verbose: if verbose: print(line)
print(line) if line.startswith('#') or line == '': continue
if line.startswith('#') or line == '':
continue
i += 1 i += 1
payload = get_payload_from_line(line, i, args) payload = get_payload_from_line(line, i, args)
# pylint: disable=no-member # pylint: disable=no-member
for raw_otp in payload.otp_parameters: for raw_otp in payload.otp_parameters:
j += 1 j += 1
if verbose: if verbose: print('\n{}. Secret Key'.format(j))
print('\n{}. Secret Key'.format(j))
secret = convert_secret_from_bytes_to_base32_str(raw_otp.secret) secret = convert_secret_from_bytes_to_base32_str(raw_otp.secret)
otp_type_enum = get_enum_name_by_number(raw_otp, 'type') otp_type_enum = get_enum_name_by_number(raw_otp, 'type')
otp_type = get_otp_type_str_from_code(raw_otp.type) otp_type = get_otp_type_str_from_code(raw_otp.type)
@@ -153,57 +139,61 @@ def extract_otps(args):
return otps return otps
def get_lines_from_file(filepath): def get_lines_from_file(filename):
global verbose global verbose
# Check if this is an image file if filename != '=':
if(path.splitext(filepath)[1][1:].lower() in ('bmp', 'jpg', 'jpeg', 'png', 'tif', 'tiff')): check_file_exists(filename)
# It's an image file, so try to read it as a QR Code finput = fileinput.input(filename)
try: try:
decoder = QReader() lines = []
for line in (line.strip() for line in finput):
# TODO improve
# if verbose: print(line)
# if line.startswith('#') or line == '':
# continue
# unfortunately yield line leads to random test fails
lines.append(line)
return lines
except UnicodeDecodeError:
if filename == '-':
abort('\nERROR: Unable to open text file form stdin. '
'In case you want read an image file from stdin, you must use "=" instead of "-".')
# else: The file is probably an image, process below
finally:
finput.close()
if not path.isfile(filepath): # could not process text file, try reading as image
eprint('\nERROR: Input file provided is non-existent or not a file.' if filename != '-':
'\ninput file: {}'.format(filepath)) try:
return [] if filename != '=':
image = imread(filename)
else:
try:
stdin = sys.stdin.buffer.read()
except AttributeError:
# Workaround for pytest, since pytest cannot monkeypatch sys.stdin.buffer
stdin = sys.stdin.read()
array = frombuffer(stdin, dtype='uint8')
image = imdecode(array, IMREAD_UNCHANGED)
image = cv2.imread(filepath)
if image is None: if image is None:
eprint('\nERROR: Unable to open file for reading. Please ensure that you have read access to the ' abort('\nERROR: Unable to open file for reading.\ninput file: {}'.format(filename))
'file and that the file is a valid image file.\ninput file: {}'.format(filepath))
return []
decoder = QReader()
decoded_text = decoder.detect_and_decode(image=image) decoded_text = decoder.detect_and_decode(image=image)
if decoded_text is None: if decoded_text is None:
eprint('\nERROR: Unable to read QR Code from file.\ninput file: {}'.format(filepath)) abort('\nERROR: Unable to read QR Code from file.\ninput file: {}'.format(filename))
return []
return [decoded_text] return [decoded_text]
except Exception as e: except Exception as e:
eprint('\nERROR: Encountered exception "{}".\ninput file: {}'.format(str(e), filepath)) abort('\nERROR: Encountered exception "{}".\ninput file: {}'.format(str(e), filename))
return []
else:
# Not an image file, so assume it's a text file and proceed as usual
lines = []
finput = fileinput.input(filepath)
try:
for line in (line.strip() for line in finput):
if verbose:
print(line)
if line.startswith('#') or line == '':
continue
lines.append(line)
finally:
finput.close()
return lines
def get_payload_from_line(line, i, args): def get_payload_from_line(line, i, args):
global verbose global verbose
if not line.startswith('otpauth-migration://'): if not line.startswith('otpauth-migration://'):
eprint( eprint( '\nWARN: line is not a otpauth-migration:// URL\ninput file: {}\nline "{}"\nProbably a wrong file was given'.format( args.infile, line))
'\nWARN: line is not a otpauth-migration:// URL\ninput file: {}\nline "{}"\nProbably a wrong file was given'.format(
args.infile, line))
parsed_url = urlparse(line) parsed_url = urlparse(line)
if verbose > 1: print('\nDEBUG: parsed_url={}'.format(parsed_url)) if verbose > 1: print('\nDEBUG: parsed_url={}'.format(parsed_url))
try: try:
@@ -212,10 +202,7 @@ def get_payload_from_line(line, i, args):
params = [] params = []
if verbose > 1: print('\nDEBUG: querystring params={}'.format(params)) if verbose > 1: print('\nDEBUG: querystring params={}'.format(params))
if 'data' not in params: if 'data' not in params:
eprint( abort('\nERROR: no data query parameter in input URL\ninput file: {}\nline "{}"\nProbably a wrong file was given'.format( args.infile, line))
'\nERROR: no data query parameter in input URL\ninput file: {}\nline "{}"\nProbably a wrong file was given'.format(
args.infile, line))
sys.exit(1)
data_base64 = params['data'][0] data_base64 = params['data'][0]
if verbose > 1: print('\nDEBUG: data_base64={}'.format(data_base64)) if verbose > 1: print('\nDEBUG: data_base64={}'.format(data_base64))
data_base64_fixed = data_base64.replace(' ', '+') data_base64_fixed = data_base64.replace(' ', '+')
@@ -225,9 +212,8 @@ def get_payload_from_line(line, i, args):
try: try:
payload.ParseFromString(data) payload.ParseFromString(data)
except: except:
eprint('\nERROR: Cannot decode otpauth-migration migration payload.') abort('\nERROR: Cannot decode otpauth-migration migration payload.\n'
eprint('data={}'.format(data_base64)) 'data={}'.format(data_base64))
exit(1)
if verbose: if verbose:
print('\n{}. Payload Line'.format(i), payload, sep='\n') print('\n{}. Payload Line'.format(i), payload, sep='\n')
@@ -252,8 +238,7 @@ def build_otp_url(secret, raw_otp):
url_params = {'secret': secret} url_params = {'secret': secret}
if raw_otp.type == 1: url_params['counter'] = raw_otp.counter if raw_otp.type == 1: url_params['counter'] = raw_otp.counter
if raw_otp.issuer: url_params['issuer'] = raw_otp.issuer if raw_otp.issuer: url_params['issuer'] = raw_otp.issuer
otp_url = 'otpauth://{}/{}?'.format(get_otp_type_str_from_code(raw_otp.type), quote(raw_otp.name)) + urlencode( otp_url = 'otpauth://{}/{}?'.format(get_otp_type_str_from_code(raw_otp.type), quote(raw_otp.name)) + urlencode( url_params)
url_params)
return otp_url return otp_url
@@ -274,8 +259,7 @@ def save_qr(otp, args, j):
pattern = rcompile(r'[\W_]+') pattern = rcompile(r'[\W_]+')
file_otp_name = pattern.sub('', otp['name']) file_otp_name = pattern.sub('', otp['name'])
file_otp_issuer = pattern.sub('', otp['issuer']) file_otp_issuer = pattern.sub('', otp['issuer'])
save_qr_file(args, otp['url'], save_qr_file(args, otp['url'], '{}/{}-{}{}.png'.format(dir, j, file_otp_name, '-' + file_otp_issuer if file_otp_issuer else ''))
'{}/{}-{}{}.png'.format(dir, j, file_otp_name, '-' + file_otp_issuer if file_otp_issuer else ''))
return file_otp_issuer return file_otp_issuer
@@ -330,8 +314,7 @@ def write_keepass_csv(args, otps):
count_totp_entries += 1 count_totp_entries += 1
if has_hotp: if has_hotp:
with open_file_or_stdout_for_csv(otp_filename_hotp) as outfile: with open_file_or_stdout_for_csv(otp_filename_hotp) as outfile:
writer = csv.DictWriter(outfile, writer = csv.DictWriter(outfile, ["Title", "User Name", "HmacOtp-Secret-Base32", "HmacOtp-Counter", "Group"])
["Title", "User Name", "HmacOtp-Secret-Base32", "HmacOtp-Counter", "Group"])
writer.writeheader() writer.writeheader()
for otp in otps: for otp in otps:
if otp['type'] == 'hotp': if otp['type'] == 'hotp':
@@ -344,10 +327,8 @@ def write_keepass_csv(args, otps):
}) })
count_hotp_entries += 1 count_hotp_entries += 1
if not quiet: if not quiet:
if count_totp_entries > 0: print( if count_totp_entries > 0: print( "Exported {} totp entries to keepass csv file {}".format(count_totp_entries, otp_filename_totp))
"Exported {} totp entries to keepass csv file {}".format(count_totp_entries, otp_filename_totp)) if count_hotp_entries > 0: print( "Exported {} hotp entries to keepass csv file {}".format(count_hotp_entries, otp_filename_hotp))
if count_hotp_entries > 0: print(
"Exported {} hotp entries to keepass csv file {}".format(count_hotp_entries, otp_filename_hotp))
def write_json(args, otps): def write_json(args, otps):
@@ -386,10 +367,20 @@ def open_file_or_stdout_for_csv(filename):
return open(filename, "w", encoding='utf-8', newline='') if filename != '-' else sys.stdout return open(filename, "w", encoding='utf-8', newline='') if filename != '-' else sys.stdout
def check_file_exists(filename):
if filename != '-' and not path.isfile(filename):
abort('\nERROR: Input file provided is non-existent or not a file.'
'\ninput file: {}'.format(filename))
def eprint(*args, **kwargs): def eprint(*args, **kwargs):
'''Print to stderr.''' '''Print to stderr.'''
print(*args, file=sys.stderr, **kwargs) print(*args, file=sys.stderr, **kwargs)
def abort(*args, **kwargs):
eprint(*args, **kwargs)
sys.exit(1)
if __name__ == '__main__': if __name__ == '__main__':
sys_main() sys_main()

View File

@@ -2,5 +2,3 @@ wheel
pytest pytest
flake8 flake8
pylint pylint
qreader
opencv-python

View File

@@ -18,11 +18,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
from utils import read_csv, read_csv_str, read_json, read_json_str, remove_files, remove_dir_with_files, read_file_to_str, file_exits from utils import read_csv, read_csv_str, read_json, read_json_str, remove_files, remove_dir_with_files, read_file_to_str, read_binary_file_as_stream, file_exits
from os import path from os import path
from pytest import raises, mark from pytest import raises, mark
from io import StringIO from io import StringIO, BytesIO
from sys import implementation from sys import implementation, stdin
import extract_otp_secret_keys import extract_otp_secret_keys
@@ -38,6 +38,22 @@ def test_extract_stdout(capsys):
assert captured.err == '' assert captured.err == ''
def test_extract_non_existent_file(capsys):
# Act
with raises(SystemExit) as e:
extract_otp_secret_keys.main(['test/non_existent_file.txt'])
# Assert
captured = capsys.readouterr()
expected_stderr = '\nERROR: Input file provided is non-existent or not a file.\ninput file: test/non_existent_file.txt\n'
assert captured.err == expected_stderr
assert captured.out == ''
assert e.value.code == 1
assert e.type == SystemExit
def test_extract_stdin_stdout(capsys, monkeypatch): def test_extract_stdin_stdout(capsys, monkeypatch):
# Arrange # Arrange
monkeypatch.setattr('sys.stdin', StringIO(read_file_to_str('example_export.txt'))) monkeypatch.setattr('sys.stdin', StringIO(read_file_to_str('example_export.txt')))
@@ -335,7 +351,7 @@ def test_extract_debug(capsys):
def test_extract_help(capsys): def test_extract_help(capsys):
with raises(SystemExit) as pytest_wrapped_e: with raises(SystemExit) as e:
# Act # Act
extract_otp_secret_keys.main(['-h']) extract_otp_secret_keys.main(['-h'])
@@ -345,13 +361,13 @@ def test_extract_help(capsys):
assert len(captured.out) > 0 assert len(captured.out) > 0
assert "-h, --help" in captured.out and "--verbose, -v" in captured.out assert "-h, --help" in captured.out and "--verbose, -v" in captured.out
assert captured.err == '' assert captured.err == ''
assert pytest_wrapped_e.type == SystemExit assert e.type == SystemExit
assert pytest_wrapped_e.value.code == 0 assert e.value.code == 0
def test_extract_no_arguments(capsys): def test_extract_no_arguments(capsys):
# Act # Act
with raises(SystemExit) as pytest_wrapped_e: with raises(SystemExit) as e:
extract_otp_secret_keys.main([]) extract_otp_secret_keys.main([])
# Assert # Assert
@@ -361,10 +377,12 @@ def test_extract_no_arguments(capsys):
assert expected_err_msg in captured.err assert expected_err_msg in captured.err
assert captured.out == '' assert captured.out == ''
assert e.value.code == 2
assert e.type == SystemExit
def test_verbose_and_quiet(capsys): def test_verbose_and_quiet(capsys):
with raises(SystemExit) as pytest_wrapped_e: with raises(SystemExit) as e:
# Act # Act
extract_otp_secret_keys.main(['-v', '-q', 'example_export.txt']) extract_otp_secret_keys.main(['-v', '-q', 'example_export.txt'])
@@ -374,10 +392,12 @@ def test_verbose_and_quiet(capsys):
assert len(captured.err) > 0 assert len(captured.err) > 0
assert 'error: argument --quiet/-q: not allowed with argument --verbose/-v' in captured.err assert 'error: argument --quiet/-q: not allowed with argument --verbose/-v' in captured.err
assert captured.out == '' assert captured.out == ''
assert e.value.code == 2
assert e.type == SystemExit
def test_wrong_data(capsys): def test_wrong_data(capsys):
with raises(SystemExit) as pytest_wrapped_e: with raises(SystemExit) as e:
# Act # Act
extract_otp_secret_keys.main(['test/test_export_wrong_data.txt']) extract_otp_secret_keys.main(['test/test_export_wrong_data.txt'])
@@ -391,10 +411,12 @@ data=XXXX
assert captured.err == expected_stderr assert captured.err == expected_stderr
assert captured.out == '' assert captured.out == ''
assert e.value.code == 1
assert e.type == SystemExit
def test_wrong_content(capsys): def test_wrong_content(capsys):
with raises(SystemExit) as pytest_wrapped_e: with raises(SystemExit) as e:
# Act # Act
extract_otp_secret_keys.main(['test/test_export_wrong_content.txt']) extract_otp_secret_keys.main(['test/test_export_wrong_content.txt'])
@@ -415,6 +437,8 @@ Probably a wrong file was given
assert captured.out == '' assert captured.out == ''
assert captured.err == expected_stderr assert captured.err == expected_stderr
assert e.value.code == 1
assert e.type == SystemExit
def test_wrong_prefix(capsys): def test_wrong_prefix(capsys):
@@ -448,6 +472,125 @@ def test_add_pre_suffix(capsys):
assert extract_otp_secret_keys.add_pre_suffix("name", "totp") == "name.totp" assert extract_otp_secret_keys.add_pre_suffix("name", "totp") == "name.totp"
def test_img_qr_reader_from_file_happy_path(capsys):
# Act
extract_otp_secret_keys.main(['test/test_googleauth_export.png'])
# Assert
captured = capsys.readouterr()
expected_stdout =\
'''Name: Test1:test1@example1.com
Secret: JBSWY3DPEHPK3PXP
Issuer: Test1
Type: totp
Name: Test2:test2@example2.com
Secret: JBSWY3DPEHPK3PXQ
Issuer: Test2
Type: totp
Name: Test3:test3@example3.com
Secret: JBSWY3DPEHPK3PXR
Issuer: Test3
Type: totp
'''
assert captured.out == expected_stdout
assert captured.err == ''
def test_img_qr_reader_from_stdin(capsys, monkeypatch):
# Arrange
# sys.stdin.buffer should be monkey patched, but it does not work
monkeypatch.setattr('sys.stdin', read_binary_file_as_stream('test/test_googleauth_export.png'))
# Act
extract_otp_secret_keys.main(['='])
# Assert
captured = capsys.readouterr()
expected_stdout =\
'''Name: Test1:test1@example1.com
Secret: JBSWY3DPEHPK3PXP
Issuer: Test1
Type: totp
Name: Test2:test2@example2.com
Secret: JBSWY3DPEHPK3PXQ
Issuer: Test2
Type: totp
Name: Test3:test3@example3.com
Secret: JBSWY3DPEHPK3PXR
Issuer: Test3
Type: totp
'''
assert captured.out == expected_stdout
assert captured.err == ''
def test_img_qr_reader_no_qr_code_in_image(capsys):
# Act
with raises(SystemExit) as e:
extract_otp_secret_keys.main(['test/lena_std.tif'])
# Assert
captured = capsys.readouterr()
expected_stderr = '\nERROR: Unable to read QR Code from file.\ninput file: test/lena_std.tif\n'
assert captured.err == expected_stderr
assert captured.out == ''
assert e.value.code == 1
assert e.type == SystemExit
def test_img_qr_reader_nonexistent_file(capsys):
# Act
with raises(SystemExit) as e:
extract_otp_secret_keys.main(['test/nonexistent.bmp'])
# Assert
captured = capsys.readouterr()
expected_stderr = '\nERROR: Input file provided is non-existent or not a file.\ninput file: test/nonexistent.bmp\n'
assert captured.err == expected_stderr
assert captured.out == ''
assert e.value.code == 1
assert e.type == SystemExit
def test_non_image_file(capsys):
# Act
with raises(SystemExit) as e:
extract_otp_secret_keys.main(['test/text_masquerading_as_image.jpeg'])
# Assert
captured = capsys.readouterr()
expected_stderr = '''
WARN: line is not a otpauth-migration:// URL
input file: test/text_masquerading_as_image.jpeg
line "This is just a text file masquerading as an image file."
Probably a wrong file was given
ERROR: no data query parameter in input URL
input file: test/text_masquerading_as_image.jpeg
line "This is just a text file masquerading as an image file."
Probably a wrong file was given
'''
assert captured.err == expected_stderr
assert captured.out == ''
assert e.value.code == 1
assert e.type == SystemExit
def cleanup(): def cleanup():
remove_files('test_example_*.csv') remove_files('test_example_*.csv')
remove_files('test_example_*.json') remove_files('test_example_*.json')

View File

@@ -187,19 +187,41 @@ Type: totp
self.assertGreater(len(actual_output), len(expected_stdout)) self.assertGreater(len(actual_output), len(expected_stdout))
self.assertTrue("DEBUG: " in actual_output) self.assertTrue("DEBUG: " in actual_output)
def test_extract_help(self): def test_extract_help_1(self):
out = io.StringIO() out = io.StringIO()
with redirect_stdout(out): with redirect_stdout(out):
try: try:
extract_otp_secret_keys.main(['-h']) extract_otp_secret_keys.main(['-h'])
except SystemExit: self.fail("Must abort")
pass except SystemExit as e:
self.assertEqual(e.code, 0)
actual_output = out.getvalue() actual_output = out.getvalue()
self.assertGreater(len(actual_output), 0) self.assertGreater(len(actual_output), 0)
self.assertTrue("-h, --help" in actual_output and "--verbose, -v" in actual_output) self.assertTrue("-h, --help" in actual_output and "--verbose, -v" in actual_output)
def test_extract_help_2(self):
out = io.StringIO()
with redirect_stdout(out):
with self.assertRaises(SystemExit) as context:
extract_otp_secret_keys.main(['-h'])
actual_output = out.getvalue()
self.assertGreater(len(actual_output), 0)
self.assertTrue("-h, --help" in actual_output and "--verbose, -v" in actual_output)
self.assertEqual(context.exception.code, 0)
def test_extract_help_3(self):
with Capturing() as actual_output:
with self.assertRaises(SystemExit) as context:
extract_otp_secret_keys.main(['-h'])
self.assertGreater(len(actual_output), 0)
self.assertTrue("-h, --help" in "\n".join(actual_output) and "--verbose, -v" in "\n".join(actual_output))
self.assertEqual(context.exception.code, 0)
def setUp(self): def setUp(self):
self.cleanup() self.cleanup()

View File

@@ -23,9 +23,8 @@ from utils import Capturing
import extract_otp_secret_keys import extract_otp_secret_keys
class TestExtract(unittest.TestCase): class TestExtract(unittest.TestCase):
def test_happy_path(self): def test_img_qr_reader_happy_path(self):
with Capturing() as actual_output: with Capturing() as actual_output:
extract_otp_secret_keys.main(['test/test_googleauth_export.png']) extract_otp_secret_keys.main(['test/test_googleauth_export.png'])
@@ -36,34 +35,48 @@ class TestExtract(unittest.TestCase):
self.assertEqual(actual_output, expected_output) self.assertEqual(actual_output, expected_output)
def test_no_qr_code_in_image(self): def test_img_qr_reader_no_qr_code_in_image(self):
with Capturing() as actual_output: with Capturing() as actual_output:
with self.assertRaises(SystemExit) as context:
extract_otp_secret_keys.main(['test/lena_std.tif']) extract_otp_secret_keys.main(['test/lena_std.tif'])
expected_output =\ expected_output =\
['', 'ERROR: Unable to read QR Code from file.', 'input file: test/lena_std.tif'] ['', 'ERROR: Unable to read QR Code from file.', 'input file: test/lena_std.tif']
self.assertEqual(actual_output, expected_output) self.assertEqual(actual_output, expected_output)
self.assertEqual(context.exception.code, 1)
def test_nonexistent_file(self): def test_img_qr_reader_nonexistent_file(self):
with Capturing() as actual_output: with Capturing() as actual_output:
with self.assertRaises(SystemExit) as context:
extract_otp_secret_keys.main(['test/nonexistent.bmp']) extract_otp_secret_keys.main(['test/nonexistent.bmp'])
expected_output =\ expected_output =\
['', 'ERROR: Input file provided is non-existent or not a file.', 'input file: test/nonexistent.bmp'] ['', 'ERROR: Input file provided is non-existent or not a file.', 'input file: test/nonexistent.bmp']
self.assertEqual(actual_output, expected_output) self.assertEqual(actual_output, expected_output)
self.assertEqual(context.exception.code, 1)
def test_img_qr_reader_non_image_file(self):
def test_non_image_file(self):
with Capturing() as actual_output: with Capturing() as actual_output:
with self.assertRaises(SystemExit) as context:
extract_otp_secret_keys.main(['test/text_masquerading_as_image.jpeg']) extract_otp_secret_keys.main(['test/text_masquerading_as_image.jpeg'])
expected_output =\ expected_output = [
['', 'ERROR: Unable to open file for reading. Please ensure that you have read access to the file and that ' '',
'the file is a valid image file.', 'input file: test/text_masquerading_as_image.jpeg'] 'WARN: line is not a otpauth-migration:// URL',
'input file: test/text_masquerading_as_image.jpeg',
'line "This is just a text file masquerading as an image file."',
'Probably a wrong file was given',
'',
'ERROR: no data query parameter in input URL',
'input file: test/text_masquerading_as_image.jpeg',
'line "This is just a text file masquerading as an image file."',
'Probably a wrong file was given'
]
self.assertEqual(actual_output, expected_output) self.assertEqual(actual_output, expected_output)
self.assertEqual(context.exception.code, 1)
def setUp(self): def setUp(self):
self.cleanup() self.cleanup()

View File

@@ -17,7 +17,7 @@ import csv
import json import json
import os import os
import shutil import shutil
from io import StringIO from io import StringIO, BytesIO
import sys import sys
import glob import glob
@@ -102,3 +102,8 @@ def read_file_to_list(filename):
def read_file_to_str(filename): def read_file_to_str(filename):
"""Returns a str.""" """Returns a str."""
return "".join(read_file_to_list(filename)) return "".join(read_file_to_list(filename))
def read_binary_file_as_stream(filename):
"""Returns binary file content."""
with open(filename, "rb",) as infile:
return BytesIO(infile.read())