rbeesoft 2.0.1__tar.gz → 2.0.3__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 (31) hide show
  1. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/PKG-INFO +1 -1
  2. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/pyproject.toml +1 -1
  3. rbeesoft-2.0.3/src/rbeesoft/app/generatekeys.py +37 -0
  4. rbeesoft-2.0.3/src/rbeesoft/app/license.json +15 -0
  5. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/src/rbeesoft/app/main.py +4 -4
  6. rbeesoft-2.0.3/src/rbeesoft/app/signlicense.py +71 -0
  7. {rbeesoft-2.0.1/src/rbeesoft/app/core → rbeesoft-2.0.3/src/rbeesoft/app/ui}/processes/dummyprocess.py +1 -1
  8. rbeesoft-2.0.3/src/rbeesoft/app/ui/rbeesoftmainwindow.py +146 -0
  9. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/src/rbeesoft/app/ui/settings.py +6 -0
  10. rbeesoft-2.0.3/src/rbeesoft/common/licenseexception.py +2 -0
  11. rbeesoft-2.0.3/src/rbeesoft/common/licensemanager.py +47 -0
  12. rbeesoft-2.0.1/src/rbeesoft/app/ui/rbeesoftmainwindow.py +0 -77
  13. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/README.md +0 -0
  14. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/src/rbeesoft/__init__.py +0 -0
  15. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/src/rbeesoft/app/__init__.py +0 -0
  16. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/src/rbeesoft/app/core/__init__.py +0 -0
  17. {rbeesoft-2.0.1/src/rbeesoft/app/core/processes → rbeesoft-2.0.3/src/rbeesoft/app/ui}/__init__.py +0 -0
  18. {rbeesoft-2.0.1/src/rbeesoft/app/ui → rbeesoft-2.0.3/src/rbeesoft/app/ui/processes}/__init__.py +0 -0
  19. {rbeesoft-2.0.1/src/rbeesoft/app/core → rbeesoft-2.0.3/src/rbeesoft/app/ui}/processes/process.py +0 -0
  20. {rbeesoft-2.0.1/src/rbeesoft/app/core → rbeesoft-2.0.3/src/rbeesoft/app/ui}/processes/processrunner.py +0 -0
  21. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/src/rbeesoft/app/ui/widgets/__init__.py +0 -0
  22. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/src/rbeesoft/app/ui/widgets/centraldockwidget.py +0 -0
  23. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/src/rbeesoft/app/ui/widgets/logdockwidget.py +0 -0
  24. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/src/rbeesoft/app/ui/widgets/pages/__init__.py +0 -0
  25. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/src/rbeesoft/app/ui/widgets/pages/page.py +0 -0
  26. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/src/rbeesoft/app/ui/widgets/pages/pagerouter.py +0 -0
  27. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/src/rbeesoft/common/__init__.py +0 -0
  28. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/src/rbeesoft/common/logmanager.py +0 -0
  29. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/src/rbeesoft/common/logmanagerlistener.py +0 -0
  30. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/src/rbeesoft/common/singleton.py +0 -0
  31. {rbeesoft-2.0.1 → rbeesoft-2.0.3}/src/rbeesoft/webapp/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rbeesoft
3
- Version: 2.0.1
3
+ Version: 2.0.3
4
4
  Summary:
5
5
  Author: Brecheisen
6
6
  Author-email: r.brecheisen@maastrichtuniversity.nl
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "rbeesoft"
3
- version = "2.0.1"
3
+ version = "2.0.3"
4
4
  description = ""
