pyauthenticator 0.1.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,35 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyauthenticator
3
- Version: 0.1.0
3
+ Version: 0.2.1
4
4
  Summary: Similar to the Google authenticator just written in python.
5
- Home-page: https://github.com/jan-janssen/pyauthenticator
6
- Author: Jan Janssen
7
- Author-email: jan.janssen@outlook.com
8
- License: BSD
9
- Description-Content-Type: text/markdown
5
+ Project-URL: Homepage, https://github.com/jan-janssen/pyauthenticator
6
+ Project-URL: Documentation, https://github.com/jan-janssen/pyauthenticator/blob/master/README.md
7
+ Project-URL: Source, https://github.com/jan-janssen/pyauthenticator
8
+ Project-URL: Tracker, https://github.com/jan-janssen/pyauthenticator/issues
9
+ Author-email: Jan Janssen <jan.janssen@outlook.com>
10
+ License: BSD-3-Clause
10
11
  License-File: LICENSE
12
+ Keywords: 2FA,MFA,authenticator
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: BSD License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.7
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Topic :: Security
26
+ Classifier: Topic :: Utilities
27
+ Requires-Python: >=3.7
28
+ Requires-Dist: pillow>=11.3.0
29
+ Requires-Dist: pyotp>=2.9.0
30
+ Requires-Dist: pyzbar>=0.1.9
31
+ Requires-Dist: qrcode>=8.2
32
+ Description-Content-Type: text/markdown
11
33
 
12
34
  # pyauthenticator
