inpython-package 1.1.7__tar.gz → 1.1.10__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.
- inpython_package-1.1.10/PKG-INFO +79 -0
- inpython_package-1.1.10/README.md +41 -0
- inpython_package-1.1.10/inpython/injcd.py +15 -0
- {inpython_package-1.1.7/inpython/inxml → inpython_package-1.1.10/inpython/inqrcode}/__init__.py +1 -1
- {inpython_package-1.1.7 → inpython_package-1.1.10}/inpython/inqrcode/paymentqrcode.py +20 -11
- inpython_package-1.1.10/inpython/inrest/__init__.py +6 -0
- {inpython_package-1.1.7 → inpython_package-1.1.10}/inpython/inrest/in_jwt.py +157 -131
- {inpython_package-1.1.7 → inpython_package-1.1.10}/inpython/intools/Servalux-devis.py +15 -11
- inpython_package-1.1.10/inpython/intools/__init__.py +6 -0
- inpython_package-1.1.10/inpython/intools/in_cron.py +66 -0
- inpython_package-1.1.10/inpython/intools/in_docx.py +162 -0
- inpython_package-1.1.7/inpython/intools/in_docx.py → inpython_package-1.1.10/inpython/intools/in_docx_extract.py +23 -16
- inpython_package-1.1.10/inpython/intools/in_docx_extract_canvas.py +257 -0
- inpython_package-1.1.10/inpython/intools/in_docx_protect.py +73 -0
- inpython_package-1.1.10/inpython/intools/in_geo_calc.py +25 -0
- inpython_package-1.1.10/inpython/intools/in_log.py +81 -0
- {inpython_package-1.1.7 → inpython_package-1.1.10}/inpython/intools/in_projet.py +9 -7
- {inpython_package-1.1.7 → inpython_package-1.1.10}/inpython/intools/in_test.py +1 -2
- inpython_package-1.1.10/inpython/intools/xls_to_csv.py +126 -0
- inpython_package-1.1.10/inpython/servalux/__init__.py +6 -0
- inpython_package-1.1.10/inpython/servalux/docx_ext_schema_svl.py +16 -0
- inpython_package-1.1.10/inpython/servalux/servalux-devis.py +202 -0
- inpython_package-1.1.10/inpython_package.egg-info/PKG-INFO +79 -0
- {inpython_package-1.1.7 → inpython_package-1.1.10}/inpython_package.egg-info/SOURCES.txt +10 -2
- inpython_package-1.1.10/inpython_package.egg-info/requires.txt +18 -0
- inpython_package-1.1.10/setup.py +41 -0
- inpython_package-1.1.7/PKG-INFO +0 -57
- inpython_package-1.1.7/README.md +0 -30
- inpython_package-1.1.7/inpython/inqrcode/__init__.py +0 -6
- inpython_package-1.1.7/inpython/inrest/__init__.py +0 -6
- inpython_package-1.1.7/inpython/intools/__init__.py +0 -6
- inpython_package-1.1.7/inpython/intools/xls_to_csv.py +0 -45
- inpython_package-1.1.7/inpython/inxml/validator.py +0 -43
- inpython_package-1.1.7/inpython_package.egg-info/PKG-INFO +0 -57
- inpython_package-1.1.7/inpython_package.egg-info/requires.txt +0 -5
- inpython_package-1.1.7/setup.py +0 -29
- {inpython_package-1.1.7 → inpython_package-1.1.10}/LICENSE.txt +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.10}/inpython/__init__.py +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.10}/inpython/__main__.py +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.10}/inpython/aotools/__init__.py +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.10}/inpython/aotools/aogeo.py +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.10}/inpython/ingraph/__init__.py +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.10}/inpython/ingraph/ingraph.py +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.10}/inpython/inqrcode/inqrcode.py +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.10}/inpython_package.egg-info/dependency_links.txt +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.10}/inpython_package.egg-info/top_level.txt +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.10}/setup.cfg +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: inpython-package
|
|
3
|
+
Version: 1.1.10
|
|
4
|
+
Summary: # Infodata's IN-Tools U2Python Package
|
|
5
|
+
Home-page: https://bitbucket.org/infodata-dev/inpython
|
|
6
|
+
Author: infodata
|
|
7
|
+
Author-email: jcd@infodata.lu
|
|
8
|
+
License: MIT
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE.txt
|
|
12
|
+
Requires-Dist: msal>=1.22
|
|
13
|
+
Requires-Dist: requests
|
|
14
|
+
Requires-Dist: qrcode[pil]
|
|
15
|
+
Requires-Dist: python-dotenv
|
|
16
|
+
Requires-Dist: lxml
|
|
17
|
+
Requires-Dist: openpyxl
|
|
18
|
+
Requires-Dist: latlon3
|
|
19
|
+
Requires-Dist: geopy
|
|
20
|
+
Requires-Dist: python-docx
|
|
21
|
+
Requires-Dist: docxcompose
|
|
22
|
+
Requires-Dist: docxtpl
|
|
23
|
+
Requires-Dist: pandas
|
|
24
|
+
Requires-Dist: cron-converter
|
|
25
|
+
Requires-Dist: cryptography
|
|
26
|
+
Requires-Dist: PyJWT
|
|
27
|
+
Requires-Dist: pywin32; platform_system == "Windows"
|
|
28
|
+
Dynamic: author
|
|
29
|
+
Dynamic: author-email
|
|
30
|
+
Dynamic: description
|
|
31
|
+
Dynamic: description-content-type
|
|
32
|
+
Dynamic: home-page
|
|
33
|
+
Dynamic: license
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
Dynamic: requires-dist
|
|
36
|
+
Dynamic: requires-python
|
|
37
|
+
Dynamic: summary
|
|
38
|
+
|
|
39
|
+
# README Production – inpython-package
|
|
40
|
+
|
|
41
|
+
Ce document explique comment installer et utiliser le package **inpython-package** en production.
|
|
42
|
+
|
|
43
|
+
## Présentation
|
|
44
|
+
|
|
45
|
+
**inpython-package** regroupe des modules Python pour l'intégration MS-Graph, la génération de QR code pour les paiements via ayden, la gestion de fichiers, la manipulation de documents, etc., utilisés par Integrix.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
Installer le package depuis PyPI :
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install inpython-package
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Structure du package
|
|
56
|
+
|
|
57
|
+
- `inpython.aotools` : outils avancés (géolocalisation, etc.)
|
|
58
|
+
- `inpython.ingraph` : gestion de l'authentification et des accès MS-Graph (utilise MSAL)
|
|
59
|
+
- `inpython.inqrcode` : génération de QR codes, gestion des liens de paiement, intégration de logos
|
|
60
|
+
- `inpython.inrest` : outils pour l'authentification JWT et REST
|
|
61
|
+
- `inpython.intools` : outils divers (conversion de fichiers, gestion de projets, cron, etc.)
|
|
62
|
+
- `inpython.inxml` : validation et manipulation de fichiers XML
|
|
63
|
+
|
|
64
|
+
## Utilisation
|
|
65
|
+
|
|
66
|
+
Importer les modules nécessaires dans vos scripts Python :
|
|
67
|
+
```python
|
|
68
|
+
from inpython.ingraph import ...
|
|
69
|
+
from inpython.inqrcode import ...
|
|
70
|
+
# etc.
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
Certains modules nécessitent un fichier `.env` pour les clés API et paramètres sensibles. Placez ce fichier à la racine de votre projet ou fournissez son chemin lors de l'appel des fonctions (pour inqrcode par exemple).
|
|
76
|
+
|
|
77
|
+
## Support
|
|
78
|
+
|
|
79
|
+
Pour toute question ou problème en production, contactez le support Integrix ou consultez la documentation officielle sur PyPI.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# README Production – inpython-package
|
|
2
|
+
|
|
3
|
+
Ce document explique comment installer et utiliser le package **inpython-package** en production.
|
|
4
|
+
|
|
5
|
+
## Présentation
|
|
6
|
+
|
|
7
|
+
**inpython-package** regroupe des modules Python pour l'intégration MS-Graph, la génération de QR code pour les paiements via ayden, la gestion de fichiers, la manipulation de documents, etc., utilisés par Integrix.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Installer le package depuis PyPI :
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install inpython-package
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Structure du package
|
|
18
|
+
|
|
19
|
+
- `inpython.aotools` : outils avancés (géolocalisation, etc.)
|
|
20
|
+
- `inpython.ingraph` : gestion de l'authentification et des accès MS-Graph (utilise MSAL)
|
|
21
|
+
- `inpython.inqrcode` : génération de QR codes, gestion des liens de paiement, intégration de logos
|
|
22
|
+
- `inpython.inrest` : outils pour l'authentification JWT et REST
|
|
23
|
+
- `inpython.intools` : outils divers (conversion de fichiers, gestion de projets, cron, etc.)
|
|
24
|
+
- `inpython.inxml` : validation et manipulation de fichiers XML
|
|
25
|
+
|
|
26
|
+
## Utilisation
|
|
27
|
+
|
|
28
|
+
Importer les modules nécessaires dans vos scripts Python :
|
|
29
|
+
```python
|
|
30
|
+
from inpython.ingraph import ...
|
|
31
|
+
from inpython.inqrcode import ...
|
|
32
|
+
# etc.
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
Certains modules nécessitent un fichier `.env` pour les clés API et paramètres sensibles. Placez ce fichier à la racine de votre projet ou fournissez son chemin lors de l'appel des fonctions (pour inqrcode par exemple).
|
|
38
|
+
|
|
39
|
+
## Support
|
|
40
|
+
|
|
41
|
+
Pour toute question ou problème en production, contactez le support Integrix ou consultez la documentation officielle sur PyPI.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from inrest import in_jwt
|
|
3
|
+
|
|
4
|
+
def main(argv):
|
|
5
|
+
#
|
|
6
|
+
try:
|
|
7
|
+
token = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJDYW14amZKUGw5aXhKWDdTQ3VKMXI0X2JWb2hFbVdkb3RHRmkwYmF5TFNzIn0.eyJleHAiOjE3ODc1NjU5MTcsImlhdCI6MTc4NzU2NTYxNywiYXV0aF90aW1lIjoxNzg3NTU0MzA4LCJqdGkiOiJvbnJ0YWM6MzVjODY2ZGYtNmIzZC02MjVlLTExMTgtNDI1ZmUyNjc3YjY3IiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay5kZXYuaW50cmEucHJpbWVhbGUubmV0L2F1dGgvcmVhbG1zL3ByaW1lYWxlLWNvbm5lY3QiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiZDFiMGM5M2MtMjhlMy00YzM4LTk3MTMtNjU1ZTFjM2FkN2Q4IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoicmVmZXJlbnRpZWwtZnJvbnQiLCJzaWQiOiIxUGMteGRHZ21hSmtOelI2NmNOTDRTdGoiLCJhY3IiOiIwIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6NDIwMyIsImh0dHBzOi8vcmVmZXJlbnRpZWwuZGV2LmludHJhLnByaW1lYWxlLm5ldCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsImRlZmF1bHQtcm9sZXMtYWQgZmxvcsOpYWxlIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6Ik5pY29sYXMgU2FiaW4gKFByaW3DqWFsZSBGcmFuY2UpIFNhYmluIiwicHJlZmVycmVkX3VzZXJuYW1lIjoibmlzYWJpbiIsImdpdmVuX25hbWUiOiJOaWNvbGFzIFNhYmluIChQcmltw6lhbGUgRnJhbmNlKSIsImZhbWlseV9uYW1lIjoiU2FiaW4iLCJlbWFpbCI6Im5pc2FiaW5AcHJpbWVhbGUuY29tIn0.nb3ii1SoGiypmZm2tTDsoKEqYPhrjSrQCMs5I4XHca1CJS_c2jwE6sbfJBio98xKwZEwokt9_-7uLxD6LKVxhVZHBI08X-TEs_iNh1XRynJcORsdr2z9oiYc4rW8lq-H_22zo33vbWnrjnQflxLvTwD8-xvnwbTVBqqJKUY7mugZBy9KnR6uOJqMks0D7uon2-XiKJq9idznYOtaoGHbeb9vMZJxrJTHV7gj9SNzOeGOGJQoLxqG2QCbA-uYlBYvp4xV2iUIH9eeUr1hcETw4wHAdn87qxHj5dqsU7zN0oO7fAdcCXzWvXHnx__Wi7pKfVPLUSD0jNSy4ugmGcuoRg"
|
|
8
|
+
claims = in_jwt.validate_keycloak_jwt(token, "http://localhost:8080/realms/myrealm","myclient-web")
|
|
9
|
+
print(claims)
|
|
10
|
+
except ValueError as e:
|
|
11
|
+
print(f"Erreur: {e}")
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
main(sys.argv[1:])
|
|
15
|
+
|
|
@@ -15,8 +15,8 @@ HEADERS = {
|
|
|
15
15
|
"Content-Type": "application/json"
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
def create_payment_link_adyen(amount_cents:
|
|
19
|
-
"""Crée un lien de paiement via l'API Adyen. Si mode=2, génère aussi un QR code (avec logo si fourni). Si env_path est fourni, charge les variables d'environnement depuis ce fichier."""
|
|
18
|
+
def create_payment_link_adyen(amount_cents: float, currency: str, reference: str, mode: str = 1, logo_path: str = None, env_path: str = None, qrcode_path: str = None, jpg_filename: str = None ) -> str:
|
|
19
|
+
"""Crée un lien de paiement via l'API Adyen. Si mode=2, génère aussi un QR code (avec logo si fourni). Si env_path est fourni, charge les variables d'environnement depuis ce fichier. jpg_filename permet de choisir le nom du fichier jpg du QR code."""
|
|
20
20
|
if env_path and os.path.exists(env_path):
|
|
21
21
|
from dotenv import dotenv_values
|
|
22
22
|
env_vars = dotenv_values(env_path)
|
|
@@ -33,7 +33,7 @@ def create_payment_link_adyen(amount_cents: int, currency: str, reference: str,
|
|
|
33
33
|
}
|
|
34
34
|
payload = {
|
|
35
35
|
"amount": {
|
|
36
|
-
"value": amount_cents,
|
|
36
|
+
"value": int(round(amount_cents)),
|
|
37
37
|
"currency": currency
|
|
38
38
|
},
|
|
39
39
|
"reference": reference,
|
|
@@ -45,23 +45,31 @@ def create_payment_link_adyen(amount_cents: int, currency: str, reference: str,
|
|
|
45
45
|
data = response.json()
|
|
46
46
|
payment_url = data["url"]
|
|
47
47
|
if mode == 2:
|
|
48
|
-
qr_path = generate_qr_code(payment_url, reference, logo_path,qrcode_path)
|
|
48
|
+
qr_path = generate_qr_code(payment_url, reference, logo_path, qrcode_path, jpg_filename)
|
|
49
49
|
return qr_path
|
|
50
50
|
return payment_url
|
|
51
51
|
|
|
52
|
-
def generate_qr_code(url: str, reference: str, logo_path: str = None, qrcode_path: str = None) -> None:
|
|
53
|
-
"""Génère un QR code dans le dossier ./qrcode/ (au même niveau que paymentqrcode.py), avec logo si fourni. Crée/écrase un fichier qrcode_logs.txt pour le diagnostic."""
|
|
52
|
+
def generate_qr_code(url: str, reference: str, logo_path: str = None, qrcode_path: str = None, jpg_filename: str = None) -> None:
|
|
53
|
+
"""Génère un QR code dans le dossier ./qrcode/ (au même niveau que paymentqrcode.py), avec logo si fourni. Crée/écrase un fichier qrcode_logs.txt pour le diagnostic. jpg_filename permet de choisir le nom du fichier jpg du QR code (avec ou sans extension)."""
|
|
54
54
|
import traceback
|
|
55
55
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
|
56
56
|
output_dir = os.path.join(base_dir, "qrcode")
|
|
57
57
|
|
|
58
58
|
os.makedirs(output_dir, exist_ok=True)
|
|
59
59
|
print("qrcode_path : ",qrcode_path)
|
|
60
|
+
if jpg_filename:
|
|
61
|
+
filename_base = jpg_filename
|
|
62
|
+
else:
|
|
63
|
+
filename_base = f"{reference}.jpg"
|
|
64
|
+
# Ajoute .jpg seulement si jpg_filename est vide ou ne contient pas déjà une extension
|
|
65
|
+
if jpg_filename:
|
|
66
|
+
if not (jpg_filename.lower().endswith('.jpg') or jpg_filename.lower().endswith('.jpeg')):
|
|
67
|
+
filename_base = jpg_filename + ".jpg"
|
|
60
68
|
if qrcode_path:
|
|
61
|
-
filename = os.path.join(qrcode_path,
|
|
69
|
+
filename = os.path.join(qrcode_path, filename_base)
|
|
62
70
|
log_file = os.path.join(qrcode_path, "qrcode_debug.log")
|
|
63
71
|
else:
|
|
64
|
-
filename = os.path.join(output_dir,
|
|
72
|
+
filename = os.path.join(output_dir, filename_base)
|
|
65
73
|
log_file = os.path.join(output_dir, "qrcode_debug.log")
|
|
66
74
|
|
|
67
75
|
# Supprime le fichier s’il existe
|
|
@@ -115,15 +123,16 @@ def clean(s):
|
|
|
115
123
|
if __name__ == "__main__":
|
|
116
124
|
import sys
|
|
117
125
|
try:
|
|
118
|
-
# Utilisation : python paymentqrcode.py <mode> <montant> <devise> <reference> [chemin_logo] [chemin_env]
|
|
126
|
+
# Utilisation : python paymentqrcode.py <mode> <montant> <devise> <reference> [chemin_logo] [chemin_env] [qrcode_path] [jpg_filename]
|
|
119
127
|
mode = int(clean(sys.argv[1]))
|
|
120
|
-
amount =
|
|
128
|
+
amount = float(clean(sys.argv[2]))
|
|
121
129
|
currency = sys.argv[3]
|
|
122
130
|
reference = sys.argv[4]
|
|
123
131
|
logo_path = sys.argv[5] if len(sys.argv) > 5 else ''
|
|
124
132
|
env_path = sys.argv[6] if len(sys.argv) > 6 else ''
|
|
125
133
|
qrcode_path = sys.argv[7] if len(sys.argv) > 7 else ''
|
|
126
|
-
|
|
134
|
+
jpg_filename = sys.argv[8] if len(sys.argv) > 8 else ''
|
|
135
|
+
result = create_payment_link_adyen(amount, currency, reference, mode, logo_path, env_path, qrcode_path, jpg_filename if jpg_filename else None)
|
|
127
136
|
print(result)
|
|
128
137
|
except Exception as e:
|
|
129
138
|
print("Erreur :", e)
|
|
@@ -1,131 +1,157 @@
|
|
|
1
|
-
#--------------------------------------------------------
|
|
2
|
-
# Manipulates a Json web token
|
|
3
|
-
#
|
|
4
|
-
#--------------------------------------------------------
|
|
5
|
-
# 22/08/2024 (JCD) : Creation fonction generateJwtClientAssertion
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
from cryptography.hazmat.primitives import
|
|
9
|
-
from cryptography.hazmat.
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
=
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
Path
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
1
|
+
#--------------------------------------------------------
|
|
2
|
+
# Manipulates a Json web token
|
|
3
|
+
#
|
|
4
|
+
#--------------------------------------------------------
|
|
5
|
+
# 22/08/2024 (JCD) : Creation fonction generateJwtClientAssertion
|
|
6
|
+
# 21/08/2026 (JCD) : ajout validate_keycloak_jwt et validate_microsoft_jwt
|
|
7
|
+
#--------------------------------------------------------
|
|
8
|
+
from cryptography.hazmat.primitives.serialization import pkcs12
|
|
9
|
+
from cryptography.hazmat.primitives import serialization
|
|
10
|
+
from cryptography.hazmat.backends import default_backend
|
|
11
|
+
import jwt
|
|
12
|
+
import datetime
|
|
13
|
+
from jwt import PyJWKClient
|
|
14
|
+
import json
|
|
15
|
+
|
|
16
|
+
# Generates a jwt client assertion from a private key
|
|
17
|
+
def generateJwtClientAssertion(mode,path_certificate,private_key,client_id,audience,expirationTime=10):
|
|
18
|
+
""" Return jwt token assertion
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
------------
|
|
22
|
+
mode:int
|
|
23
|
+
= 1 : Private key
|
|
24
|
+
= 2 : Private password
|
|
25
|
+
path_certificate: string
|
|
26
|
+
Path crt/cer file if mode = 1
|
|
27
|
+
Path pfx file if mode = 2
|
|
28
|
+
private_key: byte
|
|
29
|
+
Path key file file if mode = 1
|
|
30
|
+
Private password if mode = 2
|
|
31
|
+
client_id: string
|
|
32
|
+
audience: string
|
|
33
|
+
expirationTime: string, Optional
|
|
34
|
+
in minutes. Default to 10 minutes
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
if mode != "1" and mode != "2" :
|
|
38
|
+
raise Exception("Unmanaged mode")
|
|
39
|
+
|
|
40
|
+
if not expirationTime:
|
|
41
|
+
expirationTime = 10
|
|
42
|
+
else:
|
|
43
|
+
expirationTime = int(expirationTime)
|
|
44
|
+
|
|
45
|
+
# Load the public certificate
|
|
46
|
+
with open(path_certificate, "rb") as cert_file:
|
|
47
|
+
certificate_data = cert_file.read()
|
|
48
|
+
|
|
49
|
+
# Load the private key
|
|
50
|
+
if mode == "1":
|
|
51
|
+
with open(private_key, "rb") as key_file:
|
|
52
|
+
private_key = serialization.load_pem_private_key(
|
|
53
|
+
key_file.read(),
|
|
54
|
+
password=None,
|
|
55
|
+
backend=default_backend()
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
private_key, certificate, additional_certs = pkcs12.load_key_and_certificates(
|
|
59
|
+
certificate_data, private_key.encode(), backend=default_backend()
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
|
63
|
+
# Define the JWT claims
|
|
64
|
+
claims = {
|
|
65
|
+
"iss": client_id, # The issuer, typically your client ID
|
|
66
|
+
"sub": client_id, # The subject, typically your client ID
|
|
67
|
+
"aud": audience, # The audience, typically the token endpoint URL
|
|
68
|
+
"exp": now + datetime.timedelta(expirationTime), # Expiration time
|
|
69
|
+
"jti": "unique-identifier", # JWT ID, a unique identifier for the JWT
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Create the JWT (client assertion)
|
|
73
|
+
if mode == 1:
|
|
74
|
+
client_assertion = jwt.encode(
|
|
75
|
+
claims,
|
|
76
|
+
private_key,
|
|
77
|
+
algorithm="RS256",
|
|
78
|
+
headers={"x5c": [certificate_data.decode("utf-8").replace("\n", "")]}
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
client_assertion = jwt.encode(
|
|
82
|
+
claims, private_key, algorithm="RS256", headers={"alg": "RS256","typ": "JWT"}
|
|
83
|
+
)
|
|
84
|
+
return client_assertion
|
|
85
|
+
except:
|
|
86
|
+
raise # so that the caller receives the exception
|
|
87
|
+
|
|
88
|
+
def validate_keycloak_jwt(token: str,keycloak_url: str,client_id: str):
|
|
89
|
+
"""
|
|
90
|
+
Valide un JWT Keycloak.
|
|
91
|
+
Accepte un client_id qui correspond soit a aud, soit a azp.
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
jwks_url = (f"{keycloak_url}/protocol/openid-connect/certs")
|
|
95
|
+
signing_key = PyJWKClient(jwks_url).get_signing_key_from_jwt(token)
|
|
96
|
+
payload = jwt.decode(
|
|
97
|
+
token,
|
|
98
|
+
signing_key.key,
|
|
99
|
+
algorithms=["RS256"],
|
|
100
|
+
issuer=keycloak_url,
|
|
101
|
+
options={"verify_aud": False}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
aud = payload.get("aud")
|
|
105
|
+
azp = payload.get("azp")
|
|
106
|
+
|
|
107
|
+
if isinstance(aud, list):
|
|
108
|
+
audience_matches = client_id in aud
|
|
109
|
+
else:
|
|
110
|
+
audience_matches = aud == client_id
|
|
111
|
+
|
|
112
|
+
if not audience_matches and azp != client_id:
|
|
113
|
+
raise ValueError(
|
|
114
|
+
"Audience Keycloak incorrect: "
|
|
115
|
+
f"client_id='{client_id}' ne correspond ni a aud='{aud}' "
|
|
116
|
+
f"ni a azp='{azp}'"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return json.dumps(payload)
|
|
120
|
+
except jwt.ExpiredSignatureError:
|
|
121
|
+
raise ValueError("Expired JWT")
|
|
122
|
+
except jwt.InvalidIssuerError:
|
|
123
|
+
raise ValueError("Issuer Keycloak incorrect")
|
|
124
|
+
except jwt.InvalidTokenError as e:
|
|
125
|
+
raise ValueError(
|
|
126
|
+
f"JWT Keycloak invalide: {str(e)}"
|
|
127
|
+
)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
raise ValueError(
|
|
130
|
+
f"Erreur validation Keycloak: {str(e)}"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def validate_microsoft_jwt(token: str, issuer: str = None, audience: str = None):
|
|
134
|
+
"""
|
|
135
|
+
Valide un JWT Microsoft en récupérant les clés JWKS depuis:
|
|
136
|
+
https://login.microsoftonline.com/common/discovery/v2.0/keys
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
jwks_url = "https://login.microsoftonline.com/common/discovery/v2.0/keys"
|
|
140
|
+
signing_key = PyJWKClient(jwks_url).get_signing_key_from_jwt(token)
|
|
141
|
+
|
|
142
|
+
options = {"verify_aud": bool(audience), "verify_iss": bool(issuer)}
|
|
143
|
+
claims = jwt.decode(
|
|
144
|
+
token,
|
|
145
|
+
signing_key.key,
|
|
146
|
+
algorithms=["RS256"],
|
|
147
|
+
audience=audience,
|
|
148
|
+
issuer=issuer,
|
|
149
|
+
options=options
|
|
150
|
+
)
|
|
151
|
+
return json.dumps(claims)
|
|
152
|
+
except jwt.ExpiredSignatureError:
|
|
153
|
+
raise ValueError("JWT Microsoft expiré")
|
|
154
|
+
except jwt.InvalidTokenError as e:
|
|
155
|
+
raise ValueError(f"JWT Microsoft invalide: {str(e)}")
|
|
156
|
+
except Exception as e:
|
|
157
|
+
raise ValueError(f"Erreur validation Microsoft: {str(e)}")
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
from docx import Document
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
def chk_br(par):
|
|
4
5
|
for run in par.r_lst:
|
|
5
6
|
if len(run.br_lst) > 0:
|
|
6
|
-
return(True)
|
|
7
|
-
return(False)
|
|
7
|
+
return (True)
|
|
8
|
+
return (False)
|
|
9
|
+
|
|
8
10
|
|
|
9
|
-
def build_word_file(doc_directory,fab_filename,skipped_pg_br,keeped_pg_br):
|
|
11
|
+
def build_word_file(doc_directory, fab_filename, skipped_pg_br, keeped_pg_br):
|
|
10
12
|
tmp_file = doc_directory + "/schemas.docx"
|
|
11
13
|
|
|
12
14
|
print(fab_filename)
|
|
@@ -36,23 +38,25 @@ def build_word_file(doc_directory,fab_filename,skipped_pg_br,keeped_pg_br):
|
|
|
36
38
|
if num_page_breaks == 3:
|
|
37
39
|
state = 2
|
|
38
40
|
doc_tmp.element.body.remove(elt)
|
|
39
|
-
#doc_tmp.add_paragraph()
|
|
40
|
-
#elif elt.tag == "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}tbl":
|
|
41
|
-
|
|
41
|
+
# doc_tmp.add_paragraph()
|
|
42
|
+
# elif elt.tag == "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}tbl":
|
|
43
|
+
# table_copy(elt, doc_fab, doc_tmp)
|
|
42
44
|
elif state == 2:
|
|
43
45
|
if elt.tag != "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}sectPr":
|
|
44
46
|
doc_tmp.element.body.remove(elt)
|
|
45
|
-
|
|
47
|
+
|
|
46
48
|
print("save " + tmp_file)
|
|
47
49
|
doc_tmp.save(tmp_file)
|
|
48
50
|
|
|
49
|
-
return(tmp_file)
|
|
51
|
+
return (tmp_file)
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
|
|
54
|
+
def build_devis(doc_directory, fab_filename, startdoc, end_doc):
|
|
55
|
+
tmp_doc = build_word_file(doc_directory, fab_filename, 2, 1)
|
|
53
56
|
print(tmp_doc)
|
|
54
57
|
|
|
58
|
+
|
|
55
59
|
doc_dir = "Q:/is/chantier/1000/"
|
|
56
60
|
fab_fn = "Q:/is/chantier/1000/FABRIQUANT/1000.docx"
|
|
57
61
|
|
|
58
|
-
build_devis(doc_dir,fab_fn,"","")
|
|
62
|
+
build_devis(doc_dir, fab_fn, "", "")
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# compute cron value ; used by IN.JOB
|
|
2
|
+
# efv 01.12.2025 create
|
|
3
|
+
# ------------------------------------------
|
|
4
|
+
import sys
|
|
5
|
+
from cron_converter import Cron
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def next(cronexp: str, dtiso: str):
|
|
10
|
+
"""Compute next datetime from crontab expression, starting from dtiso else now().
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
cronexp (str): a crontab expression string like : */5 * * * *
|
|
14
|
+
dtiso (str): a datetime iso format string like : 2025-12-01T09:00:23
|
|
15
|
+
if null use now()
|
|
16
|
+
|
|
17
|
+
Raises:
|
|
18
|
+
Exception: forward the exception to the caller
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
_type_: datetime.isoformat YYYY-MM-DDTHH:MM:SS
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
if len(cronexp) == 0:
|
|
25
|
+
raise Exception("cronexp is not provided.")
|
|
26
|
+
if len(dtiso) == 0:
|
|
27
|
+
reference = datetime.now()
|
|
28
|
+
else:
|
|
29
|
+
reference = datetime.fromisoformat(dtiso)
|
|
30
|
+
cron_instance = Cron() # Create a new instance
|
|
31
|
+
cron_instance.from_string(cronexp) # load the cron expression
|
|
32
|
+
schedule = cron_instance.schedule(reference) # load the datetime reference
|
|
33
|
+
thenext = schedule.next() # compute the next runtime datetime
|
|
34
|
+
return thenext.isoformat() # return isoformat YYYY-MM-DDTHH:MM:SS
|
|
35
|
+
except:
|
|
36
|
+
raise # raise the error to the parent caller
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
def validate(cronexp: str):
|
|
41
|
+
try:
|
|
42
|
+
if cronexp == '':
|
|
43
|
+
raise Exception("cronexp is not provided.")
|
|
44
|
+
cron_instance = Cron() # Create a new instance
|
|
45
|
+
cron_instance.from_string(cronexp)
|
|
46
|
+
return cron_instance.to_string()
|
|
47
|
+
except:
|
|
48
|
+
raise
|
|
49
|
+
|
|
50
|
+
# ---------------------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
"""
|
|
54
|
+
test and sample of cron validate(), cron next()
|
|
55
|
+
python inpython\intools\in_cron.py '*/5 * * * *' '2025-12-01T09:00:23'
|
|
56
|
+
python inpython\intools\in_cron.py '*/5 * * * *'
|
|
57
|
+
"""
|
|
58
|
+
cronexp = sys.argv[1]
|
|
59
|
+
dtiso = ''
|
|
60
|
+
if len(sys.argv) == 3:
|
|
61
|
+
dtiso = sys.argv[2]
|
|
62
|
+
res = validate(cronexp)
|
|
63
|
+
print(res, type(res))
|
|
64
|
+
res = next(cronexp, dtiso)
|
|
65
|
+
print(res, type(res))
|
|
66
|
+
|