viewo-api 1.0.0__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.
Files changed (32) hide show
  1. viewo_api-1.0.0/LICENSE +21 -0
  2. viewo_api-1.0.0/PKG-INFO +25 -0
  3. viewo_api-1.0.0/README +1 -0
  4. viewo_api-1.0.0/pyproject.toml +89 -0
  5. viewo_api-1.0.0/setup.cfg +4 -0
  6. viewo_api-1.0.0/tests/test_login.py +58 -0
  7. viewo_api-1.0.0/tests/test_visitor.py +18 -0
  8. viewo_api-1.0.0/viewo_api/__init__.py +46 -0
  9. viewo_api-1.0.0/viewo_api/config.py +49 -0
  10. viewo_api-1.0.0/viewo_api/mail/__init__.py +8 -0
  11. viewo_api-1.0.0/viewo_api/mail/app.py +33 -0
  12. viewo_api-1.0.0/viewo_api/mail/send.py +37 -0
  13. viewo_api-1.0.0/viewo_api/mail/tech.py +42 -0
  14. viewo_api-1.0.0/viewo_api/mail/user.py +19 -0
  15. viewo_api-1.0.0/viewo_api/message.py +48 -0
  16. viewo_api-1.0.0/viewo_api/user/__init__.py +14 -0
  17. viewo_api-1.0.0/viewo_api/user/config.py +18 -0
  18. viewo_api-1.0.0/viewo_api/user/create.py +31 -0
  19. viewo_api-1.0.0/viewo_api/user/data.py +129 -0
  20. viewo_api-1.0.0/viewo_api/user/password.py +101 -0
  21. viewo_api-1.0.0/viewo_api/user/serialize.py +51 -0
  22. viewo_api-1.0.0/viewo_api/user/state.py +30 -0
  23. viewo_api-1.0.0/viewo_api/user/token.py +10 -0
  24. viewo_api-1.0.0/viewo_api/user/update.py +49 -0
  25. viewo_api-1.0.0/viewo_api/utils.py +14 -0
  26. viewo_api-1.0.0/viewo_api/visitor.py +68 -0
  27. viewo_api-1.0.0/viewo_api.egg-info/PKG-INFO +25 -0
  28. viewo_api-1.0.0/viewo_api.egg-info/SOURCES.txt +30 -0
  29. viewo_api-1.0.0/viewo_api.egg-info/dependency_links.txt +1 -0
  30. viewo_api-1.0.0/viewo_api.egg-info/entry_points.txt +2 -0
  31. viewo_api-1.0.0/viewo_api.egg-info/requires.txt +10 -0
  32. viewo_api-1.0.0/viewo_api.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Helmut Konrad Schewe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: viewo_api
