rbeesoft 2.0.1__py3-none-any.whl → 2.0.3__py3-none-any.whl

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.
@@ -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
+ }
rbeesoft/app/main.py CHANGED
@@ -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):
@@ -1,60 +1,129 @@
1
+ import os
1
2
  from PySide6.QtCore import Qt, QByteArray
2
- from PySide6.QtWidgets import QMainWindow, QStyle
3
+ from PySide6.QtWidgets import QMainWindow, QStyle, QFileDialog
3
4
  from PySide6.QtGui import QGuiApplication, QAction
4
5
  from rbeesoft.app.ui.settings import Settings
5
6
  from rbeesoft.app.ui.widgets.centraldockwidget import CentralDockWidget
6
7
  from rbeesoft.app.ui.widgets.logdockwidget import LogDockWidget
8
+ from rbeesoft.common.licensemanager import LicenseManager
9
+ from rbeesoft.common.licenseexception import LicenseException
7
10
  from rbeesoft.common.logmanager import LogManager
8
11
 
9
12
  LOG = LogManager()
10
13
 
14
+ PUBLIC_KEY_B64 = 'C7yBmGtvBkvnvGtWiey4PKXZWo7Lza61+FwV2UyAu34='
15
+
11
16
 
12
17
  class RbeesoftMainWindow(QMainWindow):
13
- def __init__(self, bundle_identifier, app_name, app_title, width=1024, height=1024, app_icon=None):
18
+ def __init__(self, bundle_identifier, app_name, app_title, app_width=1024, app_height=1024, app_icon=None):
14
19
  super(RbeesoftMainWindow, self).__init__()
15
20
  self._settings = Settings(bundle_identifier, app_name)
21
+ self._settings.set('public_key', PUBLIC_KEY_B64)
16
22
  self._app_title = app_title
17
- self._width = width
18
- self._height = height
23
+ self._app_width = app_width
24
+ self._app_height = app_height
19
25
  self._app_icon = app_icon
20
- self._central_dockwidget = CentralDockWidget(self, self._settings)
21
- self._log_dockwidget = LogDockWidget(self)
26
+ self._central_dockwidget = None
27
+ self._log_dockwidget = None
28
+ self._license_manager = None
29
+ self._license = None
22
30
  self.init()
23
31
 
24
32
  # INITIALIZATION
25
33
 
26
34
  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)
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())
32
40
  self.load_geometry_and_state()
33
41
  self.init_default_menus()
42
+ self.check_license()
34
43
  self.statusBar().showMessage('Ready')
35
44
  LOG.info(f'Settings path: {self.settings().fileName()}')
36
45
 
37
46
  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)
47
+ # Application menu
48
+ icon = self.style().standardIcon(QStyle.StandardPixmap.SP_MessageBoxCritical)
49
+ exit_action = QAction(icon, 'E&xit', self)
40
50
  exit_action.triggered.connect(self.close)
41
51
  application_menu = self.menuBar().addMenu('Application')
42
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)
43
59
 
44
60
  # GETTERS
45
61
 
46
62
  def settings(self):
47
63
  return self._settings
48
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
+
49
96
  # EVENT HANDLERS
50
97
 
51
- def closeEvent(self, _):
98
+ def closeEvent(self, event):
52
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()
53
109
 
54
110
  # HELPERS
55
111
 
56
112
  def add_page(self, page, home_page=False):
57
- self._central_dockwidget.add_page(page, home_page)
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
58
127
 
59
128
  def load_geometry_and_state(self):
60
129
  geometry = self.settings().get('mainwindow/geometry')
@@ -63,7 +132,7 @@ class RbeesoftMainWindow(QMainWindow):
63
132
  if isinstance(state, QByteArray):
64
133
  self.restoreState(state)
65
134
  return
66
- self.resize(self._width, self._height)
135
+ self.resize(self.app_width(), self.app_height())
67
136
  self.center_window()
68
137
 
69
138
  def save_geometry_and_state(self):
@@ -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,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,14 +1,17 @@
1
1
  rbeesoft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  rbeesoft/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  rbeesoft/app/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- rbeesoft/app/core/processes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- rbeesoft/app/core/processes/dummyprocess.py,sha256=mnFrgDWDfG_acfdaKDbtXZgadnY6UmHf1PVoN04Rrl0,472