5
5
  authors = [
6
6
  {name = "Brecheisen", email = "r.brecheisen@maastrichtuniversity.nl"}
@@ -0,0 +1,37 @@
1
+ import os
2
+ import base64
3
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
4
+ from cryptography.hazmat.primitives import serialization
5
+ from pathlib import Path
6
+
7
+ # RUN THIS ONLY ONCE!!! You can distribute the public key to clients
8
+
9
+
10
+ def main():
11
+ output_dir = Path.home() / 'keys'
12
+ os.makedirs(output_dir, exist_ok=True)
13
+ priv = Ed25519PrivateKey.generate()
14
+ pub = priv.public_key()
15
+ priv_bytes = priv.private_bytes(
16
+ encoding=serialization.Encoding.Raw,
17
+ format=serialization.PrivateFormat.Raw,
18
+ encryption_algorithm=serialization.NoEncryption(),
19
+ )
20
+ pub_bytes = pub.public_bytes(
21
+ encoding=serialization.Encoding.Raw,
22
+ format=serialization.PublicFormat.Raw,
23
+ )
24
+ priv_file = output_dir / 'ed25519_private.key'
25
+ priv_file.write_bytes(priv_bytes)
26
+ pub_file = output_dir / 'ed25519_public.key'
27
+ pub_file.write_bytes(pub_bytes)
28
+ print("Wrote:")
29
+ print(" - $HOME/keys/ed25519_private.key (KEEP SECRET)")
30
+ print(" - $HOME/keys/ed25519_public.key (embed in app)")
31
+ print("\nPublic key (base64) to embed:")
32
+ print(base64.b64encode(pub_bytes).decode("ascii"))
33
+
34
+
35
+ if __name__ == '__main__':
36
+ main()
37
+
@@ -0,0 +1,15 @@
1
+ {
2
+ "payload": {
3
+ "customer": "Ralph Example",
4
+ "product": "MyPySideApp Pro",
5
+ "features": [
6
+ "pro",
7
+ "export_pdf"
8
+ ],
9
+ "issued_at": 1770031472,
10
+ "exp": 1772623472,
11
+ "license_id": "LIC-1770031472"
12
+ },
13
+ "signature": "2NPw5sb9zeOF3b6Sm5hrxWcne/cX0G0vJwVJ+bG+0i7/WY6SJyCNcmN5v+2eBe7WZjs+E+c33JOA7mX8BLTmBA==",
14
+ "alg": "Ed25519"
15
+ }
@@ -2,8 +2,8 @@ import sys
2
2
  from PySide6 import QtWidgets, QtCore
3
3
  from rbeesoft.app.ui.rbeesoftmainwindow import RbeesoftMainWindow
4
4
  from rbeesoft.app.ui.widgets.pages.page import Page
5
- from rbeesoft.app.core.processes.process import Process
6
- from rbeesoft.app.core.processes.processrunner import ProcessRunner
5
+ from rbeesoft.app.ui.processes.process import Process
6
+ from rbeesoft.app.ui.processes.processrunner import ProcessRunner
7
7
 
8
8
 
9
9
  class MainWindow(RbeesoftMainWindow):
@@ -12,8 +12,8 @@ class MainWindow(RbeesoftMainWindow):
12
12
  bundle_identifier='rbeesoft.nl',
13
13
  app_name='example',
14
14
  app_title='Rbeesoft App Example',
15
- width=800,
16
- height=600,
15
+ app_width=800,
16
+ app_height=600,
17
17
  app_icon=app_icon,
18
18
  )
19
19
  self.add_page(HomePage(self.settings()), home_page=True)
@@ -0,0 +1,71 @@
1
+ import base64
2
+ import json
3
+ import time
4
+ from pathlib import Path
5
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
6
+
7
+ def canonical_json_bytes(obj: dict) -> bytes:
8
+ # Deterministic JSON -> same bytes everywhere
9
+ return json.dumps(
10
+ obj,
11
+ sort_keys=True,
12
+ separators=(",", ":"),
13
+ ensure_ascii=False
14
+ ).encode("utf-8")
15
+
16
+ def load_private_key(path: Path) -> Ed25519PrivateKey:
17
+ key_bytes = path.read_bytes()
18
+ return Ed25519PrivateKey.from_private_bytes(key_bytes)
19
+
20
+ def make_license_payload(
21
+ customer: str,
22
+ product: str,
23
+ expires_days_from_now: int,
24
+ features: list[str],
25
+ machine_fp: str | None = None,
26
+ ) -> dict:
27
+ now = int(time.time())
28
+ exp = now + expires_days_from_now * 24 * 3600
29
+ payload = {
30
+ "customer": customer,
31
+ "product": product,
32
+ "features": features,
33
+ "issued_at": now,
34
+ "exp": exp,
35
+ "license_id": f"LIC-{now}",
36
+ }
37
+ # Optional machine binding (keep None for portable)
38
+ if machine_fp:
39
+ payload["machine_fp"] = machine_fp
40
+ return payload
41
+
42
+ def sign_license(payload: dict, priv: Ed25519PrivateKey) -> dict:
43
+ msg = canonical_json_bytes(payload)
44
+ sig = priv.sign(msg)
45
+ return {
46
+ "payload": payload,
47
+ "signature": base64.b64encode(sig).decode("ascii"),
48
+ "alg": "Ed25519",
49
+ }
50
+
51
+ def main():
52
+ priv_path = Path.home() / 'keys/ed25519_private.key'
53
+ out_path = Path("license.json")
54
+
55
+ priv = load_private_key(priv_path)
56
+
57
+ payload = make_license_payload(
58
+ customer="Ralph Example",
59
+ product="MyPySideApp Pro",
60
+ expires_days_from_now=30,
61
+ features=["pro", "export_pdf"],
62
+ machine_fp=None, # set to a fingerprint to bind to a device
63
+ )
64
+
65
+ signed = sign_license(payload, priv)
66
+ out_path.write_text(json.dumps(signed, indent=2, ensure_ascii=False), encoding="utf-8")
67
+ print(f"Wrote signed license to: {out_path.resolve()}")
68
+ print(f"Expires at (unix): {payload['exp']}")
69
+
70
+ if __name__ == "__main__":
71
+ main()
@@ -1,5 +1,5 @@
1
1
  import time
