cmdbsyncer 3.12.6__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.
Files changed (201) hide show
  1. application/__init__.py +354 -0
  2. application/_version.py +8 -0
  3. application/api/__init__.py +92 -0
  4. application/api/objects.py +282 -0
  5. application/api/syncer.py +145 -0
  6. application/api/views.py +44 -0
  7. application/auth/views.py +240 -0
  8. application/buildinfo.txt +1 -0
  9. application/cli.py +31 -0
  10. application/config.py +199 -0
  11. application/docu_links.py +11 -0
  12. application/enterprise.py +55 -0
  13. application/helpers/__init__.py +0 -0
  14. application/helpers/cron.py +46 -0
  15. application/helpers/get_account.py +88 -0
  16. application/helpers/inventory.py +95 -0
  17. application/helpers/plugins.py +132 -0
  18. application/helpers/sates.py +26 -0
  19. application/helpers/sql.py +36 -0
  20. application/helpers/syncer_jinja.py +181 -0
  21. application/helpers/tablib_formater.py +16 -0
  22. application/models/__init__.py +0 -0
  23. application/models/account.py +164 -0
  24. application/models/config.py +11 -0
  25. application/models/cron.py +101 -0
  26. application/models/forms.py +59 -0
  27. application/models/host.py +593 -0
  28. application/models/states.py +14 -0
  29. application/models/user.py +142 -0
  30. application/modules/__init__.py +0 -0
  31. application/modules/custom_attributes/models.py +27 -0
  32. application/modules/custom_attributes/rules.py +35 -0
  33. application/modules/custom_attributes/views.py +37 -0
  34. application/modules/debug.py +100 -0
  35. application/modules/email.py +52 -0
  36. application/modules/log/log.py +65 -0
  37. application/modules/log/models.py +33 -0
  38. application/modules/log/views.py +80 -0
  39. application/modules/plugin.py +424 -0
  40. application/modules/rule/__init__.py +0 -0
  41. application/modules/rule/filter.py +45 -0
  42. application/modules/rule/match.py +110 -0
  43. application/modules/rule/models.py +214 -0
  44. application/modules/rule/rewrite.py +102 -0
  45. application/modules/rule/rule.py +328 -0
  46. application/modules/rule/views.py +727 -0
  47. application/modules/statefile.py +99 -0
  48. application/plugins/__init__.py +4 -0
  49. application/plugins/ansible/__init__.py +260 -0
  50. application/plugins/ansible/admin_views.py +41 -0
  51. application/plugins/ansible/inventory.py +137 -0
  52. application/plugins/ansible/models.py +75 -0
  53. application/plugins/ansible/rest_api/ansible.py +38 -0
  54. application/plugins/ansible/rules.py +24 -0
  55. application/plugins/ansible/site_syncer.py +82 -0
  56. application/plugins/ansible/views.py +20 -0
  57. application/plugins/bmc_remedy/__init__.py +21 -0
  58. application/plugins/bmc_remedy/bmc_remedy.py +95 -0
  59. application/plugins/checkmk/__init__.py +578 -0
  60. application/plugins/checkmk/admin_views.py +204 -0
  61. application/plugins/checkmk/bi.py +170 -0
  62. application/plugins/checkmk/cmk2.py +181 -0
  63. application/plugins/checkmk/cmk_rules.py +422 -0
  64. application/plugins/checkmk/dcd.py +181 -0
  65. application/plugins/checkmk/downtimes.py +323 -0
  66. application/plugins/checkmk/groups.py +296 -0
  67. application/plugins/checkmk/helpers.py +34 -0
  68. application/plugins/checkmk/import_v1.py +69 -0
  69. application/plugins/checkmk/import_v2.py +64 -0
  70. application/plugins/checkmk/inits.py +443 -0
  71. application/plugins/checkmk/inventorize.py +317 -0
  72. application/plugins/checkmk/models.py +716 -0
  73. application/plugins/checkmk/passwords.py +91 -0
  74. application/plugins/checkmk/poolfolder.py +38 -0
  75. application/plugins/checkmk/rules.py +330 -0
  76. application/plugins/checkmk/sites.py +39 -0
  77. application/plugins/checkmk/syncer.py +1265 -0
  78. application/plugins/checkmk/tags.py +401 -0
  79. application/plugins/checkmk/tests/__init__.py +3 -0
  80. application/plugins/checkmk/tests/test_bi.py +122 -0
  81. application/plugins/checkmk/tests/test_cmk2.py +262 -0
  82. application/plugins/checkmk/tests/test_cmk_rules.py +238 -0
  83. application/plugins/checkmk/tests/test_dcd.py +207 -0
  84. application/plugins/checkmk/tests/test_downtimes.py +148 -0
  85. application/plugins/checkmk/tests/test_groups.py +159 -0
  86. application/plugins/checkmk/tests/test_helpers.py +108 -0
  87. application/plugins/checkmk/tests/test_import_v1.py +93 -0
  88. application/plugins/checkmk/tests/test_import_v2.py +154 -0
  89. application/plugins/checkmk/tests/test_inventorize.py +148 -0
  90. application/plugins/checkmk/tests/test_passwords.py +118 -0
  91. application/plugins/checkmk/tests/test_poolfolder.py +114 -0
  92. application/plugins/checkmk/tests/test_rules.py +244 -0
  93. application/plugins/checkmk/tests/test_sites.py +66 -0
  94. application/plugins/checkmk/tests/test_syncer.py +1060 -0
  95. application/plugins/checkmk/tests/test_tags.py +240 -0
  96. application/plugins/checkmk/tests/test_users.py +190 -0
  97. application/plugins/checkmk/users.py +105 -0
  98. application/plugins/checkmk/views.py +1095 -0
  99. application/plugins/cisco_dna/__init__.py +67 -0
  100. application/plugins/cisco_dna/syncer.py +227 -0
  101. application/plugins/cron.py +198 -0
  102. application/plugins/csv/__init__.py +156 -0
  103. application/plugins/csv/csv.py +95 -0
  104. application/plugins/file_requests.py +39 -0
  105. application/plugins/idoit/__init__.py +143 -0
  106. application/plugins/idoit/admin_views.py +17 -0
  107. application/plugins/idoit/models.py +69 -0
  108. application/plugins/idoit/rules.py +43 -0
  109. application/plugins/idoit/syncer.py +320 -0
  110. application/plugins/idoit/views.py +51 -0
  111. application/plugins/jdisc/__init__.py +153 -0
  112. application/plugins/jdisc/applications.py +56 -0
  113. application/plugins/jdisc/devices.py +169 -0
  114. application/plugins/jdisc/executables.py +52 -0
  115. application/plugins/jdisc/jdisc.py +114 -0
  116. application/plugins/jira/__init__.py +19 -0
  117. application/plugins/jira/jira.py +74 -0
  118. application/plugins/jira_cloud/__init__.py +25 -0
  119. application/plugins/jira_cloud/jira_cloud.py +109 -0
  120. application/plugins/json/__init__.py +21 -0
  121. application/plugins/json/json.py +12 -0
  122. application/plugins/ldap/__init__.py +38 -0
  123. application/plugins/ldap/ldap.py +123 -0
  124. application/plugins/maintenance/__init__.py +365 -0
  125. application/plugins/mssql/__init__.py +53 -0
  126. application/plugins/mysql/__init__.py +33 -0
  127. application/plugins/mysql/mysql.py +93 -0
  128. application/plugins/netbox/__init__.py +530 -0
  129. application/plugins/netbox/admin_views.py +64 -0
  130. application/plugins/netbox/cluster.py +41 -0
  131. application/plugins/netbox/contacts.py +30 -0
  132. application/plugins/netbox/dataflow.py +179 -0
  133. application/plugins/netbox/devices.py +192 -0
  134. application/plugins/netbox/interfaces.py +124 -0
  135. application/plugins/netbox/ips.py +112 -0
  136. application/plugins/netbox/models.py +486 -0
  137. application/plugins/netbox/netbox.py +389 -0
  138. application/plugins/netbox/networks.py +22 -0
  139. application/plugins/netbox/prefixes.py +22 -0
  140. application/plugins/netbox/rules.py +441 -0
  141. application/plugins/netbox/views.py +202 -0
  142. application/plugins/netbox/virtualmachines.py +189 -0
  143. application/plugins/prtg/__init__.py +42 -0
  144. application/plugins/prtg/prtg.py +168 -0
  145. application/plugins/pyodbc/__init__.py +52 -0
  146. application/plugins/pyodbc/pyodbc.py +97 -0
  147. application/plugins/rest/__init__.py +31 -0
  148. application/plugins/rest/rest.py +131 -0
  149. application/plugins/rules/__init__.py +56 -0
  150. application/plugins/rules/admin_views.py +16 -0
  151. application/plugins/rules/autorules.py +170 -0
  152. application/plugins/rules/models.py +23 -0
  153. application/plugins/rules/rule_definitions.py +44 -0
  154. application/plugins/rules/rule_import_export.py +142 -0
  155. application/plugins/rules/views.py +76 -0
  156. application/plugins/syncer_shell.py +245 -0
  157. application/plugins/vmware/__init__.py +83 -0
  158. application/plugins/vmware/admin_views.py +18 -0
  159. application/plugins/vmware/custom_attributes.py +170 -0
  160. application/plugins/vmware/models.py +64 -0
  161. application/plugins/vmware/rules.py +29 -0
  162. application/plugins/vmware/views.py +15 -0
  163. application/plugins/vmware/vmware.py +38 -0
  164. application/plugins/yml/__init__.py +55 -0
  165. application/plugins/yml/yml.py +167 -0
  166. application/plugins_cli.py +222 -0
  167. application/static/js/main.js +3 -0
  168. application/static/logo_white.png +0 -0
  169. application/templates/admin/file/list.html +197 -0
  170. application/templates/admin/index.html +264 -0
  171. application/templates/admin/master.html +466 -0
  172. application/templates/admin/set_cmk_version_form.html +14 -0
  173. application/templates/admin/set_template_form.html +21 -0
  174. application/templates/base.html +202 -0
  175. application/templates/debug.html +100 -0
  176. application/templates/debug_host.html +168 -0
  177. application/templates/email/newuser.html +10 -0
  178. application/templates/email/resetpassword.html +9 -0
  179. application/templates/formular.html +10 -0
  180. application/templates/license_info.html +81 -0
  181. application/templates/login.html +22 -0
  182. application/templates/set_2fa.html +63 -0
  183. application/views/account.py +238 -0
  184. application/views/config.py +29 -0
  185. application/views/cron.py +137 -0
  186. application/views/default.py +210 -0
  187. application/views/fileadmin.py +28 -0
  188. application/views/host.py +1648 -0
  189. application/views/license.py +39 -0
  190. application/views/user.py +55 -0
  191. cmdbsyncer-3.12.6.dist-info/METADATA +316 -0
  192. cmdbsyncer-3.12.6.dist-info/RECORD +201 -0
  193. cmdbsyncer-3.12.6.dist-info/WHEEL +5 -0
  194. cmdbsyncer-3.12.6.dist-info/entry_points.txt +3 -0
  195. cmdbsyncer-3.12.6.dist-info/licenses/LICENSE +21 -0
  196. cmdbsyncer-3.12.6.dist-info/top_level.txt +2 -0
  197. syncerapi/__init__.py +0 -0
  198. syncerapi/v1/__init__.py +9 -0
  199. syncerapi/v1/core/__init__.py +7 -0
  200. syncerapi/v1/inventory/__init__.py +1 -0
  201. syncerapi/v1/rest/__init__.py +2 -0
