rbeesoft 2.0.2__py3-none-any.whl → 2.0.4__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
@@ -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()
@@ -1,60 +1,130 @@
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
+ LOG.info(f'Settings path: {self.settings().fileName()}')
39
+ if self.app_icon():
40
+ self.setWindowIcon(self.app_icon())
32
41
  self.load_geometry_and_state()
33
42
  self.init_default_menus()
43
+ self.check_license()
34
44
  self.statusBar().showMessage('Ready')
35
- 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(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
58
128
 
59
129
  def load_geometry_and_state(self):
60
130
  geometry = self.settings().get('mainwindow/geometry')
@@ -63,7 +133,7 @@ class RbeesoftMainWindow(QMainWindow):
63
133
  if isinstance(state, QByteArray):
64
134
  self.restoreState(state)
65
135
  return
66
- self.resize(self._width, self._height)
136
+ self.resize(self.app_width(), self.app_height())
67
137
  self.center_window()
68
138
 
69
139
  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
 
@@ -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,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,25 +1,30 @@
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/main.py,sha256=3abWYZKn1DIeLH2YkEWsIZVayaGmdvkJziagAyv6Ko8,2878
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=Zp5bZOnmtHsFOACgU3iGRwy0Ko0nb4GZ97lB4BKTWMw,2704
5
8
  rbeesoft/app/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
9
  rbeesoft/app/ui/processes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
10
  rbeesoft/app/ui/processes/dummyprocess.py,sha256=4r97xwHv7ZaoK85zBxR00zzYnYpoFAxZfBPmxFJYDkM,470
8
11
  rbeesoft/app/ui/processes/process.py,sha256=qMrXKvT06oRe1yRSe3DLO9ObhV2qzzZLI5R-PwryOYs,1694
9
12
  rbeesoft/app/ui/processes/processrunner.py,sha256=HqDQ3ZMlqCqQkyuEgV5sJCkzD35ENvp58z9D-TtnVdA,1170
10
- rbeesoft/app/ui/rbeesoftmainwindow.py,sha256=o40iwX8KO7QuZNHB12m8yMFtw6D21T02g3qNO09_3R4,3023
11
- rbeesoft/app/ui/settings.py,sha256=9BV-nvq87xjp5JSyd0hvpj0E5duckFuWk0Mj3Fx0lnE,2062
13
+ rbeesoft/app/ui/rbeesoftmainwindow.py,sha256=p-tO7YG39DEQ_15ijLGKSG1MFWnPBWR8CGR7nl971jA,5713
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
15
18
  rbeesoft/app/ui/widgets/pages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- rbeesoft/app/ui/widgets/pages/page.py,sha256=_YI8y-VP3MLz4C6xChIJhrDwZ2QbcQBY2g7zvAI8r4M,554
19
+ rbeesoft/app/ui/widgets/pages/page.py,sha256=huzUrbvyx8qZmeelKRhLimB9RFfuj9bK_EsG_56AK_E,661
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.2.dist-info/METADATA,sha256=qursQ5Lw_jMYvL1Ej_cUTKPNYioYLcziPsyyPnV0DJc,378
24
- rbeesoft-2.0.2.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
25
- rbeesoft-2.0.2.dist-info/RECORD,,
28
+ rbeesoft-2.0.4.dist-info/METADATA,sha256=5JpIKhEMZamX4jm6keezUXIph6r5mfHTzOAp8jt7RDw,378
29
+ rbeesoft-2.0.4.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
30
+ rbeesoft-2.0.4.dist-info/RECORD,,