2
- from rbeesoft.app.core.processes.process import Process
2
+ from rbeesoft.app.ui.processes.process import Process
3
3
 
4
4
 
5
5
  class DummyProcess(Process):
@@ -0,0 +1,146 @@
1
+ import os
2
+ from PySide6.QtCore import Qt, QByteArray
3
+ from PySide6.QtWidgets import QMainWindow, QStyle, QFileDialog
4
+ from PySide6.QtGui import QGuiApplication, QAction
5
+ from rbeesoft.app.ui.settings import Settings
6
+ from rbeesoft.app.ui.widgets.centraldockwidget import CentralDockWidget
7
+ from rbeesoft.app.ui.widgets.logdockwidget import LogDockWidget
8
+ from rbeesoft.common.licensemanager import LicenseManager
9
+ from rbeesoft.common.licenseexception import LicenseException
10
+ from rbeesoft.common.logmanager import LogManager
11
+
12
+ LOG = LogManager()
13
+
14
+ PUBLIC_KEY_B64 = 'C7yBmGtvBkvnvGtWiey4PKXZWo7Lza61+FwV2UyAu34='
15
+
16
+
17
+ class RbeesoftMainWindow(QMainWindow):
18
+ def __init__(self, bundle_identifier, app_name, app_title, app_width=1024, app_height=1024, app_icon=None):
19
+ super(RbeesoftMainWindow, self).__init__()
20
+ self._settings = Settings(bundle_identifier, app_name)
21
+ self._settings.set('public_key', PUBLIC_KEY_B64)
22
+ self._app_title = app_title
23
+ self._app_width = app_width
24
+ self._app_height = app_height
25
+ self._app_icon = app_icon
26
+ self._central_dockwidget = None
27
+ self._log_dockwidget = None
28
+ self._license_manager = None
29
+ self._license = None
30
+ self.init()
31
+
32
+ # INITIALIZATION
33
+
34
+ def init(self):
35
+ self.setWindowTitle(self.app_title())
36
+ self.addDockWidget(Qt.DockWidgetArea.TopDockWidgetArea, self.central_dockwidget())
37
+ self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self.log_dockwidget())
38
+ if self.app_icon():
39
+ self.setWindowIcon(self.app_icon())
40
+ self.load_geometry_and_state()
41
+ self.init_default_menus()
42
+ self.check_license()
43
+ self.statusBar().showMessage('Ready')
44
+ LOG.info(f'Settings path: {self.settings().fileName()}')
45
+
46
+ def init_default_menus(self):
47
+ # Application menu
48
+ icon = self.style().standardIcon(QStyle.StandardPixmap.SP_MessageBoxCritical)
49
+ exit_action = QAction(icon, 'E&xit', self)
50
+ exit_action.triggered.connect(self.close)
51
+ application_menu = self.menuBar().addMenu('Application')
52
+ application_menu.addAction(exit_action)
53
+ # Settings menu
54
+ icon = self.style().standardIcon(QStyle.StandardPixmap.SP_VistaShield)
55
+ open_license_file_action = QAction(icon, 'Open license file...', self)
56
+ open_license_file_action.triggered.connect(self.handle_open_license_file_action)
57
+ settings_menu = self.menuBar().addMenu('Settings')
58
+ settings_menu.addAction(open_license_file_action)
59
+
60
+ # GETTERS
61
+
62
+ def settings(self):
63
+ return self._settings
64
+
65
+ def app_title(self):
66
+ return self._app_title
67
+
68
+ def app_width(self):
69
+ return self._app_width
70
+
71
+ def app_height(self):
72
+ return self._app_height
73
+
74
+ def app_icon(self):
75
+ return self._app_icon
76
+
77
+ def central_dockwidget(self):
78
+ if not self._central_dockwidget:
79
+ self._central_dockwidget = CentralDockWidget(self, self.settings())
80
+ return self._central_dockwidget
81
+
82
+ def log_dockwidget(self):
83
+ if not self._log_dockwidget:
84
+ self._log_dockwidget = LogDockWidget(self)
85
+ LOG.add_listener(self._log_dockwidget)
86
+ return self._log_dockwidget
87
+
88
+ def license_manager(self):
89
+ if not self._license_manager:
90
+ self._license_manager = LicenseManager(self.settings().get('public_key', None))
91
+ return self._license_manager
92
+
93
+ def license(self):
94
+ return self._license
95
+
96
+ # EVENT HANDLERS
97
+
98
+ def closeEvent(self, event):
99
+ self.save_geometry_and_state()
100
+ return super().closeEvent(event)
101
+
102
+ def handle_open_license_file_action(self):
103
+ last_directory = self.settings().get('mainwindow/last_directory', None)
104
+ file_path, _ = QFileDialog.getOpenFileName(dir=last_directory)
105
+ if file_path:
106
+ self.settings().set('mainwindow/last_directory', os.path.split(file_path)[0])
107
+ self.settings().set('mainwindow/license_file', file_path)
108
+ self.check_license()
109
+
110
+ # HELPERS
111
+
112
+ def add_page(self, page, home_page=False):
113
+ self.central_dockwidget().add_page(page, home_page)
114
+
115
+ def check_license(self):
116
+ file_path = self.settings().get('mainwindow/license_file', None)
117
+ if file_path:
118
+ try:
119
+ self._license = self.license_manager().verify(file_path)
120
+ LOG.info('License OK')
121
+ return True
122
+ except LicenseException as e:
123
+ LOG.error(e)
124
+ return False
125
+ LOG.warning('No license found')
126
+ return False
127
+
128
+ def load_geometry_and_state(self):
129
+ geometry = self.settings().get('mainwindow/geometry')
130
+ state = self.settings().get('mainwindow/state')
131
+ if isinstance(geometry, QByteArray) and self.restoreGeometry(geometry):
132
+ if isinstance(state, QByteArray):
133
+ self.restoreState(state)
134
+ return
135
+ self.resize(self.app_width(), self.app_height())
136
+ self.center_window()
137
+
138
+ def save_geometry_and_state(self):
139
+ self.settings().set('mainwindow/geometry', self.saveGeometry())
140
+ self.settings().set('mainwindow/state', self.saveState())
141
+
142
+ def center_window(self):
143
+ screen = QGuiApplication.primaryScreen().geometry()
144
+ x = (screen.width() - self.geometry().width()) / 2
145
+ y = (screen.height() - self.geometry().height()) / 2
146
+ self.move(int(x), int(y))
@@ -12,6 +12,12 @@ class Settings(QSettings):
12
12
  self._bundle_identifier = bundle_identifier
