11from json import load as json_load
2+ import json
23import logging
34from pathlib import Path
4- from typing import TypedDict , Optional
5+ import re
6+ from typing import Tuple , TypedDict , Optional
57
68from helpermodules .subdata import SubData
79from helpermodules .utils .run_command import run_command
@@ -83,6 +85,7 @@ def remove_acl_role(role_template: str, id: int):
8385
8486
8587def check_roles_at_start ():
88+ update_acls ()
8689 flag_path = Path (Path (__file__ ).resolve ().parents [2 ]/ "ramdisk" / "init_user_management" )
8790 if flag_path .is_file ():
8891 with open (flag_path , "r" ) as file :
@@ -104,3 +107,140 @@ def check_roles_at_start():
104107 if "io" in key :
105108 add_acl_role ("io-device-<id>-access" , value .config .id )
106109 flag_path .unlink ()
110+
111+
112+ def _extract_id_from_role_name (role_name : str ) -> Optional [int ]:
113+ numbers = re .findall (r'\d+' , role_name )
114+ if numbers :
115+ return int (numbers [0 ])
116+ return None
117+
118+
119+ def _compare_acl (template_acl : MosquittoAcl , configured_acl : MosquittoAcl ) -> bool :
120+ if template_acl ["acltype" ] == configured_acl ["acltype" ]:
121+ if re .sub (r'/\d+/' , '/<id>/' , template_acl ["topic" ]) == re .sub (r'/\d+/' , '/<id>/' , configured_acl ["topic" ]):
122+ if template_acl ["allow" ] == configured_acl ["allow" ]:
123+ if template_acl ["priority" ] == configured_acl ["priority" ]:
124+ return True
125+ return False
126+
127+
128+ def _get_configured_role_data (role_name : str ) -> Optional [MosquittoRole ]:
129+ role_output = run_command ([
130+ "mosquitto_ctrl" , "dynsec" , "getRole" , role_name ])
131+ # Parse the text output since it's not JSON
132+ role_data = {"rolename" : role_name , "acls" : []}
133+ lines = role_output .strip ().split ('\n ' )
134+ for line in lines [1 :]: # Skip first line with rolename
135+ if "ACLs:" in line :
136+ line = line .replace ("ACLs:" , "" )
137+ if line .strip () and ':' in line :
138+ parts = [p .strip () for p in line .split (':' )]
139+ if len (parts ) >= 4 :
140+ acl = {
141+ "acltype" : parts [0 ],
142+ "allow" : parts [1 ] == "allow" ,
143+ "topic" : parts [2 ].split ('(' )[0 ].strip (),
144+ "priority" : int (parts [3 ].replace (')' , '' ).strip ())
145+ }
146+ role_data ["acls" ].append (acl )
147+ return role_data
148+
149+
150+ def update_acls ():
151+ def is_version_updated () -> Tuple [str , str ]:
152+ for role in roles :
153+ if "openwb-version" in role :
154+ current_version = role .split (":" )[1 ]
155+ break
156+ else :
157+ raise RuntimeError ("openwb-version role not found" )
158+
159+ for role in dynsec_config ["roles" ]:
160+ try :
161+ if "openwb-version" in role ["rolename" ]:
162+ template_version = role ["rolename" ].split (":" )[1 ]
163+ break
164+ except Exception :
165+ continue
166+ else :
167+ raise RuntimeError ("openwb-version role not found in default-dynamic-security.json" )
168+
169+ if current_version != template_version :
170+ log .debug (f"Updating ACLs from version { current_version } to { template_version } " )
171+ return current_version , template_version
172+
173+ def get_template_role_data () -> Optional [MosquittoRole ]:
174+ template_role_data = None
175+ for config_role in dynsec_config ["roles" ]:
176+ if (config_role ["rolename" ] == role_data ["rolename" ] or
177+ ("openwb-version" in config_role ["rolename" ] and "openwb-version" in role_data ["rolename" ])):
178+ template_role_data = config_role
179+ break
180+ else :
181+ for config_role in role_templates_config :
182+ pattern = config_role ["rolename" ].replace ("<id>" , r"\d+" )
183+ if re .match (pattern , role_data ["rolename" ]):
184+ template_role_data = config_role
185+ break
186+ else :
187+ raise RuntimeError (f"Role { role_data ['rolename' ]} not found in default-dynamic-security.json" )
188+ return template_role_data
189+
190+ try :
191+ roles = _list_acl_roles ()
192+ with open (_get_packages_path ()/ "data/config/mosquitto/public/default-dynamic-security.json" , "r" ) as f :
193+ dynsec_config = json .load (f )
194+ current_version , template_version = is_version_updated ()
195+ if current_version != template_version :
196+ with open (_get_packages_path ()/ "data/config/mosquitto/public/role-templates.json" , "r" ) as f :
197+ role_templates_config = json .load (f )
198+ for role_name in roles :
199+ try :
200+ role_data = _get_configured_role_data (role_name )
201+ template_role_data = get_template_role_data ()
202+ if template_role_data :
203+ # vegleiche die zwei ACL dicts, welche ACLs hinzugefügt, geändert oder entfernt wurden
204+ for acl in role_data ["acls" ]:
205+ for template_acl in template_role_data ["acls" ]:
206+ if _compare_acl (template_acl , acl ):
207+ break
208+ else :
209+ log .debug (f"ACL { acl ['acltype' ]} :{ 'allow' if acl ['allow' ] else 'deny' } :{ acl ['topic' ]} :"
210+ f"{ acl ['priority' ]} in Rolle { role_data ['rolename' ]} wird entfernt." )
211+ run_command ([
212+ "mosquitto_ctrl" , "dynsec" , "removeRoleAcl" , role_data ["rolename" ],
213+ acl ["acltype" ], acl ["topic" ]
214+ ])
215+ for acl in template_role_data ["acls" ]:
216+ for role_acl in role_data ["acls" ]:
217+ if _compare_acl (acl , role_acl ):
218+ break
219+ else :
220+ rolename_id = _extract_id_from_role_name (role_data ["rolename" ])
221+ if rolename_id is not None :
222+ acl ["topic" ] = acl ["topic" ].replace ("<id>" , str (rolename_id ))
223+ log .debug (f"ACL { acl ['acltype' ]} :{ 'allow' if acl ['allow' ] else 'deny' } :{ acl ['topic' ]} :"
224+ f"{ acl ['priority' ]} in Rolle { role_data ['rolename' ]} wird hinzugefügt." )
225+ run_command ([
226+ "mosquitto_ctrl" , "dynsec" , "addRoleAcl" , role_data ["rolename" ],
227+ acl ["acltype" ], acl ["topic" ],
228+ "allow" if acl ["allow" ] else "deny" ,
229+ str (acl ["priority" ])
230+ ])
231+ elif template_role_data is None :
232+ log .debug (f"Rolle '{ role_data ['rolename' ]} ' existiert nicht in den Konfigurationsdateien und"
233+ " wird gelöscht." )
234+ run_command (["mosquitto_ctrl" , "dynsec" , "deleteRole" , role_data ["rolename" ]])
235+ except Exception :
236+ log .exception (f"Fehler beim Aktualisieren der Rolle '{ role_name } '" )
237+ # bei allen anderen Rollen dürfen nur die ACLs editiert werden,
238+ # damit diese in den Benutzergruppen erhalten bleiben
239+ run_command (["mosquitto_ctrl" , "dynsec" , "deleteRole" , f"openwb-version:{ current_version } " ])
240+ run_command (["mosquitto_ctrl" , "dynsec" , "createRole" , f"openwb-version:{ template_version } " ])
241+ except Exception :
242+ log .exception ("Fehler beim Aktualisieren der ACLs" )
243+
244+
245+ def _get_packages_path () -> Path :
246+ return Path (__file__ ).resolve ().parents [2 ]
0 commit comments