6
- rbeesoft/app/core/processes/process.py,sha256=qMrXKvT06oRe1yRSe3DLO9ObhV2qzzZLI5R-PwryOYs,1694
7
- rbeesoft/app/core/processes/processrunner.py,sha256=HqDQ3ZMlqCqQkyuEgV5sJCkzD35ENvp58z9D-TtnVdA,1170
8
- rbeesoft/app/main.py,sha256=mZmeDzFurIIRAHRPI5ktPgmTHOcJPS1oyYULwTxlhho,2882
4
+ rbeesoft/app/generatekeys.py,sha256=YVxIhKhyo0JY6HFxCTNZdD5bU_K6Vb1zkRKdsYQ80Ew,1243
5
+ rbeesoft/app/license.json,sha256=m8ukgKJdiJ3fU4nsFIrE2fm6Qk_SdIdwoqkapM2MnlI,374
6
+ rbeesoft/app/main.py,sha256=AsDayu8MZoLnd9ODZmHL-AA-dqS8exodKv4LcHKw44o,2886
7
+ rbeesoft/app/signlicense.py,sha256=9dQG4TNUdTmXTNhzQLghLn59fE_yx1BKYEwvKVAUj_w,2122
9
8
  rbeesoft/app/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- rbeesoft/app/ui/rbeesoftmainwindow.py,sha256=o40iwX8KO7QuZNHB12m8yMFtw6D21T02g3qNO09_3R4,3023
11
- rbeesoft/app/ui/settings.py,sha256=9BV-nvq87xjp5JSyd0hvpj0E5duckFuWk0Mj3Fx0lnE,2062
9
+ rbeesoft/app/ui/processes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ rbeesoft/app/ui/processes/dummyprocess.py,sha256=4r97xwHv7ZaoK85zBxR00zzYnYpoFAxZfBPmxFJYDkM,470
11
+ rbeesoft/app/ui/processes/process.py,sha256=qMrXKvT06oRe1yRSe3DLO9ObhV2qzzZLI5R-PwryOYs,1694
12
+ rbeesoft/app/ui/processes/processrunner.py,sha256=HqDQ3ZMlqCqQkyuEgV5sJCkzD35ENvp58z9D-TtnVdA,1170
13
+ rbeesoft/app/ui/rbeesoftmainwindow.py,sha256=F-oUd4gOJ6GepTP4Ysu1bN8NqvTJ3HmVqIuwcrH9mvc,5654
14
+ rbeesoft/app/ui/settings.py,sha256=JeUBdQgah9s2XbSqsgEEYzFN2yvUwFMk8L7KFsa55WM,2200
12
15
  rbeesoft/app/ui/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
16
  rbeesoft/app/ui/widgets/centraldockwidget.py,sha256=kK0x-Sz4yz7D4icaIepWpAtct7ZQ9BNroi9EccAvABA,1376
14
17
  rbeesoft/app/ui/widgets/logdockwidget.py,sha256=DZkQQitarE5B82qvNCSnUWDWXxhWsuTauboULFoh7eI,1810
@@ -16,10 +19,12 @@ rbeesoft/app/ui/widgets/pages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
16
19
  rbeesoft/app/ui/widgets/pages/page.py,sha256=_YI8y-VP3MLz4C6xChIJhrDwZ2QbcQBY2g7zvAI8r4M,554
17
20
  rbeesoft/app/ui/widgets/pages/pagerouter.py,sha256=L5EKEMWVibSI7IpabQBkLsCDw1xBt1wfYY50iR56VZA,1460
18
21
  rbeesoft/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ rbeesoft/common/licenseexception.py,sha256=oapxaf0e9Fbl3iAa5e3c2EYxNhD0e1im1mwJfimsZ3Q,44
23
+ rbeesoft/common/licensemanager.py,sha256=UxsEvWbzXHTAI5sqI-MsZZP-aG1GK14YBY_ky2M5JyE,1554
19
24
  rbeesoft/common/logmanager.py,sha256=K_qCbPjrzoV5le3KSvLOSSXYa-_MWLJ3FdFNTEGYSxg,1391
20
25
  rbeesoft/common/logmanagerlistener.py,sha256=Gaig07yjBnyQq9I8sN85olTEeDCDyCFQnEJdwzvmgvc,99
21
26
  rbeesoft/common/singleton.py,sha256=FV0k_LlOCmFhlWN6gf1c2x7YXWyd8-7DsIMvOKrI6NY,224
22
27
  rbeesoft/webapp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- rbeesoft-2.0.1.dist-info/METADATA,sha256=nx2mWDX7KhY-khtjGdPtGdyzp1I9_V6yZjhRZ4N-tTQ,378
24
- rbeesoft-2.0.1.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
25
- rbeesoft-2.0.1.dist-info/RECORD,,
28
+ rbeesoft-2.0.3.dist-info/METADATA,sha256=_60oXjgLbPCAIMrQOamK4sL-OHQj_7hQrxQ9a0t8spc,378
29
+ rbeesoft-2.0.3.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
30
+ rbeesoft-2.0.3.dist-info/RECORD,,
File without changes
File without changes
File without changes