3
+ Version: 1.0.0
4
+ Author-email: Helmut Konrad Schewe <helmutus@outlook.com>
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/anaticulae/viewo_api
7
+ Project-URL: Repository, https://github.com/anaticulae/viewo_api
8
+ Classifier: Programming Language :: Python :: 3.12
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Classifier: Programming Language :: Python :: 3.14
11
+ Requires-Python: >=3.12
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: flask<4.0.0,>=3.1.3
15
+ Requires-Dist: flask-login<1.0.0,>=0.6.3
16
+ Requires-Dist: flask-mail<1.0.0,>=0.10.0
17
+ Requires-Dist: utilo<3.0.0,>=2.112.1
18
+ Requires-Dist: configos<2.0.0,>=1.0.4
19
+ Provides-Extra: dev
20
+ Requires-Dist: utilotest<2.0.0,>=1.1.1; extra == "dev"
21
+ Requires-Dist: pytest-localserver<2.0.0,>=0.10.0; extra == "dev"
22
+ Requires-Dist: aiosmtpd==1.4.6; extra == "dev"
23
+ Dynamic: license-file
24
+
25
+ # viewo_api
viewo_api-1.0.0/README ADDED
@@ -0,0 +1 @@
1
+ # viewo_api
@@ -0,0 +1,89 @@
1
+ [project]
2
+ name = "viewo_api"
3
+ version = "1.0.0"
4
+ description = ""
5
+ requires-python = ">=3.12"
6
+ authors = [
7
+ { name = "Helmut Konrad Schewe", email = "helmutus@outlook.com" },
8
+ ]
9
+ dependencies = [
10
+ "flask>=3.1.3,<4.0.0",
11
+ "flask-login>=0.6.3,<1.0.0",
12
+ "flask-mail>=0.10.0,<1.0.0",
13
+ "utilo>=2.112.1,<3.0.0",
14
+ "configos>=1.0.4,<2.0.0",
15
+ ]
16
+ keywords = []
17
+ classifiers = [
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ "Programming Language :: Python :: 3.14",
21
+ ]
22
+ license = "MIT"
23
+ license-files = [
24
+ "LICENSE",
25
+ ]
26
+
27
+ [project.readme]
28
+ file = "README"
29
+ content-type = "text/markdown"
30
+
31
+ [project.optional-dependencies]
32
+ dev = [
33
+ "utilotest>=1.1.1,<2.0.0",
34
+ "pytest-localserver>=0.10.0,<2.0.0",
35
+ "aiosmtpd==1.4.6",
36
+ ]
37
+
38
+ [project.urls]
39
+ Homepage = "https://github.com/anaticulae/viewo_api"
40
+ Repository = "https://github.com/anaticulae/viewo_api"
41
+
42
+ [project.scripts]
43
+ viewo_api = "viewo_api.cli:main"
44
+
45
+ [build-system]
46
+ requires = [
47
+ "setuptools>=82.0.1",
48
+ "wheel>=0.46.3",
49
+ ]
50
+ build-backend = "setuptools.build_meta"
51
+
52
+ [tool.semantic_release]
53
+ version_toml = [
54
+ "pyproject.toml:project.version",
55
+ ]
56
+
57
+ [tool.semantic_release.changelog]
58
+ mode = "init"
59
+ output_format = "md"
60
+
61
+ [tool.semantic_release.changelog.default_templates]
62
+ changelog_file = "CHANGELOG"
63
+
64
+ [tool.semantic_release.commit_parser_options]
65
+ patch_tags = [
66
+ "fix",
67
+ "perf",
68
+ "build",
69
+ "chore",
70
+ "ci",
71
+ "docs",
72
+ "style",
73
+ "refactor",
74
+ "test",
75
+ "deps",
76
+ ]
77
+
78
+ [tool.setuptools.packages.find]
79
+ where = [
80
+ ".",
81
+ ]
82
+ include = [
83
+ "viewo_api",
84
+ "viewo_api.mail",
85
+ "viewo_api.user",
86
+ ]
87
+ exclude = [
88
+ "tests/*",
89
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,58 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import http
11
+
12
+ import utilotest
13
+
14
+ import viewo_api
15
+ import viewo_api.user.state
16
+
17
+ PASSWORD = 'helmut1337' # nosec
18
+ MAIL = 'modern@modern.com'
19
+
20
+
21
+ def test_login_logout(client): # pylint:disable=W0613
22
+ user = viewo_api.create_user(password=PASSWORD)
23
+ utilotest.apipost(
24
+ client,
25
+ '/login',
26
+ data={
27
+ 'user': user.userid,
28
+ 'password': PASSWORD,
29
+ },
30
+ )
31
+ utilotest.apicall(client, '/logout')
32
+
33
+
34
+ def test_login_wrong_password(client): # pylint:disable=W0613
35
+ user = viewo_api.create_user(password=PASSWORD)
36
+ utilotest.apipost(
37
+ client,
38
+ '/login',
39
+ data={
40
+ 'user': user.userid,
41
+ 'password': 'wrong'
42
+ }, # nosec
43
+ expected=http.HTTPStatus.FORBIDDEN,
44
+ )
45
+
46
+
47
+ def test_login_withmail(client): # pylint:disable=W0613
48
+ user = viewo_api.create_user(password=PASSWORD)
49
+ assert viewo_api.user_email_update(email=MAIL, userid=user.userid)
50
+ utilotest.apipost(
51
+ client,
52
+ '/login',
53
+ data={
54
+ 'user': MAIL,
55
+ 'password': PASSWORD,
56
+ },
57
+ )
58
+ utilotest.apicall(client, '/logout')
@@ -0,0 +1,18 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import utilotest
11
+
12
+
13
+ def test_request(client): # pylint:disable=W0621
14
+ response = utilotest.decode(client.get('/return_request'))
15
+ assert 'Env(hashed=' in response
16
+ # assert "agent='Werkzeug/3.1.8', method='GET', path='/return_request'" in response
17
+ assert "agent='Werkzeug/" in response
18
+ assert "', method='GET', path='/return_request'" in response
@@ -0,0 +1,46 @@
1
+ #==============================================================================
2
+ # C O P Y R I G H T
3
+ #------------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ #==============================================================================
9
+
10
+ import importlib.metadata
11
+ import os
12
+
13
+ from viewo_api.mail.send import send_mail
14
+ from viewo_api.mail.tech import send_upload_info
15
+ from viewo_api.message import bad
16
+ from viewo_api.message import internal
17
+ from viewo_api.message import notfound
18
+ from viewo_api.message import success
19
+ from viewo_api.user.config import userspath
20
+ from viewo_api.user.create import create_user
21
+ from viewo_api.user.data import FlaskUser
22
+ from viewo_api.user.data import User
23
+ from viewo_api.user.data import hash_email
24
+ from viewo_api.user.data import user_fromid
25
+ from viewo_api.user.data import userlist_raw
26
+ from viewo_api.user.data import userpublic_fromid
27
+ from viewo_api.user.password import password_generate
28
+ from viewo_api.user.password import password_public_set
29
+ from viewo_api.user.password import password_valid
30
+ from viewo_api.user.password import password_verify
31
+ from viewo_api.user.password import username_valid
32
+ from viewo_api.user.state import login
33
+ from viewo_api.user.state import logout
34
+ from viewo_api.user.update import user_append_document
35
+ from viewo_api.user.update import user_email_update
36
+ from viewo_api.user.update import user_update
37
+ from viewo_api.utils import json
38
+ from viewo_api.visitor import access
39
+ from viewo_api.visitor import oneline
40
+ from viewo_api.visitor import request
41
+ from viewo_api.visitor import user_current
42
+ from viewo_api.visitor import user_hash
43
+
44
+ __version__ = importlib.metadata.version('viewo_api')
45
+
46
+ ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
@@ -0,0 +1,49 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import configos
11
+ import flask
12
+
13
+ PASSWORD_SALT = configos.HV_STR(default='THISISSALT')
14
+
15
+ PASSWORD_DIGITS = configos.HV_INT_PLUS(default=64)
16
+
17
+ PASSWORD_RANDOM_LENGTH = configos.HV_INT_PLUS(default=16)
18
+
19
+ PASSWORD_MIN_LENGTH = configos.HV_INT_PLUS(default=8)
20
+ PASSWORD_MAX_LENGTH = configos.HV_INT_PLUS(default=64)
21
+
22
+ USER_LENGTH = configos.HV_INT_PLUS(default=16)
23
+
24
+ EMAIL_SALT = configos.HV_STR(default='EMAILNOGMAIL')
25
+
26
+ EMAIL_DIGITS = configos.HV_INT_PLUS(default=64)
27
+
28
+ EMAIL_NO_AUTHOR = configos.HV_STR(default='postkasten@checkitweg.de')
29
+
30
+ EMAIL_SERVER = configos.HV_STR(default='guardian@checkitweg.de')
31
+ # inform admin about new customer
32
+ EMAIL_CUSTOMER_NEW = configos.env('CHANIA_CUSTOMER_NEW', default=None)
33
+
34
+
35
+ def common():
36
+ return flask.current_app.config['COMMON']
37
+
38
+
39
+ def todo():
40
+ return flask.current_app.config['TODO']
41
+
42
+
43
+ def ready():
44
+ return flask.current_app.config['READY']
45
+
46
+
47
+ configos.todo = todo
48
+ configos.ready = ready
49
+ configos.share = common
@@ -0,0 +1,8 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
@@ -0,0 +1,33 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import flask
11
+ import flask_mail
12
+
13
+
14
+ def register(app):
15
+ mail = flask_mail.Mail(app)
16
+ app.mail = mail
17
+
18
+
19
+ def configure(
20
+ server,
21
+ user=None,
22
+ password=None,
23
+ tls: bool = False,
24
+ port: int = 587,
25
+ ):
26
+ config = flask.current_app.config
27
+ config['MAIL_SERVER'] = server
28
+ config['MAIL_PORT'] = port
29
+ config['MAIL_USERNAME'] = user
30
+ config['MAIL_PASSWORD'] = password
31
+ config['MAIL_USE_TLS'] = tls
32
+ config['MAIL_SUPPRESS_SEND'] = False
33
+ flask.current_app.mail.init_app(flask.current_app)
@@ -0,0 +1,37 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import flask
11
+ import flask_mail
12
+
13
+ import viewo_api.config
14
+
15
+
16
+ def send_mail(
17
+ recipient: str,
18
+ subject: str,
19
+ body: str,
20
+ author: str = viewo_api.config.EMAIL_NO_AUTHOR,
21
+ ):
22
+ msg = flask_mail.Message(
23
+ recipients=[recipient],
24
+ subject=subject,
25
+ body=body,
26
+ sender=author.value,
27
+ )
28
+ flask.current_app.mail.send(msg)
29
+ # threading.Thread(
30
+ # target=send_async,
31
+ # args=[flask.current_app, msg],
32
+ # ).start()
33
+
34
+
35
+ # def send_async( msg):
36
+ # with app.app_context():
37
+ # app.mail.send(msg)
@@ -0,0 +1,42 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import utilo
11
+
12
+ import viewo_api
13
+ import viewo_api.config
14
+
15
+ MESSAGE_NEW_SUBJECT = 'New Customer'
16
+ MESSAGE_NEW_BODY = """\
17
+ New customer on the line!
18
+
19
+ {data}
20
+ """
21
+
22
+
23
+ def send_upload_info(data: dict = None):
24
+ if viewo_api.config.EMAIL_CUSTOMER_NEW is None:
25
+ msg = 'CONFIGURE: CHANIA_CUSTOMER_NEW could not send_upload_info'
26
+ utilo.error(msg)
27
+ return
28
+ data = utilo.dict_dump(data) if data else ''
29
+ body = MESSAGE_NEW_BODY.format(data=data)
30
+ viewo_api.send_mail(
31
+ recipient=viewo_api.config.EMAIL_CUSTOMER_NEW,
32
+ subject=MESSAGE_NEW_SUBJECT,
33
+ body=body,
34
+ )
35
+
36
+
37
+ def send_server_status():
38
+ pass
39
+
40
+
41
+ def send_login_request():
42
+ """Send token before login into admin area."""
@@ -0,0 +1,19 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ # import viewo_api.mail.send
11
+
12
+ # TEMPLATE = """\
13
+ # Änderung erfordert Bestätigung
14
+
15
+ # """
16
+
17
+ # def verify_change(email: str, token: str, title: str, message: str):
18
+ # """Send E-Mail to user to verify user action."""
19
+ # # viewo_api.email.send.email()
@@ -0,0 +1,48 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import flask
11
+
12
+ import viewo_api
13
+
14
+
15
+ def success(message: str = 'completed', **args):
16
+ result = viewo_api.json({
17
+ 'message': message,
18
+ **args,
19
+ })
20
+ return result, 200
21
+
22
+
23
+ def notfound(message: str):
24
+ result = viewo_api.json({
25
+ 'error': 'not found',
26
+ 'message': message,
27
+ })
28
+ return result, 404
29
+
30
+
31
+ def bad(message: str):
32
+ result = viewo_api.json({
33
+ 'error': 'bad request',
34
+ 'message': message,
35
+ })
36
+ return result, 400
37
+
38
+
39
+ def internal(message: str):
40
+ result = viewo_api.json({
41
+ 'error': 'internal',
42
+ 'message': message,
43
+ })
44
+ return result, 500
45
+
46
+
47
+ def flash(message: str):
48
+ flask.flash(message)
@@ -0,0 +1,14 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+ """Users
10
+ =====
11
+
12
+ * public user: open browser and view page
13
+ * private user: account which is logged in via login form
14
+ """
@@ -0,0 +1,18 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import os
11
+
12
+ import viewo_api.config
13
+
14
+
15
+ def userspath():
16
+ result = os.path.join(viewo_api.config.common(), 'users')
17
+ os.makedirs(result, exist_ok=True)
18
+ return result
@@ -0,0 +1,31 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import os
11
+
12
+ import utilo
13
+
14
+ import viewo_api.config
15
+ import viewo_api.user.data
16
+ import viewo_api.user.password
17
+ import viewo_api.user.update
18
+
19
+
20
+ def create_user(password: str, userid: str = None):
21
+ # validate password
22
+ password = viewo_api.user.password.hashed(password)
23
+ # generate new random user name
24
+ if not userid:
25
+ userid = utilo.tmpname(viewo_api.config.USER_LENGTH)
26
+ while os.path.exists(viewo_api.user.data.userpath(userid, False)):
27
+ userid = utilo.tmpname(viewo_api.config.USER_LENGTH)
28
+ new = viewo_api.user.data.User(userid=userid, password=password)
29
+ # write to user database
30
+ viewo_api.user.update.user_update(new)
31
+ return new
@@ -0,0 +1,129 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import dataclasses
11
+ import os
12
+
13
+ import flask_login
14
+ import utilo
15
+
16
+ import viewo_api.user.config
17
+ import viewo_api.user.password
18
+ import viewo_api.user.serialize
19
+
20
+
21
+ @dataclasses.dataclass(unsafe_hash=True)
22
+ class User:
23
+ email: str = None
24
+ password: str = None
25
+ documents: list = dataclasses.field(default_factory=list)
26
+ payed: int = None
27
+ userid: str = None
28
+
29
+ def append_document(self, documentid: str):
30
+ assert documentid not in self.documents # pylint:disable=E1135
31
+ self.documents.append(documentid) # pylint:disable=E1101
32
+
33
+ def __getitem__(self, index):
34
+ return self.documents[index] # pylint:disable=E1136
35
+
36
+
37
+ Users = list[User]
38
+
39
+
40
+ def user_fromid(userid: str) -> User:
41
+ path = userpath(userid, verify=True)
42
+ if not path:
43
+ # try to find user via email
44
+ user = user_from_email(userid)
45
+ return user
46
+ if not path:
47
+ return None
48
+ loaded = viewo_api.user.serialize.load_user(path)
49
+ return loaded
50
+
51
+
52
+ def user_from_email(email: str) -> User:
53
+ email = hash_email(email)
54
+ for user in users():
55
+ if user.email != email:
56
+ continue
57
+ return user
58
+ return None
59
+
60
+
61
+ def userpath(userid: int, verify: bool = False) -> str:
62
+ result = os.path.join(viewo_api.user.config.userspath(), userid)
63
+ if verify and not os.path.exists(result):
64
+ return None
65
+ return result
66
+
67
+
68
+ def users():
69
+ upath = viewo_api.user.config.userspath()
70
+ common_user = viewo_api.user.serialize.load_users(upath)
71
+ return common_user
72
+
73
+
74
+ def userlist_raw():
75
+ upath = viewo_api.user.config.userspath()
76
+ common_user = viewo_api.user.serialize.load_users(upath)
77
+ raw = [viewo_api.user.serialize.user_raw(item) for item in common_user]
78
+ return raw
79
+
80
+
81
+ def userpublic_fromid(userid: str) -> dict:
82
+ """Returns public data which are not security/data privacy
83
+ relevant."""
84
+ user = user_fromid(userid)
85
+ if not user:
86
+ return None
87
+ result = {
88
+ 'user': userid,
89
+ 'documents': list(user.documents),
90
+ }
91
+ if user.email:
92
+ # TODO: IS THIS A GOOD IDEA?
93
+ # TODO: VERIFY BEFORE RELEASE XXX
94
+ result['email'] = user.email
95
+ if user.payed is not None:
96
+ result['payed'] = user.payed
97
+ return result
98
+
99
+
100
+ def hash_email(email: str):
101
+ """\
102
+ >>> hash_email('toy@roy.com')
103
+ '...'
104
+ """
105
+ email = utilo.secure_hash(
106
+ email,
107
+ salt=viewo_api.config.EMAIL_SALT,
108
+ digits=viewo_api.config.EMAIL_DIGITS,
109
+ )
110
+ return email
111
+
112
+
113
+ @dataclasses.dataclass
114
+ class FlaskUser(flask_login.UserMixin):
115
+ identifier: str = None
116
+
117
+ def get_id(self):
118
+ return self.identifier
119
+
120
+ @property
121
+ def payed(self):
122
+ user = user_fromid(self.identifier)
123
+ if not user:
124
+ return 0
125
+ return user.payed
126
+
127
+ @property
128
+ def is_admin(self):
129
+ return False
@@ -0,0 +1,101 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import flask
11
+ import utilo
12
+
13
+ import viewo_api.config
14
+ import viewo_api.user
15
+
16
+
17
+ class PasswordError(ValueError):
18
+ pass
19
+
20
+
21
+ class PasswordTooShort(PasswordError):
22
+ pass
23
+
24
+
25
+ class PasswordTooLong(PasswordError):
26
+ pass
27
+
28
+
29
+ def password_valid(password: str) -> bool:
30
+ """\
31
+ >>> password_valid('short')
32
+ Traceback (most recent call last):
33
+ ...
34
+ viewo_api.user.password.PasswordTooShort
35
+
36
+ >>> password_valid('VeryLongPassword1337')
37
+ True
38
+ """
39
+ if password is None:
40
+ return False
41
+ password = str(password)
42
+ if len(password) < viewo_api.config.PASSWORD_MIN_LENGTH:
43
+ raise PasswordTooShort
44
+ if len(password) > viewo_api.config.PASSWORD_MAX_LENGTH:
45
+ raise PasswordTooLong
46
+ return True
47
+
48
+
49
+ def hashed(password: str) -> str:
50
+ """\
51
+ >>> len(hashed('nononono'))
52
+ 64
53
+ """
54
+ password_valid(password)
55
+ password = utilo.secure_hash(
56
+ password,
57
+ salt=viewo_api.config.PASSWORD_SALT,
58
+ digits=viewo_api.config.PASSWORD_DIGITS,
59
+ )
60
+ return password
61
+
62
+
63
+ def password_generate(length=viewo_api.config.PASSWORD_RANDOM_LENGTH) -> str:
64
+ """\
65
+ >>> assert len(password_generate(10)) == 10
66
+ """
67
+ random = utilo.tmpname(length)
68
+ return random
69
+
70
+
71
+ def password_verify(userid: str, password: str) -> bool:
72
+ user = viewo_api.user.data.user_fromid(userid)
73
+ if not user:
74
+ # try to select user via email
75
+ user = viewo_api.user.data.user_from_email(userid)
76
+ if not user:
77
+ return False
78
+ try:
79
+ password = viewo_api.user.password.hashed(password)
80
+ except viewo_api.user.password.PasswordError:
81
+ return False
82
+ return password == user.password
83
+
84
+
85
+ def password_public_set(password: str):
86
+ # TODO: CHECK FOR SECURITY ISSUES
87
+ flask.globals.session['password_raw'] = password
88
+
89
+
90
+ def username_valid(name) -> False:
91
+ """\
92
+ >>> username_valid('not a valid user')
93
+ False
94
+ >>> username_valid('012ABxxF012ABxxF')
95
+ True
96
+ >>> username_valid(' 12 xxx')
97
+ False
98
+ """
99
+ if not len(name) == viewo_api.config.USER_LENGTH:
100
+ return False
101
+ return all(item.isalnum() for item in name)
@@ -0,0 +1,51 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import os
11
+
12
+ import utilo
13
+
14
+ import viewo_api.user.data
15
+
16
+
17
+ def user_raw(user: 'User') -> dict:
18
+ result = {
19
+ 'documents': user.documents,
20
+ 'userid': user.userid,
21
+ 'payed': user.payed,
22
+ }
23
+ return result
24
+
25
+
26
+ def dump_users(users: 'Users', path: str):
27
+ assert os.path.exists(path), path
28
+ for user in users:
29
+ dumped = dump_user(user)
30
+ outpath = os.path.join(path, user.userid)
31
+ utilo.file_replace(outpath, dumped)
32
+
33
+
34
+ def load_users(path: str) -> 'Users':
35
+ assert os.path.exists(path), path
36
+ files = utilo.file_list(path)
37
+ files = utilo.files_sort(files)
38
+ loaded = [load_user(os.path.join(path, item)) for item in files]
39
+ return loaded
40
+
41
+
42
+ def dump_user(user: 'User') -> str:
43
+ raw = vars(user)
44
+ dumped = utilo.yaml_dump(raw)
45
+ return dumped
46
+
47
+
48
+ def load_user(path: str) -> 'User':
49
+ loaded = utilo.yaml_load(path)
50
+ result = viewo_api.user.data.User(**loaded)
51
+ return result
@@ -0,0 +1,30 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import flask_login
11
+
12
+ import viewo_api.message
13
+ import viewo_api.user.data
14
+
15
+
16
+ def login(userid: str, password: str, publicpassword: bool = False):
17
+ if not viewo_api.password_verify(userid, password):
18
+ return False
19
+ flask_login.login_user(viewo_api.FlaskUser(userid))
20
+ if publicpassword:
21
+ viewo_api.password_public_set(password)
22
+ viewo_api.message.flash('Login erfolgreich.')
23
+ return True
24
+
25
+
26
+ def logout():
27
+ flask_login.logout_user()
28
+ # remove public password
29
+ viewo_api.password_public_set('')
30
+ viewo_api.message.flash('Logout erfolgreich.')
@@ -0,0 +1,10 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ # TODO: SEE MAIL.USER
@@ -0,0 +1,49 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import utilo
11
+
12
+ import viewo_api
13
+ import viewo_api.user.data
14
+ import viewo_api.user.serialize
15
+
16
+
17
+ def user_update(user: 'User', verify=False) -> bool:
18
+ path = viewo_api.user.data.userpath(user.userid, verify=verify)
19
+ if not path:
20
+ return False
21
+ dumped = viewo_api.user.serialize.dump_user(user)
22
+ # TODO: NOT THREAD SAFE
23
+ utilo.file_replace(path, dumped)
24
+ return True
25
+
26
+
27
+ PUBLIC_OWNER = '00000000'
28
+
29
+
30
+ def user_append_document(userid: str, documentid: str) -> bool:
31
+ # TODO: REPLACE LATER
32
+ if userid == PUBLIC_OWNER:
33
+ # if userid == link.PUBLIC_OWNER:
34
+ # do not add public document to public user
35
+ return False
36
+ user = viewo_api.user.data.user_fromid(userid)
37
+ user.append_document(documentid)
38
+ user_update(user)
39
+ return True
40
+
41
+
42
+ def user_email_update(email: str, userid: str = None) -> bool:
43
+ user = viewo_api.user.data.user_fromid(userid)
44
+ if not user:
45
+ return False
46
+ user.email = viewo_api.hash_email(email)
47
+ # write to user database
48
+ user_update(user)
49
+ return True
@@ -0,0 +1,14 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import flask
11
+
12
+
13
+ def json(content) -> dict:
14
+ return flask.jsonify(content)
@@ -0,0 +1,68 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import collections
11
+
12
+ import flask
13
+ import utilo
14
+
15
+ Access = collections.namedtuple('Access', 'ip, agent')
16
+ Env = collections.namedtuple(
17
+ 'Env',
18
+ 'hashed, time, ip, agent, method, path, data, dictum',
19
+ )
20
+
21
+
22
+ def request() -> Env:
23
+ env = flask.request.environ
24
+ if not env:
25
+ return None
26
+ ip = env.get('REMOTE_ADDR', 'NO_REMOTE_ADDR')
27
+ agent = env.get('HTTP_USER_AGENT', 'NO_HTTP_USER_AGENT')
28
+ hashed = user_hash(ip, agent)
29
+ method = env.get('REQUEST_METHOD', 'NO_USER_AGENT')
30
+ path = env.get('PATH_INFO', 'NO_PATH_INFO')
31
+ data = env.get('QUERY_STRING', 'NO_QUERY_STRING')
32
+ data = oneline(data)
33
+ time = utilo.now()
34
+ try:
35
+ dictum = flask.request.form.to_dict()
36
+ except OSError:
37
+ utilo.error(f'invalid/incomplete request: {flask.request}')
38
+ utilo.debug(vars(flask.request))
39
+ dictum = {}
40
+ return Env(hashed, time, ip, agent, method, path, data, dictum)
41
+
42
+
43
+ def user_hash(ip, agent) -> int:
44
+ return hash(f'{ip}:{agent}')
45
+
46
+
47
+ def access(env: Env):
48
+ return Access(env.ip, env.agent)
49
+
50
+
51
+ def user_current() -> int:
52
+ current = request()
53
+ hashed = user_hash(current.ip, current.agent)
54
+ return hashed
55
+
56
+
57
+ def oneline(data, limit: int = 256):
58
+ r"""Remove newlines and shrink content to `limit` to avoid logging to
59
+ mutch data.
60
+
61
+ >>> oneline('first\nnewline')
62
+ 'first%NL%newline'
63
+ """
64
+ data = data.replace('\r\n', '%WNL%')
65
+ data = data.replace('\n', '%NL%')
66
+ data = data.replace(' ', '%WS%')
67
+ data = data[0:limit]
68
+ return data
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: viewo_api
3
+ Version: 1.0.0
4
+ Author-email: Helmut Konrad Schewe <helmutus@outlook.com>
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/anaticulae/viewo_api
7
+ Project-URL: Repository, https://github.com/anaticulae/viewo_api
8
+ Classifier: Programming Language :: Python :: 3.12
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Classifier: Programming Language :: Python :: 3.14
11
+ Requires-Python: >=3.12
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: flask<4.0.0,>=3.1.3
15
+ Requires-Dist: flask-login<1.0.0,>=0.6.3
16
+ Requires-Dist: flask-mail<1.0.0,>=0.10.0
17
+ Requires-Dist: utilo<3.0.0,>=2.112.1
18
+ Requires-Dist: configos<2.0.0,>=1.0.4
19
+ Provides-Extra: dev
20
+ Requires-Dist: utilotest<2.0.0,>=1.1.1; extra == "dev"
21
+ Requires-Dist: pytest-localserver<2.0.0,>=0.10.0; extra == "dev"
22
+ Requires-Dist: aiosmtpd==1.4.6; extra == "dev"
23
+ Dynamic: license-file
24
+
25
+ # viewo_api
@@ -0,0 +1,30 @@
1
+ LICENSE
2
+ README
3
+ pyproject.toml
4
+ tests/test_login.py
5
+ tests/test_visitor.py
6
+ viewo_api/__init__.py
7
+ viewo_api/config.py
8
+ viewo_api/message.py
9
+ viewo_api/utils.py
10
+ viewo_api/visitor.py
11
+ viewo_api.egg-info/PKG-INFO
12
+ viewo_api.egg-info/SOURCES.txt
13
+ viewo_api.egg-info/dependency_links.txt
14
+ viewo_api.egg-info/entry_points.txt
15
+ viewo_api.egg-info/requires.txt
16
+ viewo_api.egg-info/top_level.txt
17
+ viewo_api/mail/__init__.py
18
+ viewo_api/mail/app.py
19
+ viewo_api/mail/send.py
20
+ viewo_api/mail/tech.py
21
+ viewo_api/mail/user.py
22
+ viewo_api/user/__init__.py
23
+ viewo_api/user/config.py
24
+ viewo_api/user/create.py
25
+ viewo_api/user/data.py
26
+ viewo_api/user/password.py
27
+ viewo_api/user/serialize.py
28
+ viewo_api/user/state.py
29
+ viewo_api/user/token.py
30
+ viewo_api/user/update.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ viewo_api = viewo_api.cli:main
@@ -0,0 +1,10 @@
1
+ flask<4.0.0,>=3.1.3
2
+ flask-login<1.0.0,>=0.6.3
3
+ flask-mail<1.0.0,>=0.10.0
4
+ utilo<3.0.0,>=2.112.1
5
+ configos<2.0.0,>=1.0.4
6
+
7
+ [dev]
8
+ utilotest<2.0.0,>=1.1.1
9
+ pytest-localserver<2.0.0,>=0.10.0
10
+ aiosmtpd==1.4.6
@@ -0,0 +1 @@
1
+ viewo_api