rbeesoft 2.0.2__tar.gz → 2.0.4__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.2 → rbeesoft-2.0.4}/PKG-INFO +1 -1
  2. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/pyproject.toml +1 -1
  3. rbeesoft-2.0.4/src/rbeesoft/app/generatekeys.py +37 -0
  4. rbeesoft-2.0.4/src/rbeesoft/app/license.json +15 -0
  5. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/app/main.py +2 -2
  6. rbeesoft-2.0.4/src/rbeesoft/app/signlicense.py +76 -0
  7. rbeesoft-2.0.4/src/rbeesoft/app/ui/rbeesoftmainwindow.py +147 -0
  8. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/app/ui/settings.py +6 -0
  9. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/app/ui/widgets/pages/page.py +5 -1
  10. rbeesoft-2.0.4/src/rbeesoft/common/licenseexception.py +2 -0
  11. rbeesoft-2.0.4/src/rbeesoft/common/licensemanager.py +47 -0
  12. rbeesoft-2.0.2/src/rbeesoft/app/ui/rbeesoftmainwindow.py +0 -77
  13. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/README.md +0 -0
  14. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/__init__.py +0 -0
  15. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/app/__init__.py +0 -0
  16. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/app/core/__init__.py +0 -0
  17. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/app/ui/__init__.py +0 -0
  18. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/app/ui/processes/__init__.py +0 -0
  19. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/app/ui/processes/dummyprocess.py +0 -0
  20. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/app/ui/processes/process.py +0 -0
  21. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/app/ui/processes/processrunner.py +0 -0
  22. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/app/ui/widgets/__init__.py +0 -0
  23. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/app/ui/widgets/centraldockwidget.py +0 -0
  24. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/app/ui/widgets/logdockwidget.py +0 -0
  25. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/app/ui/widgets/pages/__init__.py +0 -0
  26. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/app/ui/widgets/pages/pagerouter.py +0 -0
  27. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/common/__init__.py +0 -0
  28. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/common/logmanager.py +0 -0
  29. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/common/logmanagerlistener.py +0 -0
  30. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/src/rbeesoft/common/singleton.py +0 -0
  31. {rbeesoft-2.0.2 → rbeesoft-2.0.4}/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.2
