Skip to content

Commit 0e72949

Browse files
committed
lifecycle
1 parent e383ad9 commit 0e72949

8 files changed

Lines changed: 329 additions & 3 deletions

File tree

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
python-ldap>=3.4
1+
python-ldap>=3.4
2+
Jinja2>=3.1

src/bin/lifecycle.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/python3
2+
# Recherche un script nommé 'lifecycle' (.py/.sh/.pl) dans src/lifecycle/.
3+
# Avant d'exécuter lifecycle.py, vérifie s'il existe un script nommé
4+
# {before.lifecycle}_{after.lifecycle}+extension et l'exécute à la place si trouvé.
5+
# Si aucun script lifecycle n'est trouvé, retourne "not concerned".
6+
import json
7+
import os
8+
import subprocess
9+
import sys
10+
11+
sys.path.append('../lib')
12+
import backend_utils as u
13+
14+
15+
EXTENSIONS = ['', '.py', '.sh', '.pl']
16+
17+
18+
def run_backend(script, content):
19+
ret = subprocess.run(script, input=content.encode(), capture_output=True)
20+
return {"returncode": ret.returncode, "stdout": ret.stdout.decode()}
21+
22+
23+
def find_script(lifecycle_dir, names):
24+
for name in names:
25+
for ext in EXTENSIONS:
26+
candidate = lifecycle_dir + '/' + name + ext
27+
if os.path.isfile(candidate):
28+
return candidate
29+
return None
30+
31+
32+
def main():
33+
lifecycle_dir = '../lifecycle'
34+
raw = sys.stdin.read()
35+
content = raw.replace("\n", "")
36+
37+
try:
38+
data = json.loads(content)
39+
before = u.find_key(data, 'before.lifecycle')
40+
after = u.find_key(data, 'after.lifecycle')
41+
specific_name = before + '_' + after if before and after else None
42+
except (json.JSONDecodeError, Exception):
43+
specific_name = None
44+
45+
script = None
46+
if specific_name:
47+
script = find_script(lifecycle_dir, [specific_name])
48+
49+
if script is None:
50+
script = find_script(lifecycle_dir, ['lifecycle'])
51+
52+
if script is None:
53+
print(u.returncode(0, "not concerned"))
54+
exit(0)
55+
56+
ret = run_backend(script, content)
57+
print(ret['stdout'])
58+
exit(ret['returncode'])
59+
60+
61+
if __name__ == '__main__':
62+
main()

src/config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,6 @@ actions:
2626
PING_TARGET:
2727
script: 'ping.py'
2828
onError: 'stop'
29+
IDENTITY_LIFECYCLE_CHANGED:
30+
script: 'lifecycle.py'
31+
onError: 'stop'

src/lib/backend_ldap_utils.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import configparser
22
import json
3+
import os
34

45
import jinja2
56
import ldap
67
import ldap.modlist as modlist
78
import sys
9+
810
from sys import stdin
911
sys.path.append('.')
1012
import backend_utils as u
13+
__LIFECYLE_DIR__="../lifecycle"
1114

1215
def set_config(config):
1316
u.__CONFIG__ = config
@@ -314,4 +317,18 @@ def activate_entry(l,entity,activate):
314317
return (u.returncode(1, format_ldap_error(e)))
315318

316319
else:
317-
return (u.returncode(1,"Not Found"))
320+
return (u.returncode(1,"Not Found"))
321+
322+
def lifecycle(entity):
323+
before = entity['payload']['before']['lifecycle']
324+
after = entity['payload']['after']['lifecycle']
325+
py_name=before + "_" + after + ".py"
326+
test=os.getcwd()
327+
r=0
328+
if os.path.exists(__LIFECYLE_DIR__ + "/" + py_name):
329+
x=1
330+
elif os.path.exists(__LIFECYLE_DIR__ + "/" +"lifecycle.py"):
331+
import lifecycle
332+
lifecycle.do(entity)
333+
334+
return(r)