13
35
  [![Python package](https://github.com/jan-janssen/pyauthenticator/actions/workflows/unittest.yml/badge.svg?branch=main)](https://github.com/jan-janssen/pyauthenticator/actions/workflows/unittest.yml)
@@ -101,4 +123,4 @@ the config file contains:
101
123
  With the Google username `<username>` and the corresponding secret `<secret>` being contained in the QR code.
102
124
 
103
125
  ## License
104
- The `pyauthenticator` package is licensed under the [BSD-3-Clause license](https://github.com/jan-janssen/pyauthenticator/blob/main/LICENSE).
126
+ The `pyauthenticator` package is licensed under the [BSD-3-Clause license](https://github.com/jan-janssen/pyauthenticator/blob/main/LICENSE).
@@ -1,11 +1,13 @@
1
1
  """
2
2
  Generate two factor authentication codes on the command line
3
3
  """
4
- from pyauthenticator.share import (
5
- load_config,
6
- generate_qrcode,
7
- get_two_factor_code as get_two_factor_code_internal,
8
- )
4
+
5
+ from . import _version
6
+ from pyauthenticator.share import generate_qrcode
7
+ from pyauthenticator.share import get_two_factor_code as get_two_factor_code_internal
8
+ from pyauthenticator.share import load_config
9
+
10
+ __version__ = _version.__version__
9
11
 
10
12
 
11
13
  def write_qrcode_to_file(service, file_name=None):
@@ -1,14 +1,16 @@
1
1
  """
2
2
  Generate two factor authentication codes on the command line
3
3
  """
4
+
4
5
  import argparse
5
6
  import sys
7
+
6
8
  from pyauthenticator.share import (
7
- list_services,
8
- load_config,
9
- generate_qrcode,
10
9
  add_service,
10
+ generate_qrcode,
11
11
  get_two_factor_code,
12
+ list_services,
13
+ load_config,
12
14
  )
13
15
 
14
16
 
@@ -0,0 +1,21 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
6
+ TYPE_CHECKING = False
7
+ if TYPE_CHECKING:
8
+ from typing import Tuple
9
+ from typing import Union
10
+
11
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
12
+ else:
13
+ VERSION_TUPLE = object
14
+
15
+ version: str
16
+ __version__: str
17
+ __version_tuple__: VERSION_TUPLE
18
+ version_tuple: VERSION_TUPLE
19
+
20
+ __version__ = version = '0.2.1'
21
+ __version_tuple__ = version_tuple = (0, 2, 1)
@@ -1,14 +1,15 @@
1
1
  """
2
2
  Shared functionality to generate two factor authentication codes
3
3
  """
4
- import base64
4
+
5
5
  import json
6
6
  import os
7
- import otpauth
7
+ from inspect import signature
8
+
9
+ import pyotp
10
+ import qrcode
8
11
  from PIL import Image
9
12
  from pyzbar.pyzbar import decode
10
- import qrcode
11
-
12
13
 
13
14
  # default configuration file
14
15
  config_file = "~/.pyauthenticator"
@@ -75,28 +76,6 @@ def get_otpauth_dict(otpauth_str):
75
76
  }
76
77
 
77
78
 
78
- def add_padding(main_str, padding_str, padding_length, inverse_padding=False):
79
- """
80
- Add padding to a string either in the beginning or at the end
81
-
82
- Args:
83
- main_str (str): string to add padding to
84
- padding_str (str): padding character as string
85
- padding_length (int): the length of the final string should be a multiple of the padding length
86
- inverse_padding (bool): add padding in the beginning rather than the end
87
-
88
- Returns:
89
- str: resulting string with padding
90
- """
91
- missing_padding = len(main_str) % padding_length
92
- if missing_padding:
93
- if inverse_padding:
94
- main_str = padding_str * (padding_length - missing_padding) + main_str
95
- else:
96
- main_str += padding_str * (padding_length - missing_padding)
97
- return main_str
98
-
99
-
100
79
  def check_if_key_in_config(key, config_dict):
101
80
  """
102
81
  Check if a given key is included in a dictionary, raise an ValueError if it is not.
@@ -122,37 +101,25 @@ def get_two_factor_code(key, config_dict):
122
101
  """
123
102
  check_if_key_in_config(key=key, config_dict=config_dict)
124
103
  decode_dict_internal = get_otpauth_dict(otpauth_str=config_dict[key])
104
+ funct_sig = signature(pyotp.TOTP)
105
+ if "digits" in decode_dict_internal.keys():
106
+ digits = int(decode_dict_internal["digits"])
107
+ else:
108
+ digits = funct_sig.parameters["digits"].default
125
109
  if "period" in decode_dict_internal.keys():
126
- totp = otpauth.TOTP(
127
- secret=base64.b32decode(
128
- add_padding(
129
- main_str=decode_dict_internal["secret"],
130
- padding_str="=",
131
- padding_length=8,
132
- inverse_padding=False,
133
- ),
134
- True,
135
- ),
136
- period=int(decode_dict_internal["period"]),
137
- )
110
+ interval = int(decode_dict_internal["period"])
111
+ else:
112
+ interval = funct_sig.parameters["interval"].default
113
+ if "issuer" in decode_dict_internal.keys():
114
+ issuer = decode_dict_internal["issuer"]
138
115
  else:
139
- totp = otpauth.TOTP(
140
- secret=base64.b32decode(
141
- add_padding(
142
- main_str=decode_dict_internal["secret"],
143
- padding_str="=",
144
- padding_length=8,
145
- inverse_padding=False,
146
- ),
147
- True,
148
- ),
149
- )
150
- return add_padding(
151
- main_str=str(totp.string_code(totp.generate())),
152
- padding_str="0",
153
- padding_length=6,
154
- inverse_padding=True,
155
- )
116
+ issuer = funct_sig.parameters["issuer"].default
117
+ return pyotp.TOTP(
118
+ s=decode_dict_internal["secret"],
119
+ digits=digits,
120
+ issuer=issuer,
121
+ interval=interval,
122
+ ).now()
156
123
 
157
124
 
158
125
  def add_service(
@@ -0,0 +1,75 @@
1
+ [build-system]
2
+ requires = [
3
+ "hatchling>=1.27.0",
4
+ "hatch-vcs>=0.5.0",
5
+ "pyotp>=2.9.0",
6
+ "qrcode>=8.2",
7
+ "pyzbar>=0.1.9",
8
+ "pillow>=11.3.0",
9
+ ]
10
+ build-backend = "hatchling.build"
11
+
12
+ [project]
13
+ name = "pyauthenticator"
14
+ dynamic = ["version"]
15
+ description = "Similar to the Google authenticator just written in python."
16
+ readme = "README.md"
17
+ requires-python = ">=3.7"
18
+ license = { text = "BSD-3-Clause" }
19
+ authors = [
20
+ { name = "Jan Janssen", email = "jan.janssen@outlook.com" },
21
+ ]
22
+ keywords = ["authenticator", "2FA", "MFA"]
23
+ classifiers = [
24
+ "Development Status :: 5 - Production/Stable",
25
+ "Intended Audience :: Developers",
26
+ "License :: OSI Approved :: BSD License",
27
+ "Operating System :: OS Independent",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.7",
30
+ "Programming Language :: Python :: 3.8",
31
+ "Programming Language :: Python :: 3.9",
32
+ "Programming Language :: Python :: 3.10",
33
+ "Programming Language :: Python :: 3.11",
34
+ "Programming Language :: Python :: 3.12",
35
+ "Programming Language :: Python :: 3.13",
36
+ "Topic :: Security",
37
+ "Topic :: Utilities",
38
+ ]
39
+ dependencies = [
40
+ "pyotp>=2.9.0",
41
+ "qrcode>=8.2",
42
+ "pyzbar>=0.1.9",
43
+ "pillow>=11.3.0",
44
+ ]
45
+
46
+ [project.urls]
47
+ Homepage = "https://github.com/jan-janssen/pyauthenticator"
48
+ Documentation = "https://github.com/jan-janssen/pyauthenticator/blob/master/README.md"
49
+ Source = "https://github.com/jan-janssen/pyauthenticator"
50
+ Tracker = "https://github.com/jan-janssen/pyauthenticator/issues"
51
+
52
+ [project.scripts]
53
+ pyauthenticator = "pyauthenticator.__main__:command_line_parser"
54
+
55
+ [tool.hatch.build]
56
+ include = [
57
+ "pyauthenticator"
58
+ ]
59
+
60
+ [tool.hatch.build.hooks.vcs]
61
+ version-file = "pyauthenticator/_version.py"
62
+
63
+ [tool.hatch.build.targets.sdist]
64
+ include = [
65
+ "pyauthenticator"
66
+ ]
67
+
68
+ [tool.hatch.build.targets.wheel]
69
+ packages = [
70
+ "pyauthenticator"
71
+ ]
72
+
73
+ [tool.hatch.version]
74
+ source = "vcs"
75
+ path = "pyauthenticator/_version.py"
@@ -1,3 +0,0 @@
1
- include LICENSE
2
- include versioneer.py
3
- include pyauthenticator/_version.py
@@ -1,104 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: pyauthenticator
3
- Version: 0.1.0
4
- Summary: Similar to the Google authenticator just written in python.
5
- Home-page: https://github.com/jan-janssen/pyauthenticator
6
- Author: Jan Janssen
7
- Author-email: jan.janssen@outlook.com
8
- License: BSD
9
- Description-Content-Type: text/markdown
10
- License-File: LICENSE
11
-
12
- # pyauthenticator
13
- [![Python package](https://github.com/jan-janssen/pyauthenticator/actions/workflows/unittest.yml/badge.svg?branch=main)](https://github.com/jan-janssen/pyauthenticator/actions/workflows/unittest.yml)
14
- [![Coverage Status](https://coveralls.io/repos/github/jan-janssen/pyauthenticator/badge.svg?branch=main)](https://coveralls.io/github/jan-janssen/pyauthenticator?branch=main)
15
- [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
16
-
17
- Similar to the Google authenticator just written in Python. With more and more services requiring two factor
18
- authentication without supporting application specific passwords or other forms of token based authenication
19
- suitable for automation this python packages allows to generate two factor authentication codes on the commandline
20
- or in python.
21
-
22
- ![Preview of pyauthenticator](https://raw.githubusercontent.com/jan-janssen/pyauthenticator/main/pyauthenticator.gif)
23
-
24
- # For Users
25
- ## Installation
26
- Install `pyauthenticator` via conda:
27
- ```
28
- >>> conda install -c conda-forge pyauthenticator
29
- ```
30
-
31
- Alternatively, `pyauthenticator` can also be installed via pip:
32
- ```
33
- >>> pip install pyauthenticator
34
- ```
35
-
36
- ## Command Line
37
- Get help how to use `pyauthenticator` using the `--help/-h` option:
38
- ```
39
- >>> pyauthenticator --help
40
-
41
- usage: pyauthenticator [-h] [-qr] [-a ADD] service
42
-
43
- positional arguments:
44
- service Service to generate optauth code for. Currently no
45
- service is defined in the ~/.pyauthenticator config file.
46
-
47
- options:
48
- -h, --help show this help message and exit
49
- -qr, --qrcode Generate qrcode as <service.png> file.
50
- -a ADD, --add ADD Add service by providing the <qrcode.png> file as
51
- additional argument.
52
- ```
53
-
54
- Add `google` as new service after saving the qrcode to `Screenshot 2023-07-02 at 12.45.09.png` to your desktop:
55
- ```
56
- >>> pyauthenticator google --add ~/Desktop/Screenshot\ 2023-07-02\ at\ 12.45.09.png
57
-
58
- The service 'google' was added, from file </Users/jan/Desktop/Screenshot 2023-07-02 at 12.45.09.png>
59
- ```
60
-
61
- Afterwards, new authentication codes can be generated for the service `google` using:
62
- ```
63
- >>> pyauthenticator google
64
-
65
- 087078
66
- ```
67
- Beyond google, `pyauthenticator` works for any service which implements the two factor authentication.
68
-
69
- If you mistype the name of the service, then `pyauthenticator` suggests alternative options:
70
- ```
71
- >>> pyauthenticator googel
72
-
73
- The service "googel" does not exist.
74
-
75
- The config file ~/.pyauthenticator contains the following services:
76
- * google
77
-
78
- Choose one of these or add a new service using:
79
- pyauthenticator --add <qr-code.png> <servicename>
80
- ```
81
-
82
- ## Support
83
- For any support requests feel free to open an [issue on Github](https://github.com/jan-janssen/pyauthenticator/issues).
84
-
85
- # For Developers
86
- ## Python Interface
87
- The same functionality which is available on the command line is also available via the python interface:
88
- ```
89
- from pyauthenticator import get_two_factor_code
90
- get_two_factor_code(service)
91
- ```
92
- So `pyauthenticator` can be integrated in existing python packages which need access to resources protected by two
93
- factor authentication.
94
-
95
- ## Configuration
96
- The configuration is stored in `~/.pyauthenticator` it is written in the JSON format. For a given service like `github`
97
- the config file contains:
98
- ```
99
- {"google": "otpauth://totp/Google:<username>?secret=<secret>&issuer=Google"}
100
- ```
101
- With the Google username `<username>` and the corresponding secret `<secret>` being contained in the QR code.
102
-
103
- ## License
104
- The `pyauthenticator` package is licensed under the [BSD-3-Clause license](https://github.com/jan-janssen/pyauthenticator/blob/main/LICENSE).
@@ -1,21 +0,0 @@
1
-
2
- # This file was generated by 'versioneer.py' (0.18) from
3
- # revision-control system data, or from the parent directory name of an
4
- # unpacked source archive. Distribution tarballs contain a pre-generated copy
5
- # of this file.
6
-
7
- import json
8
-
9
- version_json = '''
10
- {
11
- "date": "2023-07-02T14:02:38-0600",
12
- "dirty": true,
13
- "error": null,
14
- "full-revisionid": "6d55f3dd129d29673573d707dbc2119c8f213451",
15
- "version": "0.1.0"
16
- }
17
- ''' # END VERSION_JSON
18
-
19
-
20
- def get_versions():
21
- return json.loads(version_json)
@@ -1,20 +0,0 @@
1
- LICENSE
2
- MANIFEST.in
3
- README.md
4
- setup.cfg
5
- setup.py
6
- versioneer.py
7
- pyauthenticator/__init__.py
8
- pyauthenticator/__main__.py
9
- pyauthenticator/_version.py
10
- pyauthenticator/share.py
11
- pyauthenticator.egg-info/PKG-INFO
12
- pyauthenticator.egg-info/SOURCES.txt
13
- pyauthenticator.egg-info/dependency_links.txt
14
- pyauthenticator.egg-info/entry_points.txt
15
- pyauthenticator.egg-info/requires.txt
16
- pyauthenticator.egg-info/top_level.txt
17
- tests/test_cmd.py
18
- tests/test_core.py
19
- tests/test_share.py
20
- tests/test_user_interface.py
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- pyauthenticator = pyauthenticator.__main__:command_line_parser
@@ -1,4 +0,0 @@
1
- otpauth>=2.0.0
2
- qrcode>=7.4.2
3
- pyzbar>=0.1.9
4
- pillow>=9.5.0
@@ -1 +0,0 @@
1
- pyauthenticator
@@ -1,11 +0,0 @@
1
- [versioneer]
2
- VCS = git
3
- style = pep440-pre
4
- versionfile_source = pyauthenticator/_version.py
5
- tag_prefix = pyauthenticator-
6
- parentdir_prefix = pyauthenticator
7
-
8
- [egg_info]
9
- tag_build =
10
- tag_date = 0
11
-
@@ -1,31 +0,0 @@
1
- """
2
- Setuptools based setup module
3
- """
4
- from setuptools import setup, find_packages
5
- import versioneer
6
-
7
-
8
- setup(
9
- name='pyauthenticator',
10
- version=versioneer.get_version(),
11
- description='Similar to the Google authenticator just written in python.',
12
- long_description=open('README.md').read(),
13
- long_description_content_type="text/markdown",
14
- url='https://github.com/jan-janssen/pyauthenticator',
15
- author='Jan Janssen',
16
- author_email='jan.janssen@outlook.com',
17
- license='BSD',
18
- packages=find_packages(exclude=["*tests*"]),
19
- install_requires=[
20
- 'otpauth>=2.0.0',
21
- 'qrcode>=7.4.2',
22
- 'pyzbar>=0.1.9',
23
- 'pillow>=9.5.0',
24
- ],
25
- cmdclass=versioneer.get_cmdclass(),
26
- entry_points={
27
- "console_scripts": [
28
- 'pyauthenticator=pyauthenticator.__main__:command_line_parser'
29
- ]
30
- }
31
- )
@@ -1,88 +0,0 @@
1
- import unittest
2
- import os
3
- import subprocess
4
- import json
5
- from contextlib import redirect_stdout
6
- from io import StringIO
7
- from pyauthenticator.share import expand_path, write_config, config_file
8
- from pyauthenticator.__main__ import command_line_parser
9
-
10
-
11
- class CmdSubprocessTest(unittest.TestCase):
12
- @classmethod
13
- def setUpClass(cls):
14
- cls.config_dict = {
15
- "test": "otpauth://totp/Test%3A%20root%40github.com?secret=6IQXETC4ADOSMMUN&issuer=Test"
16
- }
17
- cls.config_path = expand_path(config_file)
18
- if not os.path.exists(cls.config_path):
19
- write_config(
20
- config_dict=cls.config_dict
21
- )
22
-
23
- def test_main_generate_two_factor(self):
24
- code = subprocess.check_output(
25
- ["coverage", "run", "-a", "pyauthenticator", "test"],
26
- universal_newlines=True
27
- )
28
- self.assertEqual(len(code.replace("\n", "")), 6)
29
-
30
- def test_main_generate_qr_code(self):
31
- subprocess.check_output(
32
- ["coverage", "run", "-a", "pyauthenticator", "-qr", "test"],
33
- universal_newlines=True
34
- )
35
- self.assertTrue(os.path.exists("test.png"))
36
- subprocess.check_output(
37
- ["coverage", "run", "-a", "pyauthenticator", "-a", "test.png", "test2"],
38
- universal_newlines=True
39
- )
40
- with open(self.config_path, "r") as f:
41
- config_dict = json.load(f)
42
- self.assertTrue("test2" in config_dict.keys())
43
-
44
-
45
- class CmdParserTest(unittest.TestCase):
46
- @classmethod
47
- def setUpClass(cls):
48
- cls.config_dict = {
49
- "test": "otpauth://totp/Test%3A%20root%40github.com?secret=6IQXETC4ADOSMMUN&issuer=Test"
50
- }
51
- cls.config_path = expand_path(config_file)
52
- if not os.path.exists(cls.config_path):
53
- write_config(
54
- config_dict=cls.config_dict
55
- )
56
-
57
- def test_main_generate_two_factor(self):
58
- with redirect_stdout(StringIO()) as sout:
59
- command_line_parser(cmd_args=["test"])
60
- self.assertEqual(len(sout.getvalue().rstrip('\n')), 6)
61
- with redirect_stdout(StringIO()) as sout:
62
- command_line_parser(cmd_args=["test3"])
63
- self.assertEqual(
64
- sout.getvalue().split("\n")[0],
65
- "The service \"test3\" does not exist."
66
- )
67
-
68
- def test_main_generate_qr_code(self):
69
- with redirect_stdout(StringIO()) as sout:
70
- command_line_parser(cmd_args=["-qr", "test"])
71
- self.assertEqual(
72
- sout.getvalue(),
73
- "The qrcode file <test.png> was generated.\n"
74
- )
75
- self.assertTrue(os.path.exists("test.png"))
76
- with redirect_stdout(StringIO()) as sout:
77
- command_line_parser(cmd_args=["-a", "test.png", "test4"])
78
- self.assertEqual(
79
- sout.getvalue(),
80
- "The service 'test4' was added, from file <test.png>.\n"
81
- )
82
- with open(self.config_path, "r") as f:
83
- config_dict = json.load(f)
84
- self.assertTrue("test4" in config_dict.keys())
85
-
86
-
87
- if __name__ == '__main__':
88
- unittest.main()
@@ -1,48 +0,0 @@
1
- """
2
- Test for core functionality
3
- """
4
- import unittest
5
- from pyauthenticator.share import list_services, load_config, generate_qrcode, add_service, get_two_factor_code
6
- import os
7
-
8
-
9
- class TestCore(unittest.TestCase):
10
- @classmethod
11
- def setUpClass(cls):
12
- cls.qr_code_png = "test.png"
13
- cls.config_dict = {
14
- "test": "otpauth://totp/Test%3A%20root%40github.com?secret=6IQXETC4ADOSMMUN&issuer=Test&period=60"
15
- }
16
- generate_qrcode(
17
- key="test",
18
- config_dict=cls.config_dict,
19
- file_name=None
20
- )
21
-
22
- @classmethod
23
- def tearDownClass(cls):
24
- os.remove(cls.qr_code_png)
25
-
26
- def test_list_services(self):
27
- service_lst = list_services(config_dict=self.config_dict)
28
- self.assertEqual(["test"], service_lst)
29
-
30
- def test_add_service(self):
31
- config_file = "test_config.json"
32
- add_service(
33
- key="test",
34
- qrcode_png_file_name=self.qr_code_png,
35
- config_dict={},
36
- config_file_to_write=config_file
37
- )
38
- config_reload = load_config(config_file_to_load=config_file)
39
- self.assertDictEqual(config_reload, self.config_dict)
40
- os.remove(config_file)
41
-
42
- def test_get_two_factor_code(self):
43
- code = get_two_factor_code(key="test", config_dict=self.config_dict)
44
- self.assertEqual(len(code), 6)
45
-
46
-
47
- if __name__ == '__main__':
48
- unittest.main()