3
+ Version: 2.0.4
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.2"
3
+ version = "2.0.4"
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
+ }
@@ -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,76 @@
1
+ import base64
2
+ import argparse
3
+ import json
4
+ import time
5
+ from pathlib import Path
6
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
7
+
8
+ def canonical_json_bytes(obj: dict) -> bytes:
9
+ # Deterministic JSON -> same bytes everywhere
10
+ return json.dumps(
11
+ obj,
12
+ sort_keys=True,
13
+ separators=(",", ":"),
14
+ ensure_ascii=False
15
+ ).encode("utf-8")
16
+
17
+ def load_private_key(path: Path) -> Ed25519PrivateKey:
18
+ key_bytes = path.read_bytes()
19
+ return Ed25519PrivateKey.from_private_bytes(key_bytes)
20
+
21
+ def make_license_payload(
22
+ customer: str,
23
+ product: str,
24
+ expires_days_from_now: int,
25
+ features: list[str],
26
+ machine_fp: str | None = None,
27
+ ) -> dict:
28
+ now = int(time.time())
29
+ exp = now + expires_days_from_now * 24 * 3600
30
+ payload = {
31
+ "customer": customer,
32
+ "product": product,
33
+ "features": features,
34
+ "issued_at": now,
35
+ "exp": exp,
36
+ "license_id": f"LIC-{now}",
37
+ }
38
+ # Optional machine binding (keep None for portable)
39
+ if machine_fp:
40
+ payload["machine_fp"] = machine_fp
41
+ return payload
42
+
43
+ def sign_license(payload: dict, priv: Ed25519PrivateKey) -> dict:
44
+ msg = canonical_json_bytes(payload)
45
+ sig = priv.sign(msg)
46
+ return {
47
+ "payload": payload,
48
+ "signature": base64.b64encode(sig).decode("ascii"),
49
+ "alg": "Ed25519",
50
+ }
51
+
52
+ def main():
53
+ parser = argparse.ArgumentParser()
54
+ parser.add_argument('private_key_path', help='Path to private key file')
55
+ parser.add_argument('output_dir', help='Path to output directory where license.json is written')
56
+ parser.add_argument('product', help='Name of product')
57
+ parser.add_argument('expires_days_from_now', help='Number of days to expire', type=int)
58
+ parser.add_argument('--features', help='Feature list', default='')
59
+ args = parser.parse_args()
60
+ priv_path = Path(args.private_key_path) # Path.home() / 'keys/ed25519_private.key'
61
+ out_path = Path(args.output_dir) / 'license.json'
62
+ priv = load_private_key(priv_path)
63
+ payload = make_license_payload(
64
+ customer='Default customer',
65
+ product=args.product,
66
+ expires_days_from_now=args.expires_days_from_now,
67
+ features=[x.strip() for x in args.features.split(',')],
68
+ machine_fp=None, # set to a fingerprint to bind to a device
69
+ )
70
+ signed = sign_license(payload, priv)
71
+ out_path.write_text(json.dumps(signed, indent=2, ensure_ascii=False), encoding="utf-8")
72
+ print(f"Wrote signed license to: {out_path.resolve()}")
73
+ print(f"Expires at (unix): {payload['exp']}")
74
+
75
+ if __name__ == "__main__":
76
+ main()
@@ -0,0 +1,147 @@
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
+ LOG.info(f'Settings path: {self.settings().fileName()}')
39
+ if self.app_icon():
40
+ self.setWindowIcon(self.app_icon())
41
+ self.load_geometry_and_state()
42
+ self.init_default_menus()
43
+ self.check_license()
44
+ self.statusBar().showMessage('Ready')
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(f'License found at {file_path}')
121
+ LOG.info('License OK')
122
+ return True
123
+ except LicenseException as e:
124
+ LOG.error(e)
125
+ return False
126
+ LOG.warning('No license found')
127
+ return False
128
+
129
+ def load_geometry_and_state(self):
130
+ geometry = self.settings().get('mainwindow/geometry')
131
+ state = self.settings().get('mainwindow/state')
132
+ if isinstance(geometry, QByteArray) and self.restoreGeometry(geometry):
133
+ if isinstance(state, QByteArray):
134
+ self.restoreState(state)
135
+ return
136
+ self.resize(self.app_width(), self.app_height())
137
+ self.center_window()
138
+
139
+ def save_geometry_and_state(self):
140
+ self.settings().set('mainwindow/geometry', self.saveGeometry())
141
+ self.settings().set('mainwindow/state', self.saveState())
142
+
143
+ def center_window(self):
144
+ screen = QGuiApplication.primaryScreen().geometry()
145
+ x = (screen.width() - self.geometry().width()) / 2
146
+ y = (screen.height() - self.geometry().height()) / 2
147
+ 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
 
@@ -5,11 +5,12 @@ from PySide6.QtWidgets import QWidget
5
5
  class Page(QWidget):
6
6
  page_changed = Signal(str)
7
7
 
8
- def __init__(self, name, title, settings):
8
+ def __init__(self, name, title, settings, license=None):
9
9
  super(Page, self).__init__()
10
10
  self._name = name
11
11
  self._title = title
12
12
  self._settings = settings
13
+ self._license = license
13
14
 
14
15
  def name(self):
15
16
  return self._name
@@ -20,5 +21,8 @@ class Page(QWidget):
20
21
  def settings(self):
21
22
  return self._settings
22
23
 
24
+ def license(self):
25
+ return self._license
26
+
23
27
  def switch_to_page(self, name):
24
28
  self.page_changed.emit(name)
@@ -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