src/lifecycle/lifecycle.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/python3
2+
3+
import sys
4+
5+
sys.path.append('../lib')
6+
import backend_utils as u
7+
8+
9+
10+
11+
def main():
12+
content=u.readjsoninput()
13+
return print(u.returncode(0,'lifecycle.py'))
14+
15+
if __name__ == '__main__':
16+
main()
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
{
2+
"concernedTo": "6852cd3980ae416061df5dae",
3+
"payload": {
4+
"before": {
5+
"_id": "6852cd3980ae416061df5dae",
6+
"inetOrgPerson": {
7+
"cn": "Dupont Gerald",
8+
"displayName": "Gérald Dupont",
9+
"facsimileTelephoneNumber": "",
10+
"givenName": "Gérald",
11+
"labeledURI": "",
12+
"mail": "",
13+
"mobile": "06 01 02 03 04",
14+
"postalAddress": "",
15+
"preferredLanguage": "",
16+
"sn": "Dupont",
17+
"telephoneNumber": "",
18+
"title": "",
19+
"uid": "gdupont",
20+
"employeeNumber": [
21+
"1"
22+
],
23+
"employeeType": "TAIGA",
24+
"departmentNumber": [
25+
"esn"
26+
],
27+
"jpegPhoto": "",
28+
"userCertificate": ""
29+
},
30+
"additionalFields": {
31+
"objectClasses": [
32+
"supannPerson",
33+
"eduPerson",
34+
"sogxuser"
35+
],
36+
"attributes": {
37+
"eduPerson": {
38+
"edupersonaffiliation": [
39+
"teacher",
40+
"member"
41+
],
42+
"edupersonprimaryaffiliation": "teacher",
43+
"edupersonprincipalname": "gdupont@domaine.fr"
44+
},
45+
"sogxuser": {
46+
"sogxdisableflag": 0,
47+
"sogxquota": 0,
48+
"proxyaddress": []
49+
},
50+
"supannPerson": {
51+
"supannAutreMail": "dupont@perso.fr",
52+
"supannEmpId": "1",
53+
"supannEtablissement": "{UAI}123456A",
54+
"supannNomdeNaissance": "",
55+
"supannOIDCDatedeNaissance": "30/01/1975",
56+
"supannOIDCGenre": "M.",
57+
"supannPrenomsEtatCivil": "Gérald",
58+
"supannRefId": [
59+
"1",
60+
"2"
61+
],
62+
"supannTypeEntiteAffectation": [
63+
"esn"
64+
],
65+
"supanncivilite": "M.",
66+
"supannCodeINSEEPaysDeNaissance": "",
67+
"supannCodeINSEEVilleDeNaissance": "",
68+
"supannListeRouge": "",
69+
"mailForwardingAddress": "",
70+
"supannMailPerso": "test@test.fr",
71+
"supannRoleGenerique": "",
72+
"supannParrainDN": "",
73+
"supannActivite": "",
74+
"supannEmpDateFin": "",
75+
"supannEtuAnneeInscription": "",
76+
"supannEntiteAffectation": [],
77+
"supannEntiteAffectationPrincipale": "xxx",
78+
"supannEtUid": "",
79+
"supannEtuCursusAnnee": "",
80+
"supannEtuDiplome": "",
81+
"supannCodeIne": "",
82+
"supannEtuId": ""
83+
}
84+
},
85+
"validations": {}
86+
},
87+
"dataStatus": 1,
88+
"deletedFlag": false,
89+
"initInfo": {
90+
"sentDate": "",
91+
"initDate": "2026-04-07T10:57:16.669Z"
92+
},
93+
"initState": 1,
94+
"lastBackendSync": "2026-04-14T08:08:00.510Z",
95+
"lastSync": "2025-06-18T14:29:13.646Z",
96+
"lifecycle": "O",
97+
"metadata": {
98+
"createdBy": "local",
99+
"createdAt": "2025-06-18T14:29:13.646Z",
100+
"lastUpdatedBy": "admin",
101+
"lastUpdatedAt": "2026-04-14T08:07:37.997Z"
102+
},
103+
"primaryEmployeeNumber": "",
104+
"srcFusionId": "",
105+
"state": 99,
106+
"fingerprint": "e2d5b2c49db1e24632ca21d2a0f8a43136f4270093b55cf7394fe8b212cc9114",
107+
"ignoreLifecycle": false,
108+
"lastLifecycleUpdate": "2026-03-23T08:32:52.926Z",
109+
"ignoreFusion": []
110+
},
111+
"after": {
112+
"_id": "6852cd3980ae416061df5dae",
113+
"inetOrgPerson": {
114+
"cn": "Dupont Gerald",
115+
"displayName": "Gérald Dupont",
116+
"facsimileTelephoneNumber": "",
117+
"givenName": "Gérald",
118+
"labeledURI": "",
119+
"mail": "",
120+
"mobile": "06 01 02 03 04",
121+
"postalAddress": "",
122+
"preferredLanguage": "",
123+
"sn": "Dupont",
124+
"telephoneNumber": "",
125+
"title": "",
126+
"uid": "gdupont",
127+
"employeeNumber": "1",
128+
"employeeType": "TAIGA",
129+
"departmentNumber": [
130+
"esn"
131+
],
132+
"jpegPhoto": "",
133+
"userCertificate": ""
134+
},
135+
"additionalFields": {
136+
"objectClasses": [
137+
"supannPerson",
138+
"eduPerson",
139+
"sogxuser"
140+
],
141+
"attributes": {
142+
"eduPerson": {
143+
"edupersonaffiliation": [
144+
"teacher",
145+
"member"
146+
],
147+
"edupersonprimaryaffiliation": "teacher",
148+
"edupersonprincipalname": "gdupont@domaine.fr"
149+
},
150+
"sogxuser": {
151+
"sogxdisableflag": 0,
152+
"sogxquota": 0,
153+
"proxyaddress": []
154+
},
155+
"supannPerson": {
156+
"supannAutreMail": "dupont@perso.fr",
157+
"supannEmpId": "1",
158+
"supannEtablissement": "{UAI}123456A",
159+
"supannNomdeNaissance": "",
160+
"supannOIDCDatedeNaissance": "30/01/1975",
161+
"supannOIDCGenre": "M.",
162+
"supannPrenomsEtatCivil": "Gérald",
163+
"supannRefId": [
164+
"1",
165+
"2"
166+
],
167+
"supannTypeEntiteAffectation": [
168+
"esn"
169+
],
170+
"supanncivilite": "M.",
171+
"supannCodeINSEEPaysDeNaissance": "",
172+
"supannCodeINSEEVilleDeNaissance": "",
173+
"supannListeRouge": "",
174+
"mailForwardingAddress": "",
175+
"supannMailPerso": "test@test.fr",
176+
"supannRoleGenerique": "",
177+
"supannParrainDN": "",
178+
"supannActivite": "",
179+
"supannEmpDateFin": "",
180+
"supannEtuAnneeInscription": "",
181+
"supannEntiteAffectation": [],
182+
"supannEntiteAffectationPrincipale": "xxx",
183+
"supannEtUid": "",
184+
"supannEtuCursusAnnee": "",
185+
"supannEtuDiplome": "",
186+
"supannCodeIne": "",
187+
"supannEtuId": ""
188+
}
189+
},
190+
"validations": {}
191+
},
192+
"dataStatus": 1,
193+
"deletedFlag": false,
194+
"initInfo": {
195+
"sentDate": "",
196+
"initDate": "2026-04-07T10:57:16.669Z"
197+
},
198+
"initState": 1,
199+
"lastBackendSync": "2026-04-14T08:08:00.510Z",
200+
"lastSync": "2025-06-18T14:29:13.646Z",
201+
"lifecycle": "I",
202+
"metadata": {
203+
"createdBy": "local",
204+
"createdAt": "2025-06-18T14:29:13.646Z",
205+
"lastUpdatedBy": "admin",
206+
"lastUpdatedAt": "2026-04-14T08:08:08.106Z"
207+
},
208+
"primaryEmployeeNumber": "",
209+
"srcFusionId": "",
210+
"state": 99,
211+
"fingerprint": "e2d5b2c49db1e24632ca21d2a0f8a43136f4270093b55cf7394fe8b212cc9114",
212+
"ignoreLifecycle": false,
213+
"lastLifecycleUpdate": "2026-04-14T08:08:08.104Z",
214+
"ignoreFusion": []
215+
}
216+
},
217+
"options": {
218+
"updateStatus": true,
219+
"task": "69ddf5e8fad42e7823306393"
220+
}
221+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "concernedTo": "6852cd3980ae416061df5dae", "payload": { "before": { "_id": "6852cd3980ae416061df5dae", "inetOrgPerson": { "cn": "Dupont Gerald", "displayName": "Gérald Dupont", "facsimileTelephoneNumber": "", "givenName": "Gérald", "labeledURI": "", "mail": "", "mobile": "06 01 02 03 04", "postalAddress": "", "preferredLanguage": "", "sn": "Dupont", "telephoneNumber": "", "title": "", "uid": "gdupont", "employeeNumber": [ "1" ], "employeeType": "TAIGA", "departmentNumber": [ "esn" ], "jpegPhoto": "", "userCertificate": "" }, "additionalFields": { "objectClasses": [ "supannPerson", "eduPerson", "sogxuser" ], "attributes": { "eduPerson": { "edupersonaffiliation": [ "teacher", "member" ], "edupersonprimaryaffiliation": "teacher", "edupersonprincipalname": "gdupont@domaine.fr" }, "sogxuser": { "sogxdisableflag": 0, "sogxquota": 0, "proxyaddress": [] }, "supannPerson": { "supannAutreMail": "dupont@perso.fr", "supannEmpId": "1", "supannEtablissement": "{UAI}123456A", "supannNomdeNaissance": "", "supannOIDCDatedeNaissance": "30/01/1975", "supannOIDCGenre": "M.", "supannPrenomsEtatCivil": "Gérald", "supannRefId": [ "1", "2" ], "supannTypeEntiteAffectation": [ "esn" ], "supanncivilite": "M.", "supannCodeINSEEPaysDeNaissance": "", "supannCodeINSEEVilleDeNaissance": "", "supannListeRouge": "", "mailForwardingAddress": "", "supannMailPerso": "test@test.fr", "supannRoleGenerique": "", "supannParrainDN": "", "supannActivite": "", "supannEmpDateFin": "", "supannEtuAnneeInscription": "", "supannEntiteAffectation": [], "supannEntiteAffectationPrincipale": "esn", "supannEtUid": "", "supannEtuCursusAnnee": "", "supannEtuDiplome": "", "supannCodeIne": "", "supannEtuId": "" } }, "validations": {} }, "dataStatus": 1, "deletedFlag": false, "initInfo": { "sentDate": "", "initDate": "2026-04-07T10:57:16.669Z" }, "initState": 1, "lastBackendSync": "2026-04-14T08:08:00.510Z", "lastSync": "2025-06-18T14:29:13.646Z", "lifecycle": "O", "metadata": { "createdBy": "local", "createdAt": "2025-06-18T14:29:13.646Z", "lastUpdatedBy": "admin", "lastUpdatedAt": "2026-04-14T08:07:37.997Z" }, "primaryEmployeeNumber": "", "srcFusionId": "", "state": 99, "fingerprint": "e2d5b2c49db1e24632ca21d2a0f8a43136f4270093b55cf7394fe8b212cc9114", "ignoreLifecycle": false, "lastLifecycleUpdate": "2026-03-23T08:32:52.926Z", "ignoreFusion": [] }, "after": { "_id": "6852cd3980ae416061df5dae", "inetOrgPerson": { "cn": "Dupont Gerald", "displayName": "Gérald Dupont", "facsimileTelephoneNumber": "", "givenName": "Gérald", "labeledURI": "", "mail": "", "mobile": "06 01 02 03 04", "postalAddress": "", "preferredLanguage": "", "sn": "Dupont", "telephoneNumber": "", "title": "", "uid": "gdupont", "employeeNumber": "1", "employeeType": "TAIGA", "departmentNumber": [ "esn" ], "jpegPhoto": "", "userCertificate": "" }, "additionalFields": { "objectClasses": [ "supannPerson", "eduPerson", "sogxuser" ], "attributes": { "eduPerson": { "edupersonaffiliation": [ "teacher", "member" ], "edupersonprimaryaffiliation": "teacher", "edupersonprincipalname": "gdupont@domaine.fr" }, "sogxuser": { "sogxdisableflag": 0, "sogxquota": 0, "proxyaddress": [] }, "supannPerson": { "supannAutreMail": "dupont@perso.fr", "supannEmpId": "1", "supannEtablissement": "{UAI}123456A", "supannNomdeNaissance": "", "supannOIDCDatedeNaissance": "30/01/1975", "supannOIDCGenre": "M.", "supannPrenomsEtatCivil": "Gérald", "supannRefId": [ "1", "2" ], "supannTypeEntiteAffectation": [ "esn" ], "supanncivilite": "M.", "supannCodeINSEEPaysDeNaissance": "", "supannCodeINSEEVilleDeNaissance": "", "supannListeRouge": "", "mailForwardingAddress": "", "supannMailPerso": "test@test.fr", "supannRoleGenerique": "", "supannParrainDN": "", "supannActivite": "", "supannEmpDateFin": "", "supannEtuAnneeInscription": "", "supannEntiteAffectation": [], "supannEntiteAffectationPrincipale": "esn", "supannEtUid": "", "supannEtuCursusAnnee": "", "supannEtuDiplome": "", "supannCodeIne": "", "supannEtuId": "" } }, "validations": {} }, "dataStatus": 1, "deletedFlag": false, "initInfo": { "sentDate": "", "initDate": "2026-04-07T10:57:16.669Z" }, "initState": 1, "lastBackendSync": "2026-04-14T08:08:00.510Z", "lastSync": "2025-06-18T14:29:13.646Z", "lifecycle": "I", "metadata": { "createdBy": "local", "createdAt": "2025-06-18T14:29:13.646Z", "lastUpdatedBy": "admin", "lastUpdatedAt": "2026-04-14T08:08:08.106Z" }, "primaryEmployeeNumber": "", "srcFusionId": "", "state": 99, "fingerprint": "e2d5b2c49db1e24632ca21d2a0f8a43136f4270093b55cf7394fe8b212cc9114", "ignoreLifecycle": false, "lastLifecycleUpdate": "2026-04-14T08:08:08.104Z", "ignoreFusion": [] } }, "options": { "updateStatus": true, "task": "69ddf5e8fad42e7823306393" }}