@@ -0,0 +1,354 @@
1
+ """ Main entry """
2
+ # Flask app factory with intentional deferred imports to avoid circular imports.
3
+ # pylint: disable=wrong-import-position,import-outside-toplevel,ungrouped-imports,line-too-long,wildcard-import,unused-wildcard-import,cyclic-import
4
+ import os
5
+ import sys
6
+ import logging
7
+ import importlib
8
+ import pkgutil
9
+ import warnings
10
+ from logging import config as log_config
11
+ from tablib.formats import registry as tablib_registry
12
+ import mongoengine
13
+ from sortedcontainers import SortedDict
14
+ from flask import Flask, url_for, redirect
15
+ from flask_admin import Admin
16
+ from flask_admin.menu import MenuLink
17
+ from flask_login import LoginManager
18
+ from flask_mail import Mail
19
+ from flask_bootstrap import Bootstrap
20
+ from flask_mongoengine import MongoEngine
21
+ from flask_wtf.csrf import CSRFProtect
22
+
23
+ from application.helpers.tablib_formater import ExportObjects
24
+
25
+ warnings.filterwarnings('ignore', category=UserWarning)
26
+
27
+ tablib_registry.register('syncer_rules', ExportObjects())
28
+
29
+
30
+ def _read_version_from_changelog():
31
+ """
32
+ Resolve the current version from the newest changelog/v*.md file by
33
+ reading its first `## Version x.y.z` header. Used as the dev-mode source
34
+ so VERSION tracks the changelog without waiting for `make sync-version`.
35
+ """
36
+ import glob as _glob
37
+ import re as _re
38
+ changelog_dir = os.path.join(os.path.dirname(__file__), "..", "changelog")
39
+ files = _glob.glob(os.path.join(changelog_dir, "v*.md"))
40
+
41
+ def _key(path):
42
+ m = _re.search(r"v(\d+)\.(\d+)\.md$", path)
43
+ return (int(m.group(1)), int(m.group(2))) if m else (0, 0)
44
+
45
+ for path in sorted(files, key=_key, reverse=True):
46
+ with open(path, encoding="utf-8") as fh:
47
+ for changelog_line in fh:
48
+ m = _re.match(r"^## Version (\d+\.\d+\.\d+)\s*$", changelog_line)
49
+ if m:
50
+ return m.group(1)
51
+ return None
52
+
53
+
54
+ def _resolve_version():
55
+ # In a source checkout the changelog directory is present and authoritative
56
+ # so edits become visible without running `make sync-version`. In an
57
+ # installed wheel the changelog is gone and `_version.py` is the single
58
+ # source of truth (written at build time and matched by pyproject.toml).
59
+ changelog_dir = os.path.join(os.path.dirname(__file__), "..", "changelog")
60
+ if os.path.isdir(changelog_dir):
61
+ from_changelog = _read_version_from_changelog()
62
+ if from_changelog:
63
+ return from_changelog
64
+ from application._version import __version__
65
+ return __version__
66
+
67
+
68
+ VERSION = _resolve_version()
69
+
70
+ CONFIG_MAP = {
71
+ 'prod': 'application.config.ProductionConfig',
72
+ 'compose': 'application.config.ComposeConfig',
73
+ 'base': 'application.config.BaseConfig',
74
+ }
75
+
76
+ app = Flask(__name__)
77
+ config_name = os.environ.get('config', 'base').lower()
78
+ app.config.from_object(CONFIG_MAP.get(config_name, CONFIG_MAP['base']))
79
+ if config_name == "base":
80
+ app.jinja_env.auto_reload = True
81
+ csrf = CSRFProtect(app)
82
+
83
+
84
+ ## Read Build Data
85
+
86
+ # buildinfo.txt lives inside the package so it ships with the wheel; the
87
+ # pre-commit hook rewrites it on every commit so dev runs also see a fresh
88
+ # timestamp. Missing file (e.g. running from a raw checkout without the
89
+ # hook installed) is not fatal — we just expose an 'unknown' build date.
90
+ _buildinfo_path = os.path.join(app.root_path, "buildinfo.txt")
91
+ if os.path.isfile(_buildinfo_path):
92
+ with open(_buildinfo_path, encoding="utf-8") as _buildinfo:
93
+ for line in _buildinfo:
94
+ name, key = line.split('=')
95
+ app.config[name] = key.strip()
96
+ else:
97
+ app.config.setdefault("BUILD_DATE", "unknown")
98
+
99
+ log_config.dictConfig(app.config['LOGGING'])
100
+ logger = logging.getLogger('debug')
101
+
102
+
103
+ try:
104
+ from local_config import config
105
+ app.config.update(config)
106
+ except ModuleNotFoundError:
107
+ pass
108
+
109
+
110
+ if '--debug' in sys.argv:
111
+ logger.setLevel(logging.DEBUG)
112
+
113
+ if app.config['DEBUG']:
114
+ logger.info('Loaded Debug Mode')
115
+
116
+ ## Sentry
117
+ if app.config['SENTRY_ENABLED']:
118
+ import sentry_sdk
119
+ from sentry_sdk.integrations.flask import FlaskIntegration
120
+
121
+ def filter_events(event, _hint):
122
+ """
123
+ Filter a list of Exception from sending to sentry
124
+ """
125
+ excp = event.get('exception', {})
126
+ values = excp.get('values', [])
127
+ if values:
128
+ excp_type = values[0]['type']
129
+ if excp_type in [
130
+ 'timeout',
131
+ ]:
132
+ return None
133
+ return event
134
+
135
+ sentry_sdk.init(
136
+ dsn=app.config['SENTRY_DSN'],
137
+ before_send=filter_events,
138
+ integrations=[FlaskIntegration(),
139
+ ],
140
+ release=VERSION
141
+ )
142
+
143
+ try:
144
+ db = MongoEngine()
145
+ from uwsgidecorators import postfork
146
+
147
+ @postfork
148
+ def setup_db():
149
+ """db init in uwsgi"""
150
+ db.init_app(app)
151
+
152
+ except ImportError:
153
+ #print(" \033[91mWARNING: STANDALONE MODE - NOT FOR PROD\033[0m")
154
+ #print(" * HINT: uwsgi modul not loaded")
155
+ # Output makes problems for commands
156
+ db = MongoEngine(app)
157
+
158
+ def init_db():
159
+ """DB Init for Multiprocessing Pool"""
160
+ mongoengine.disconnect()
161
+ with app.app_context():
162
+ MongoEngine(app)
163
+
164
+
165
+ from application.helpers.sates import get_changes
166
+
167
+
168
+ @app.before_request
169
+ def load_before_request():
170
+ """
171
+ Helper to have up to date data for each request
172
+ """
173
+ app.config['CHANGES'] = get_changes()
174
+
175
+ # We need the db in the Module
176
+ from application.modules.log.log import Log
177
+
178
+
179
+ log = Log()
180
+
181
+ mail = Mail(app)
182
+ bootstrap = Bootstrap(app)
183
+
184
+ login_manager = LoginManager()
185
+ login_manager.init_app(app)
186
+ login_manager.login_view = 'auth.login'
187
+ login_manager.login_message = False
188
+
189
+
190
+
191
+ cron_register = SortedDict()
192
+ plugin_register = []
193
+
194
+
195
+ from application.views.default import IndexView, DefaultModelView
196
+
197
+ from application.auth.views import AUTH
198
+ app.register_blueprint(AUTH)
199
+
200
+ from application.modules.rule.views import FiltereModelView, RewriteAttributeView
201
+
202
+ @app.route('/')
203
+ def page_redirect():
204
+ """
205
+ Redirect to admin Panel
206
+ """
207
+ return redirect(url_for("admin.index"))
208
+
209
+ def _register_all_plugin_admin_views():
210
+ from application.helpers.plugins import is_plugin_disabled
211
+ import application.plugins as plugins_package
212
+ import plugins as external_plugins_package
213
+
214
+ plugin_modules = []
215
+
216
+ for _, module_name, _ in pkgutil.iter_modules(
217
+ external_plugins_package.__path__, external_plugins_package.__name__ + "."
218
+ ):
219
+ # module_name is e.g. "plugins.netbox" — extract the short ident
220
+ short_name = module_name.rsplit(".", 1)[-1]
221
+ if is_plugin_disabled(short_name):
222
+ logger.info("Plugin '%s' is disabled, skipping", short_name)
223
+ continue
224
+ plugin_modules.append(module_name)
225
+
226
+ for _, module_name, _ in pkgutil.iter_modules(
227
+ plugins_package.__path__, plugins_package.__name__ + "."
228
+ ):
229
+ short_name = module_name.rsplit(".", 1)[-1]
230
+ if is_plugin_disabled(short_name):
231
+ logger.info("Plugin '%s' is disabled, skipping", short_name)
232
+ continue
233
+ plugin_modules.append(module_name)
234
+
235
+ for module_name in plugin_modules:
236
+ admin_module_name = f"{module_name}.admin_views"
237
+ try:
238
+ admin_module = importlib.import_module(admin_module_name)
239
+ except ModuleNotFoundError as exc:
240
+ if exc.name == admin_module_name:
241
+ continue
242
+ raise
243
+ except Exception: # pylint: disable=broad-exception-caught
244
+ logger.exception(
245
+ "Failed to register admin views for plugin %s", module_name
246
+ )
247
+ if '--debug' in sys.argv:
248
+ raise
249
+ continue
250
+
251
+ register = getattr(admin_module, "register_admin_views", None)
252
+ if callable(register):
253
+ register(admin)
254
+
255
+
256
+ from application.api.views import API_BP as api
257
+ app.register_blueprint(api, url_prefix="/api/v1")
258
+ csrf.exempt(api)
259
+
260
+ admin = Admin(app, name=f"cmdbsyncer {VERSION}",
261
+ index_view=IndexView(),
262
+ category_icon_classes={
263
+ 'Accounts': 'fa fa-users',
264
+ 'Ansible': 'fa fa-cogs',
265
+ 'Checkmk': 'fa fa-heartbeat',
266
+ 'Checkmk Server': 'fa fa-building',
267
+ 'Cronjobs': 'fa fa-clock-o',
268
+ 'i-doit': 'fa fa-sitemap',
269
+ 'Manage Business Intelligence': 'fa fa-sitemap',
270
+ 'Modules': 'fa fa-puzzle-piece',
271
+ 'Netbox': 'fa fa-database',
272
+ 'Plugin: Dataflow': 'fa fa-arrows',
273
+ 'Profile': 'fa fa-user-cog',
274
+ 'Syncer Rules': 'fa fa-bolt',
275
+ 'VMware': 'fa fa-server'
276
+ })
277
+
278
+
279
+ # .-- Host
280
+ from application.models.host import Host
281
+ from application.views.host import HostModelView, ObjectModelView, TemplateModelView
282
+ admin.add_view(HostModelView(Host, name="Hosts", menu_icon_type='fa', menu_icon_value='fa-server'))
283
+ admin.add_category(name="Objects", icon_type='fa', icon_value='fa-folder-open')
284
+ admin.add_view(ObjectModelView(Host, name="All Objects", endpoint="Objects",category="Objects", menu_icon_type='fa', menu_icon_value='fa-cubes'))
285
+ admin.add_view(TemplateModelView(Host, name="Templates", endpoint="Objects Templates",category="Objects", menu_icon_type='fa', menu_icon_value='fa-files-o'))
286
+ #.
287
+ # .-- Global
288
+ from application.modules.custom_attributes.models import CustomAttributeRule
289
+ from application.modules.custom_attributes.views import CustomAttributeView
290
+ admin.add_view(CustomAttributeView(CustomAttributeRule, name="Global Custom Attributes", category="Modules", menu_icon_type='fa', menu_icon_value='fa-cog'))
291
+ #.
292
+
293
+ _register_all_plugin_admin_views()
294
+
295
+
296
+ from application.models.account import Account
297
+ from application.views.account import AccountModelView, ChildAccountModelView
298
+ admin.add_category(name="Accounts", icon_type='fa', icon_value='fa-users')
299
+ admin.add_view(AccountModelView(Account, name="Accounts", category="Accounts", menu_icon_type='fa', menu_icon_value='fa-user-circle'))
300
+ admin.add_view(ChildAccountModelView(Account, name="Config Childs", endpoint='account_childs', category="Accounts", menu_icon_type='fa', menu_icon_value='fa-users'))
301
+
302
+ from application.models.cron import CronGroup, CronStats
303
+ from application.views.cron import CronStatsView, CronGroupView
304
+ admin.add_view(CronGroupView(CronGroup, name="Cronjob Group", category="Cronjobs", menu_icon_type='fa', menu_icon_value='fa-calendar'))
305
+ admin.add_view(CronStatsView(CronStats, name="State Table", category="Cronjobs", menu_icon_type='fa', menu_icon_value='fa-table'))
306
+
307
+ from application.views.fileadmin import FileAdminView
308
+ if os.path.exists(app.config['FILEADMIN_PATH']):
309
+ file_admin_view = FileAdminView(app.config['FILEADMIN_PATH'], name="Filemanager", menu_icon_type='fa', menu_icon_value='fa-folder-open')
310
+ admin.add_view(file_admin_view)
311
+ csrf.exempt(file_admin_view.blueprint)
312
+
313
+ from application.modules.log.models import LogEntry
314
+ from application.modules.log.views import LogView
315
+ admin.add_view(LogView(LogEntry, name="Log", menu_icon_type='fa', menu_icon_value='fa-file-text-o'))
316
+
317
+ #.
318
+ # .-- Config
319
+ admin.add_category(name="Profile", icon_type='fa', icon_value='fa-user-cog')
320
+ from application.models.user import User
321
+ from application.views.user import UserView
322
+ admin.add_view(UserView(User, category='Profile', menu_icon_type='fa', menu_icon_value='fa-user'))
323
+
324
+ from application.models.config import Config
325
+ from application.views.config import ConfigModelView
326
+
327
+ admin.add_view(ConfigModelView(Config, name="System Config", category="Profile", menu_icon_type='fa', menu_icon_value='fa-cogs'))
328
+
329
+ from application.views.license import LicenseView
330
+ admin.add_view(LicenseView(name="License", endpoint="license", category="Profile",
331
+ menu_icon_type='fa', menu_icon_value='fa-id-card'))
332
+ #.
333
+
334
+ # .-- Rest
335
+ admin.add_link(MenuLink(name='Change Password', category='Profile',
336
+ url=f"{app.config['BASE_PREFIX']}change-password",
337
+ icon_type='fa', icon_value='fa-key'))
338
+ admin.add_link(MenuLink(name='Set 2FA Code', category='Profile',
339
+ url=f"{app.config['BASE_PREFIX']}set-2fa",
340
+ icon_type='fa', icon_value='fa-shield'))
341
+ admin.add_link(MenuLink(name='Logout', category='Profile',
342
+ url=f"{app.config['BASE_PREFIX']}logout",
343
+ icon_type='fa', icon_value='fa-sign-out'))
344
+
345
+ #.
346
+ admin.add_link(MenuLink(name='Commit Changes',
347
+ url="#activate_changes",
348
+ class_name="toggle_activate_modal btn btn-primary commit-changes-btn",
349
+ icon_type='fa', icon_value='fa-check-circle'))
350
+
351
+
352
+
353
+ from plugins import *
354
+ from application.plugins import *
@@ -0,0 +1,8 @@
1
+ """Single source of truth for the cmdbsyncer version.
2
+
3
+ Regenerated from the newest ``changelog/v*.md`` entry by ``make sync-version``.
4
+ Kept as a standalone module so ``pyproject.toml`` can resolve the version via
5
+ ``[tool.setuptools.dynamic]`` without importing the Flask application.
6
+ """
7
+
8
+ __version__ = "3.12.6"
@@ -0,0 +1,92 @@
1
+ """
2
+ API
3
+ """
4
+ from functools import wraps
5
+ from flask import abort, request, current_app
6
+ from mongoengine.errors import DoesNotExist
7
+ from application.models.account import Account
8
+ from application.models.user import User
9
+ from application import log
10
+
11
+
12
+ def _is_secure_api_request():
13
+ if current_app.config.get("ALLOW_INSECURE_API_AUTH"):
14
+ return True
15
+ if request.is_secure:
16
+ return True
17
+ if request.headers.get("X-Forwarded-Proto", "").lower() == "https":
18
+ return True
19
+ if request.host.split(":", 1)[0].lower() == "localhost":
20
+ return True
21
+ return request.remote_addr in {"127.0.0.1", "::1"}
22
+
23
+
24
+ def _extract_login_credentials():
25
+ auth = request.authorization
26
+ if auth and auth.username and auth.password is not None:
27
+ return auth.username, auth.password
28
+
29
+ login_user = request.headers.get('x-login-user')
30
+ if login_user:
31
+ if ':' not in login_user:
32
+ abort(401, "Invalid login")
33
+ return login_user.split(':', 1)
34
+ return None, None
35
+
36
+ def _abort_unauthorized(reason="Unauthorized"):
37
+ details = [
38
+ ('reason', reason),
39
+ ('user', f"{request.authorization.username if request.authorization else 'unknown'}"),
40
+ ('ip', request.remote_addr),
41
+ ]
42
+ log.log("API Login failed",
43
+ details=details,
44
+ source="API")
45
+ abort(401, "unauthorized")
46
+
47
+
48
+ def require_token(fn):
49
+ """
50
+ Decorator for Endpoints with token
51
+ """
52
+ @wraps(fn)
53
+ def decorated_view(*args, **kwargs):
54
+ username, user_password = _extract_login_credentials()
55
+ if username:
56
+ if not _is_secure_api_request():
57
+ details = [
58
+ ('reason', 'HTTPS required'),
59
+ ('user', username),
60
+ ('ip', request.remote_addr),
61
+ ]
62
+ log.log("API Login failed", details=details, source="API")
63
+ abort(401, "HTTPS is required for password-based API authentication")
64
+ try:
65
+ user_result = User.objects.get(
66
+ disabled__ne=True,
67
+ __raw__={'$or': [{'name': username}, {'email': username}]}
68
+ )
69
+ roles = user_result.api_roles
70
+ current_path = request.path.replace('/api/v1/','')
71
+ if roles:
72
+ allowed = False
73
+ for role in roles:
74
+ if role == 'all':
75
+ allowed = True
76
+ break
77
+ if current_path.startswith(role):
78
+ allowed = True
79
+ if not allowed:
80
+ _abort_unauthorized(f"User '{username}' not allowed for path '{current_path}'")
81
+ except DoesNotExist:
82
+ _abort_unauthorized("Invalid credentials")
83
+ if not user_result.check_password(user_password):
84
+ _abort_unauthorized("Invalid credentials")
85
+ elif request.headers.get('x-login-token'):
86
+ _abort_unauthorized("Invalid or removed login token")
87
+ else:
88
+ _abort_unauthorized("No credentials provided")
89
+
90
+ return fn(*args, **kwargs)
91
+
92
+ return decorated_view