scientiveshield-tools 0.1.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.
@@ -0,0 +1,88 @@
1
+ Business Source License 1.1
2
+
3
+ License text copyright © 2017 MariaDB Corporation Ab, All Rights Reserved.
4
+ "Business Source License" is a trademark of MariaDB Corporation Ab.
5
+
6
+ Terms
7
+
8
+ The Licensor hereby grants you the right to copy, modify, create derivative
9
+ works, redistribute, and make non-production use of the Licensed Work. The
10
+ Licensor may make an Additional Use Grant, above, permitting limited
11
+ production use.
12
+
13
+ Effective on the Change Date, or the fourth anniversary of the first publicly
14
+ available distribution of a specific version of the Licensed Work under this
15
+ License, whichever comes first, the Licensor hereby grants you rights under
16
+ the terms of the Change License, and the rights granted in the paragraph
17
+ above terminate.
18
+
19
+ If your use of the Licensed Work does not comply with the requirements
20
+ currently in effect as described in this License, you must purchase a
21
+ commercial license from the Licensor, its affiliated entities, or authorized
22
+ resellers, or you must refrain from using the Licensed Work.
23
+
24
+ All copies of the original and modified Licensed Work, and derivative works
25
+ of the Licensed Work, are subject to this License. This License applies
26
+ separately for each version of the Licensed Work and the Change Date may vary
27
+ for each version of the Licensed Work released by Licensor.
28
+
29
+ You must conspicuously display this License on each original or modified copy
30
+ of the Licensed Work. If you receive the Licensed Work in original or
31
+ modified form from a third party, the terms and conditions set forth in this
32
+ License apply to your use of that work.
33
+
34
+ Any use of the Licensed Work in violation of this License will automatically
35
+ terminate your rights under this License for the current and all other
36
+ versions of the Licensed Work.
37
+
38
+ This License does not grant you any right in any trademark or logo of
39
+ Licensor or its affiliates (provided that you may use a trademark or logo of
40
+ Licensor as expressly required by this License).
41
+
42
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
43
+ AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
44
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
45
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
46
+ TITLE.
47
+
48
+ MariaDB hereby grants you permission to use this License’s text to license
49
+ your works, and to refer to it using the trademark “Business Source License”,
50
+ as long as you comply with the Covenants of Licensor below.
51
+
52
+ Covenants of Licensor
53
+
54
+ In consideration of the right to use this License’s text and the “Business
55
+ Source License” name and trademark, Licensor covenants to MariaDB, and to all
56
+ other recipients of the licensed work to be provided by Licensor:
57
+
58
+ 1. To specify as the Change License the GPL Version 2.0 or any later version,
59
+ or a license that is compatible with GPL Version 2.0 or a later version,
60
+ where “compatible” means that software provided under the Change License can
61
+ be included in a program with software provided under GPL Version 2.0 or a
62
+ later version. Licensor may specify additional Change Licenses without
63
+ limitation.
64
+
65
+ 2. To either: (a) specify an additional grant of rights to use that does not
66
+ impose any additional restriction on the right granted in this License, as
67
+ the Additional Use Grant; or (b) insert the text “None”.
68
+
69
+ 3. To specify a Change Date.
70
+
71
+ 4. Not to modify this License in any other way.
72
+
73
+ ---
74
+
75
+ License parameters
76
+
77
+ Licensor: ScientiveShield
78
+ Licensed Work: scientiveshield-tools, version 0.1.0 and later versions
79
+ Additional Use Grant: You may use, reproduce, and distribute the Licensed Work,
80
+ including in production, solely to export data from Devolens/Cryptolens for
81
+ import into ScientiveShield. This includes use by the licensed customer's
82
+ employees, contractors, agencies, and other authorized service providers
83
+ acting on the customer's behalf, and internal evaluation, testing, and CI use.
84
+ You may not offer the Licensed Work, or a derivative work, as a standalone
85
+ commercial product, managed service, or as part of a competing software
86
+ licensing platform.
87
+ Change Date: 2030-10-21
88
+ Change License: Apache License, Version 2.0
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: scientiveshield-tools
3
+ Version: 0.1.0
4
+ Summary: Export Devolens (Cryptolens) licensing data into a ScientiveShield import bundle.
5
+ Author: ScientiveShield
6
+ License-Expression: BUSL-1.1
7
+ Project-URL: Homepage, https://scientiveshield.pro/migrate/developer/
8
+ Project-URL: Documentation, https://scientiveshield.pro/migrate/developer/
9
+ Project-URL: Repository, https://github.com/franciscomvargas/scientiveshield-tools
10
+ Project-URL: Issues, https://github.com/franciscomvargas/scientiveshield-tools/issues
11
+ Keywords: licensing,cryptolens,devolens,migration,scientiveshield
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: requests>=2.31
25
+ Dynamic: license-file
26
+
27
+ # scientiveshield-tools
28
+
29
+ Export Devolens (formerly Cryptolens) products, customers, templates, licenses, activations and
30
+ data objects into one ScientiveShield import bundle.
31
+
32
+ This package is the official ScientiveShield exporter. It is **not** a Cryptolens SDK.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ python -m pip install --upgrade scientiveshield-tools
38
+ ```
39
+
40
+ Python 3.10+ is required.
41
+
42
+ ## Create an import file
43
+
44
+ Use a read-only Devolens/Cryptolens token with these permissions:
45
+
46
+ - Get Products
47
+ - Get Keys
48
+ - Get Customers
49
+ - License Template
50
+ - List Data Objects
51
+
52
+ ```python
53
+ from scientiveshield_tools import create_import_file
54
+
55
+ result = create_import_file(
56
+ token="read-only-cryptolens-token",
57
+ product_ids=[33453, 33454],
58
+ output="bundle.json",
59
+ )
60
+
61
+ print(result.summary)
62
+ ```
63
+
64
+ The exporter writes schema version 2 and preserves key strings. It includes key-scoped and
65
+ machine-scoped data objects. Product selection is explicit; it never exports every product by
66
+ accident.
67
+
68
+ ### Inspect products
69
+
70
+ ```python
71
+ from scientiveshield_tools import list_products
72
+
73
+ for product in list_products(token="..."):
74
+ print(product.id, product.name)
75
+ ```
76
+
77
+ ### Validate the file
78
+
79
+ ```python
80
+ from scientiveshield_tools import validate_import_file
81
+
82
+ result = validate_import_file("bundle.json")
83
+ print(result.ok, result.errors, result.warnings)
84
+ ```
85
+
86
+ ### Free environment
87
+
88
+ Cryptolens’ limited free environment allows one API request every 10 seconds. Use:
89
+
90
+ ```python
91
+ create_import_file(..., free_tier=True)
92
+ ```
93
+
94
+ The client then spaces requests proactively. On a 429 response it honors `Retry-After`, otherwise
95
+ waits 10 seconds. A free-tier export can take a few minutes; that is expected.
96
+
97
+ ## CLI (optional)
98
+
99
+ ```bash
100
+ python -m scientiveshield_tools list-products --token "$CRYPTOLENS_TOKEN"
101
+ python -m scientiveshield_tools export --interactive --out bundle.json
102
+ python -m scientiveshield_tools validate --bundle bundle.json
103
+ ```
104
+
105
+ ## Security
106
+
107
+ - Use a **read-only** token. Never use the Cryptolens account password.
108
+ - The token is never logged or included in the bundle.
109
+ - The bundle contains customer secrets and license data. Share it only via a secure channel.
110
+ - Revoke the source token after the export.
111
+ - Do not commit `bundle.json` or tokens.
112
+
113
+ ## Import and cutover
114
+
115
+ Upload the bundle in ScientiveShield → **Migration**. The dashboard validates it, shows a dry-run
116
+ preview, imports through a background worker, and provides the app cutover kit.
117
+
118
+ Developer guide: <https://scientiveshield.pro/migrate/developer/>
119
+
120
+ ## License
121
+
122
+ Business Source License 1.1 with an Additional Use Grant. See [LICENSE](LICENSE). Commercial use is
123
+ permitted for migrating your own or your clients’ software into ScientiveShield. The Licensed Work
124
+ may not be offered as a standalone commercial product, managed service, or competing licensing
125
+ platform. The Change Date is 2030-10-21; the Change License is Apache-2.0.
@@ -0,0 +1,99 @@
1
+ # scientiveshield-tools
2
+
3
+ Export Devolens (formerly Cryptolens) products, customers, templates, licenses, activations and
4
+ data objects into one ScientiveShield import bundle.
5
+
6
+ This package is the official ScientiveShield exporter. It is **not** a Cryptolens SDK.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ python -m pip install --upgrade scientiveshield-tools
12
+ ```
13
+
14
+ Python 3.10+ is required.
15
+
16
+ ## Create an import file
17
+
18
+ Use a read-only Devolens/Cryptolens token with these permissions:
19
+
20
+ - Get Products
21
+ - Get Keys
22
+ - Get Customers
23
+ - License Template
24
+ - List Data Objects
25
+
26
+ ```python
27
+ from scientiveshield_tools import create_import_file
28
+
29
+ result = create_import_file(
30
+ token="read-only-cryptolens-token",
31
+ product_ids=[33453, 33454],
32
+ output="bundle.json",
33
+ )
34
+
35
+ print(result.summary)
36
+ ```
37
+
38
+ The exporter writes schema version 2 and preserves key strings. It includes key-scoped and
39
+ machine-scoped data objects. Product selection is explicit; it never exports every product by
40
+ accident.
41
+
42
+ ### Inspect products
43
+
44
+ ```python
45
+ from scientiveshield_tools import list_products
46
+
47
+ for product in list_products(token="..."):
48
+ print(product.id, product.name)
49
+ ```
50
+
51
+ ### Validate the file
52
+
53
+ ```python
54
+ from scientiveshield_tools import validate_import_file
55
+
56
+ result = validate_import_file("bundle.json")
57
+ print(result.ok, result.errors, result.warnings)
58
+ ```
59
+
60
+ ### Free environment
61
+
62
+ Cryptolens’ limited free environment allows one API request every 10 seconds. Use:
63
+
64
+ ```python
65
+ create_import_file(..., free_tier=True)
66
+ ```
67
+
68
+ The client then spaces requests proactively. On a 429 response it honors `Retry-After`, otherwise
69
+ waits 10 seconds. A free-tier export can take a few minutes; that is expected.
70
+
71
+ ## CLI (optional)
72
+
73
+ ```bash
74
+ python -m scientiveshield_tools list-products --token "$CRYPTOLENS_TOKEN"
75
+ python -m scientiveshield_tools export --interactive --out bundle.json
76
+ python -m scientiveshield_tools validate --bundle bundle.json
77
+ ```
78
+
79
+ ## Security
80
+
81
+ - Use a **read-only** token. Never use the Cryptolens account password.
82
+ - The token is never logged or included in the bundle.
83
+ - The bundle contains customer secrets and license data. Share it only via a secure channel.
84
+ - Revoke the source token after the export.
85
+ - Do not commit `bundle.json` or tokens.
86
+
87
+ ## Import and cutover
88
+
89
+ Upload the bundle in ScientiveShield → **Migration**. The dashboard validates it, shows a dry-run
90
+ preview, imports through a background worker, and provides the app cutover kit.
91
+
92
+ Developer guide: <https://scientiveshield.pro/migrate/developer/>
93
+
94
+ ## License
95
+
96
+ Business Source License 1.1 with an Additional Use Grant. See [LICENSE](LICENSE). Commercial use is
97
+ permitted for migrating your own or your clients’ software into ScientiveShield. The Licensed Work
98
+ may not be offered as a standalone commercial product, managed service, or competing licensing
99
+ platform. The Change Date is 2030-10-21; the Change License is Apache-2.0.
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "scientiveshield-tools"
7
+ version = "0.1.0"
8
+ description = "Export Devolens (Cryptolens) licensing data into a ScientiveShield import bundle."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "BUSL-1.1"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "ScientiveShield" }]
14
+ keywords = ["licensing", "cryptolens", "devolens", "migration", "scientiveshield"]
15
+ dependencies = ["requests>=2.31"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://scientiveshield.pro/migrate/developer/"
30
+ Documentation = "https://scientiveshield.pro/migrate/developer/"
31
+ Repository = "https://github.com/franciscomvargas/scientiveshield-tools"
32
+ Issues = "https://github.com/franciscomvargas/scientiveshield-tools/issues"
33
+
34
+ [project.scripts]
35
+ scientiveshield-tools = "scientiveshield_tools.cli:main"
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
39
+
40
+ [tool.setuptools.package-data]
41
+ scientiveshield_tools = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,35 @@
1
+ """Official ScientiveShield exporter for Devolens (Cryptolens) licensing data."""
2
+
3
+ from .bundle import SCHEMA_VERSION, validate_bundle_data, validate_import_file
4
+ from .cryptolens import (
5
+ DEFAULT_BASE_URL,
6
+ CryptolensClient,
7
+ create_import_file,
8
+ list_products,
9
+ )
10
+ from .errors import (
11
+ ConfigurationError,
12
+ CryptolensError,
13
+ ScientiveShieldToolsError,
14
+ ValidationError,
15
+ )
16
+ from .models import ExportResult, Product, ValidationResult
17
+
18
+ __all__ = [
19
+ "ConfigurationError",
20
+ "CryptolensClient",
21
+ "CryptolensError",
22
+ "DEFAULT_BASE_URL",
23
+ "ExportResult",
24
+ "Product",
25
+ "SCHEMA_VERSION",
26
+ "ScientiveShieldToolsError",
27
+ "ValidationError",
28
+ "ValidationResult",
29
+ "create_import_file",
30
+ "list_products",
31
+ "validate_bundle_data",
32
+ "validate_import_file",
33
+ ]
34
+
35
+ __version__ = "0.1.0"
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,159 @@
1
+ """Bundle schema helpers and offline validation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from .errors import ValidationError
11
+ from .models import ValidationResult
12
+
13
+ SCHEMA_VERSION = 2
14
+
15
+
16
+ def normalize_customer_id(value: Any) -> int | None:
17
+ """Cryptolens uses -1 for "no customer"; normalize it to None."""
18
+ if value is None:
19
+ return None
20
+ try:
21
+ number = int(value)
22
+ except (TypeError, ValueError):
23
+ return None
24
+ return number if number > 0 else None
25
+
26
+
27
+ def normalize_machine_limit(value: Any) -> int:
28
+ try:
29
+ number = int(value or 0)
30
+ except (TypeError, ValueError):
31
+ number = 0
32
+ return max(0, number)
33
+
34
+
35
+ def load_bundle(path: str | Path) -> dict[str, Any]:
36
+ try:
37
+ with open(path, encoding="utf-8") as handle:
38
+ data = json.load(handle)
39
+ except (OSError, json.JSONDecodeError) as exc:
40
+ raise ValidationError(f"Could not read bundle: {exc}") from exc
41
+ if not isinstance(data, dict):
42
+ raise ValidationError("The bundle root must be a JSON object.")
43
+ return data
44
+
45
+
46
+ def validate_bundle_data(bundle: dict[str, Any]) -> ValidationResult:
47
+ errors: list[str] = []
48
+ warnings: list[str] = []
49
+
50
+ schema = int(bundle.get("schemaVersion") or 1)
51
+ if schema > SCHEMA_VERSION:
52
+ errors.append(f"Unsupported schema version {schema}; this tool supports up to {SCHEMA_VERSION}.")
53
+
54
+ products = bundle.get("products") or []
55
+ customers = bundle.get("customers") or []
56
+ templates = bundle.get("templates") or []
57
+ keys = bundle.get("keys") or []
58
+ if not isinstance(products, list):
59
+ errors.append("products must be a list.")
60
+ products = []
61
+ if not isinstance(customers, list):
62
+ errors.append("customers must be a list.")
63
+ customers = []
64
+ if not isinstance(templates, list):
65
+ errors.append("templates must be a list.")
66
+ templates = []
67
+ if not isinstance(keys, list):
68
+ errors.append("keys must be a list.")
69
+ keys = []
70
+
71
+ product_ids = {int(p.get("sourceId", 0)) for p in products if isinstance(p, dict)}
72
+ if len(product_ids) != len(products):
73
+ errors.append("Duplicate or missing product source ids.")
74
+ customer_ids = {int(c.get("sourceId", 0)) for c in customers if isinstance(c, dict)}
75
+ if len(customer_ids) != len(customers):
76
+ errors.append("Duplicate or missing customer source ids.")
77
+ template_ids = {int(t.get("sourceId", 0)) for t in templates if isinstance(t, dict)}
78
+ if len(template_ids) != len(templates):
79
+ errors.append("Duplicate or missing template source ids.")
80
+
81
+ for product in products:
82
+ if not isinstance(product, dict) or not str(product.get("name") or "").strip():
83
+ errors.append("A product has no name.")
84
+ break
85
+
86
+ for template in templates:
87
+ if not isinstance(template, dict):
88
+ errors.append("A template is not an object.")
89
+ continue
90
+ source_product = template.get("sourceProductId")
91
+ if source_product not in product_ids:
92
+ errors.append(f"Template {template.get('sourceId')} references unknown product {source_product}.")
93
+ if not str(template.get("name") or "").strip():
94
+ errors.append(f"Template {template.get('sourceId')} has no name.")
95
+
96
+ seen_keys: set[tuple[int, str]] = set()
97
+ activation_count = 0
98
+ data_object_count = 0
99
+ for key in keys:
100
+ if not isinstance(key, dict):
101
+ errors.append("A license is not an object.")
102
+ continue
103
+ source_product = key.get("sourceProductId")
104
+ normalized = str(key.get("key") or "").strip().upper()
105
+ if not normalized:
106
+ errors.append(f"License in product {source_product} has no key string.")
107
+ elif len(normalized) > 32:
108
+ errors.append(f"License {normalized} is longer than 32 characters.")
109
+ elif (source_product, normalized) in seen_keys:
110
+ errors.append(f"Duplicate license {normalized} in product {source_product}.")
111
+ seen_keys.add((source_product, normalized))
112
+ if source_product not in product_ids:
113
+ errors.append(f"License {normalized} references unknown product {source_product}.")
114
+ customer_id = normalize_customer_id(key.get("sourceCustomerId"))
115
+ if customer_id is not None and customer_id not in customer_ids:
116
+ errors.append(f"License {normalized} references unknown customer {customer_id}.")
117
+ if int(key.get("maxNoOfMachines") or 0) < 0:
118
+ warnings.append(f"License {normalized} has a negative machine limit; treating it as unlimited.")
119
+ activations = key.get("activations") or []
120
+ if isinstance(activations, list):
121
+ activation_count += len(activations)
122
+ data_objects = key.get("dataObjects") or []
123
+ if isinstance(data_objects, list):
124
+ data_object_count += len(data_objects)
125
+
126
+ counts = {
127
+ "products": len(products),
128
+ "customers": len(customers),
129
+ "templates": len(templates),
130
+ "keys": len(keys),
131
+ "activations": activation_count,
132
+ "dataObjects": data_object_count,
133
+ }
134
+ return ValidationResult(ok=not errors, errors=errors, warnings=warnings, counts=counts)
135
+
136
+
137
+ def validate_import_file(path: str | Path) -> ValidationResult:
138
+ return validate_bundle_data(load_bundle(path))
139
+
140
+
141
+ def write_bundle(path: str | Path, bundle: dict[str, Any]) -> Path:
142
+ target = Path(path).expanduser()
143
+ target.parent.mkdir(parents=True, exist_ok=True)
144
+ text = json.dumps(bundle, indent=2) + "\n"
145
+ # The bundle contains customer secrets and license keys. Create it owner-only on POSIX.
146
+ if os.name == "posix":
147
+ fd = os.open(target, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
148
+ try:
149
+ with os.fdopen(fd, "w", encoding="utf-8") as handle:
150
+ handle.write(text)
151
+ except Exception:
152
+ try:
153
+ os.close(fd)
154
+ except OSError:
155
+ pass
156
+ raise
157
+ else:
158
+ target.write_text(text, encoding="utf-8")
159
+ return target