unittest/ldapBinTest.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import subprocess
33
import os
44
import json
5-
__PYTHONENV__='/../.venv/sesame-backend-ldap/bin/python'
5+
__PYTHONENV__='/../.venv/bin/python'
66
class ldapBinTest (unittest.TestCase):
77
def run_backend(self,script, file= "",args=""):
88
dir = os.getcwd()
@@ -19,6 +19,7 @@ def run_backend(self,script, file= "",args=""):
1919
#open file to pass to stdin
2020
fic = open(file, "r")
2121
content = fic.read()
22+
content = content.replace("\n", "")
2223
fic.close()
2324
os.chdir('../src/bin')
2425
ret = subprocess.run(execargs,input=content.encode(),capture_output=True)
@@ -129,5 +130,9 @@ def test_20eduperson(self):
129130
self.assertEqual(result["status"], 0)
130131
self.assertEqual(result["message"], "Entree uid=omounier,ou=adm,ou=PERSONNES,dc=lyon,dc=archi,dc=fr mod")
131132

133+
def test_21lifecycle(self):
134+
ret =self.run_backend('lifecycle.py', './files_ad_utils/lifecycle.json')
135+
result = json.loads(ret["stdout"])
136+
132137
if __name__ == '__main__':
133138
unittest.main()

0 commit comments

Comments
 (0)