13
13
  self._app_name = app_name
14
14
 
15
+ def bundle_identifier(self):
16
+ return self._bundle_identifier
17
+
18
+ def app_name(self):
19
+ return self._app_name
20
+
15
21
  def _prepend_bundle_identifier_and_name(self, name):
16
22
  return '{}.{}.{}'.format(self._bundle_identifier, self._app_name, name)
17
23
 
@@ -0,0 +1,2 @@
1
+ class LicenseException(Exception):
2
+ pass
@@ -0,0 +1,47 @@
1
+ import json
2
+ import time
3
+ import base64
4
+ from pathlib import Path
5
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
6
+ from rbeesoft.common.licenseexception import LicenseException
7
+
8
+
9
+ class LicenseManager:
10
+ def __init__(self, public_key):
11
+ self._file_path = None
12
+ self._public_key = public_key
13
+
14
+ def file_path(self):
15
+ return self._file_path
16
+
17
+ def public_key(self):
18
+ return self._public_key
19
+
20
+ def canonical_json_bytes(self, obj: dict) -> bytes:
21
+ return json.dumps(
22
+ obj,
23
+ sort_keys=True,
24
+ separators=(",", ":"),
25
+ ensure_ascii=False
26
+ ).encode("utf-8")
27
+
28
+ def verify(self, file_path):
29
+ if isinstance(file_path, str):
30
+ file_path = Path(file_path)
31
+ try:
32
+ signed = json.loads(file_path.read_text(encoding='utf-8'))
33
+ payload = signed['payload']
34
+ sig_b64 = signed['signature']
35
+ sig = base64.b64decode(sig_b64)
36
+ msg = self.canonical_json_bytes(payload)
37
+ pub_bytes = base64.b64decode(self.public_key())
38
+ pub = Ed25519PublicKey.from_public_bytes(pub_bytes)
39
+ pub.verify(sig, msg)
40
+ # expiry check
41
+ now = int(time.time())
42
+ exp = int(payload["exp"])
43
+ if now > exp:
44
+ raise LicenseException('License expired')
45
+ return payload
46
+ except Exception as e:
47
+ raise LicenseException(f'Invalid license: {e}')
@@ -1,77 +0,0 @@
1
- from PySide6.QtCore import Qt, QByteArray
2
- from PySide6.QtWidgets import QMainWindow, QStyle
3
- from PySide6.QtGui import QGuiApplication, QAction
4
- from rbeesoft.app.ui.settings import Settings
5
- from rbeesoft.app.ui.widgets.centraldockwidget import CentralDockWidget
6
- from rbeesoft.app.ui.widgets.logdockwidget import LogDockWidget
7
- from rbeesoft.common.logmanager import LogManager
8
-
9
- LOG = LogManager()
10
-
11
-
12
- class RbeesoftMainWindow(QMainWindow):
13
- def __init__(self, bundle_identifier, app_name, app_title, width=1024, height=1024, app_icon=None):
14
- super(RbeesoftMainWindow, self).__init__()
15
- self._settings = Settings(bundle_identifier, app_name)
16
- self._app_title = app_title
17
- self._width = width
18
- self._height = height
19
- self._app_icon = app_icon
20
- self._central_dockwidget = CentralDockWidget(self, self._settings)
21
- self._log_dockwidget = LogDockWidget(self)
22
- self.init()
23
-
24
- # INITIALIZATION
25
-
26
- def init(self):
27
- self.setWindowTitle(self._app_title)
28
- self.addDockWidget(Qt.DockWidgetArea.TopDockWidgetArea, self._central_dockwidget)
29
- self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, self._log_dockwidget)
30
- if self._app_icon:
31
- self.setWindowIcon(self._app_icon)
32
- self.load_geometry_and_state()
33
- self.init_default_menus()
34
- self.statusBar().showMessage('Ready')
35
- LOG.info(f'Settings path: {self.settings().fileName()}')
36
-
37
- def init_default_menus(self):
38
- exit_action_icon = self.style().standardIcon(QStyle.StandardPixmap.SP_DialogCloseButton)
39
- exit_action = QAction(exit_action_icon, 'E&xit', self)
40
- exit_action.triggered.connect(self.close)
41
- application_menu = self.menuBar().addMenu('Application')
42
- application_menu.addAction(exit_action)
43
-
44
- # GETTERS
45
-
46
- def settings(self):
47
- return self._settings
48
-
49
- # EVENT HANDLERS
50
-
51
- def closeEvent(self, _):
52
- self.save_geometry_and_state()
53
-
54
- # HELPERS
55
-
56
- def add_page(self, page, home_page=False):
57
- self._central_dockwidget.add_page(page, home_page)
58
-
59
- def load_geometry_and_state(self):
60
- geometry = self.settings().get('mainwindow/geometry')
61
- state = self.settings().get('mainwindow/state')
62
- if isinstance(geometry, QByteArray) and self.restoreGeometry(geometry):
63
- if isinstance(state, QByteArray):
64
- self.restoreState(state)
65
- return
66
- self.resize(self._width, self._height)
67
- self.center_window()
68
-
69
- def save_geometry_and_state(self):
70
- self.settings().set('mainwindow/geometry', self.saveGeometry())
71
- self.settings().set('mainwindow/state', self.saveState())
72
-
73
- def center_window(self):
74
- screen = QGuiApplication.primaryScreen().geometry()
75
- x = (screen.width() - self.geometry().width()) / 2
76
- y = (screen.height() - self.geometry().height()) / 2
77
- self.move(int(x), int(y))
File without changes