secufusion-mcp 1.0.12 → 1.0.13
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.
- package/.secufusion-project-spec.json +850 -0
- package/AGENTS.md +284 -0
- package/index.js +31 -0
- package/package.json +4 -2
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"generated_at": "2026-09-02T00:00:00Z",
|
|
4
|
+
"generated_from": "codebase analysis",
|
|
5
|
+
"workspace_root": "c:\\Users\\Yash\\Desktop\\secufi_full"
|
|
6
|
+
},
|
|
7
|
+
|
|
8
|
+
"project": {
|
|
9
|
+
"name": "SecuFusion",
|
|
10
|
+
"devops_org": "secufusion",
|
|
11
|
+
"devops_project": "SFCloud-MSSP",
|
|
12
|
+
"devops_base_url": "https://dev.azure.com/secufusion/SFCloud-MSSP",
|
|
13
|
+
"branch_naming_convention": "Mixed, not strictly enforced. Common patterns: feature/<kebab-description>, fix/<desc> or bugfix/<ticket-or-desc>, ticket-only (TASK-####, TASK-####-<desc>, BUG-####-<desc>), release-<major>.<minor>.<patch> (dash, not slash) for release branches. Trunk branches are 'main' and 'develop' in every repo, but which one is checked out as the working default varies per repo (sfn-events-api and sfn-web-ui default to develop; snf-browser-extn defaults to main). Many ad hoc/free-text branch names also exist.",
|
|
14
|
+
"commit_message_pattern": "No single enforced convention. Recurring mixture: (1) Azure Repos auto-generated 'Merged PR <number>: <title>' on PR completion (dominant pattern in all 3 sampled repos), (2) ticket-prefixed 'TASK-<number>: <desc>' or 'TASK-<number>-<desc>' (common in sfn-events-api), (3) conventional-commit style 'feat(<scope>): ...' / 'fix(<scope>): ...' (used consistently within recent 'ai-ops' feature work in sfn-web-ui, not elsewhere), (4) plain imperative descriptive messages with no prefix at all. Work items referenced via https://dev.azure.com/secufusion/SFCloud-MSSP/_workitems/edit/<id>."
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
"microservices": {
|
|
18
|
+
"sfn-auth-api": {
|
|
19
|
+
"artifact_id": "sfn-auth-api",
|
|
20
|
+
"port": "CONFLICT: 8081 (hardcoded literal in application.properties, confirmed by direct read) vs 8087 (AUTH_SERVICE_PORT in shared deployment env config supplied by user) — the repo's server.port is NOT wired to ${AUTH_SERVICE_PORT}, so 8087 appears unused by this service as currently checked out; flagged for manual verification",
|
|
21
|
+
"eureka_name": "AUTH-SERVICE",
|
|
22
|
+
"domain": "Legacy/parallel auth & tenant-config resolution (login, tenant-config lookup by host). Heavy functional overlap with sfn-iam-api's AuthController (see manual_review_flags) — possibly superseded/duplicate.",
|
|
23
|
+
"owns_tables": ["Address", "auth_provider_config", "groups", "roles", "scopes", "Tenant", "Users"],
|
|
24
|
+
"exposes_endpoints": [
|
|
25
|
+
"GET /tenant-config/v1 -> AuthController.getTenantConfig() [host param, referer-validated]",
|
|
26
|
+
"GET /tenant-config -> AuthController.getTenantConfig() [host param, unvalidated overload]",
|
|
27
|
+
"POST /login -> AuthController.login()"
|
|
28
|
+
],
|
|
29
|
+
"calls_services": [],
|
|
30
|
+
"kafka_produces": [],
|
|
31
|
+
"kafka_consumes": [],
|
|
32
|
+
"keycloak_realm": "not_found (no static realm; JWT validated via per-request NimbusJwtDecoder built from DB-stored AuthProviderConfig; admin-client realm=master for admin ops)",
|
|
33
|
+
"keycloak_client_id": "not_found (tenant.client-id=secufusion property exists; admin-client-id=admin-cli for admin ops)",
|
|
34
|
+
"flyway_next_version": "not_found (no db/migration directory, no Flyway dependency)",
|
|
35
|
+
"test_framework": "JUnit 5 (single contextLoads() smoke test only, no Mockito usage observed)",
|
|
36
|
+
"swagger_url": "not_found (springdoc-openapi-starter-webmvc-ui 2.5.0 present but no custom springdoc.swagger-ui.path configured; default paths under context-path /api/auth apply)",
|
|
37
|
+
"spring_boot_version": "3.4.12",
|
|
38
|
+
"java_version": "17"
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
"sfn-eureka-api": {
|
|
42
|
+
"artifact_id": "sfn-eureka-api",
|
|
43
|
+
"port": 8761,
|
|
44
|
+
"eureka_name": "CONFLICT: 'sfn-eureka-server' (application.properties) vs 'discovery-server' (application.yml) — both files loaded, effective value not verified at runtime",
|
|
45
|
+
"domain": "Eureka service registry (pure discovery server, no business logic)",
|
|
46
|
+
"owns_tables": [],
|
|
47
|
+
"exposes_endpoints": [],
|
|
48
|
+
"calls_services": [],
|
|
49
|
+
"kafka_produces": [],
|
|
50
|
+
"kafka_consumes": [],
|
|
51
|
+
"keycloak_realm": "not_found",
|
|
52
|
+
"keycloak_client_id": "not_found",
|
|
53
|
+
"flyway_next_version": "not_found (n/a, no database)",
|
|
54
|
+
"test_framework": "JUnit 5 (single contextLoads() smoke test only)",
|
|
55
|
+
"swagger_url": "not_found (no springdoc/springfox dependency)",
|
|
56
|
+
"spring_boot_version": "3.2.4",
|
|
57
|
+
"java_version": "17"
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
"sfn-events-api": {
|
|
61
|
+
"artifact_id": "sfn-events-api",
|
|
62
|
+
"port": 8086,
|
|
63
|
+
"eureka_name": "EVENTS-SERVICE",
|
|
64
|
+
"domain": "Security event ingestion, incident management, device/extension inventory, AI-on-endpoints governance, notifications. Largest/most active service (22 controllers).",
|
|
65
|
+
"owns_tables": ["devices", "device_user", "device_credential", "events", "event_inbox", "extension_events", "extension_api_keys", "extension_approval_request", "extension_policy", "installed_extensions", "events_group", "incidents", "incident_activities", "incident_assignees", "incident_events", "incident_playbooks", "incident_playbook_steps", "playbook_templates", "escalation_rules", "machine", "groups", "notifications", "notification_preferences", "smtp_config", "policy_assignments", "tenant_api_mappings", "tenant_extension_client", "tenant_extension_first_seen", "ai_policy", "ai_device_scan", "ai_host_instance", "ai_mcp_server_instance", "ai_local_model", "ai_instruction_file", "ai_extensibility_item", "ai_versioned_record", "browserpolicy", "networkpolicy", "Users", "roles", "auth_provider_config", "Tenant", "api_flags", "downloadable_files", "extension_registry", "extension_version", "extension_risk_components", "permission_risk_classification", "api_key_configuration", "scopes", "tenant_types"],
|
|
66
|
+
"exposes_endpoints": [
|
|
67
|
+
"GET /api/events/reports/ai-usage/summary -> AiUsageReportController.summary()",
|
|
68
|
+
"GET /api/events/reports/ai-usage/by-platform -> AiUsageReportController.byPlatform()",
|
|
69
|
+
"GET /api/events/reports/ai-usage/by-compliance -> AiUsageReportController.byCompliance()",
|
|
70
|
+
"GET /api/events/reports/ai-usage/by-user -> AiUsageReportController.byUser()",
|
|
71
|
+
"GET /api/events/reports/ai-usage/pii/by-user -> AiUsageReportController.getPiiByUser()",
|
|
72
|
+
"GET /api/events/reports/ai-usage/timeseries -> AiUsageReportController.timeseries()",
|
|
73
|
+
"GET /api/events/reports/ai-usage/top-risky -> AiUsageReportController.topRisky()",
|
|
74
|
+
"GET /api/events/reports/ai-usage/data-owner-split -> AiUsageReportController.dataOwnerSplit()",
|
|
75
|
+
"GET /api/events/reports/ai-usage/export -> AiUsageReportController.exportCsv()",
|
|
76
|
+
"GET /api/events/analytics/browser -> BrowserAnalyticsController.getBrowserAnalytics()",
|
|
77
|
+
"GET /api/events/analytics/browser/file-operations -> BrowserAnalyticsController.getFileOperationsStats()",
|
|
78
|
+
"GET /api/events/analytics/browser/trends -> BrowserAnalyticsController.getDailyTrends()",
|
|
79
|
+
"GET /api/events/analytics/browser/domains -> BrowserAnalyticsController.getTopDomains()",
|
|
80
|
+
"POST /api/events/devices/register -> DeviceController.registerDevice()",
|
|
81
|
+
"GET /api/events/devices -> DeviceController.getDevices()",
|
|
82
|
+
"GET /api/events/devices/{deviceId} -> DeviceController.getDevice()",
|
|
83
|
+
"PUT /api/events/devices/{deviceId}/status -> DeviceController.updateDeviceStatus()",
|
|
84
|
+
"GET /api/events/devices/{deviceId}/summary -> DeviceController.getDeviceActivitySummary()",
|
|
85
|
+
"GET /api/events/devices/stats -> DeviceController.getDeviceStats()",
|
|
86
|
+
"GET /api/events/devices/recent -> DeviceController.getRecentDevices()",
|
|
87
|
+
"GET /api/events/devices/top-active -> DeviceController.getTopActiveDevices()",
|
|
88
|
+
"GET /api/events/devices/by-type -> DeviceController.getDevicesByType()",
|
|
89
|
+
"GET /api/events/devices/user/{userName} -> DeviceController.getDevicesForUser()",
|
|
90
|
+
"GET /api/events/devices/device-user/{deviceUserId} -> DeviceController.getDevicesForDeviceUser()",
|
|
91
|
+
"DELETE /api/events/devices/{deviceId} -> DeviceController.deactivateDevice()",
|
|
92
|
+
"GET /api/events/devices/{deviceId}/deletion-summary -> DeviceController.getDeviceDeletionSummary()",
|
|
93
|
+
"DELETE /api/events/devices/{deviceId}/purge -> DeviceController.purgeDevice()",
|
|
94
|
+
"GET /api/events/devices/search -> DeviceController.searchDevices()",
|
|
95
|
+
"GET /api/events/devices/{deviceId}/events -> DeviceController.getDeviceEventsHistory()",
|
|
96
|
+
"GET /api/events/device-users -> DeviceUserController.getDeviceUsers()",
|
|
97
|
+
"GET /api/events/device-users/search -> DeviceUserController.searchDeviceUsers()",
|
|
98
|
+
"GET /api/events/device-users/{deviceUserId} -> DeviceUserController.getDeviceUser()",
|
|
99
|
+
"GET /api/events/device-users/{deviceUserId}/comprehensive -> DeviceUserController.getDeviceUserComprehensiveDetails()",
|
|
100
|
+
"GET /api/events/device-users/{deviceUserId}/devices -> DeviceUserController.getDeviceUserDevices()",
|
|
101
|
+
"GET /api/events/device-users/machines -> DeviceUserController.getTenantMachines()",
|
|
102
|
+
"GET /api/events/device-users/{deviceUserId}/machines -> DeviceUserController.getDeviceUserMachines()",
|
|
103
|
+
"GET /api/events/device-users/{deviceUserId}/events -> DeviceUserController.getDeviceUserEvents()",
|
|
104
|
+
"GET /api/events/device-users/stats -> DeviceUserController.getDeviceUserStats()",
|
|
105
|
+
"GET /api/events/device-users/by-email/{email} -> DeviceUserController.getDeviceUserByEmail()",
|
|
106
|
+
"GET /api/events/downloads -> DownloadController.listDownloads()",
|
|
107
|
+
"GET /api/events/downloads/{fileId} -> DownloadController.getDownload()",
|
|
108
|
+
"GET /api/events/downloads/{fileId}/download -> DownloadController.downloadFile()",
|
|
109
|
+
"POST /api/events/downloads/register -> DownloadController.registerExternalFile()",
|
|
110
|
+
"PUT /api/events/downloads/{fileId}/external-url -> DownloadController.updateExternalUrl()",
|
|
111
|
+
"POST /api/events/downloads -> DownloadController.uploadFile() [multipart]",
|
|
112
|
+
"PUT /api/events/downloads/{fileId} -> DownloadController.updateDownload()",
|
|
113
|
+
"PUT /api/events/downloads/{fileId}/replace -> DownloadController.replaceFile() [multipart]",
|
|
114
|
+
"DELETE /api/events/downloads/{fileId} -> DownloadController.deleteDownload()",
|
|
115
|
+
"POST /api/events/escalation-rules -> EscalationController.createRule()",
|
|
116
|
+
"GET /api/events/escalation-rules -> EscalationController.listRules()",
|
|
117
|
+
"PUT /api/events/escalation-rules/{ruleId} -> EscalationController.updateRule()",
|
|
118
|
+
"DELETE /api/events/escalation-rules/{ruleId} -> EscalationController.deactivateRule()",
|
|
119
|
+
"POST /api/events -> EventController.addEvents()",
|
|
120
|
+
"POST /api/events/kafka -> EventController.addEventsToKafka()",
|
|
121
|
+
"POST /api/events/kafka/sync -> EventController.syncTenant()",
|
|
122
|
+
"GET /api/events -> EventController.getUserEvents()",
|
|
123
|
+
"GET /api/events/allevents/pdfexport -> EventController.getPdfExportReport()",
|
|
124
|
+
"GET /api/events/allevents/export -> EventController.exportUserEvents()",
|
|
125
|
+
"GET /api/events/summary -> EventController.getTenantActivitySummary()",
|
|
126
|
+
"GET /api/events/device/{deviceId} -> EventController.getDeviceEvents()",
|
|
127
|
+
"GET /api/events/device/{deviceId}/range -> EventController.getDeviceEventsByRange()",
|
|
128
|
+
"GET /api/events/user/{userName} -> EventController.getEventsByUser()",
|
|
129
|
+
"GET /api/events/all -> EventController.getAllEvents()",
|
|
130
|
+
"GET /api/events/search -> EventController.searchEvents()",
|
|
131
|
+
"GET /api/events/filter -> EventController.filterEvents()",
|
|
132
|
+
"GET /api/events/filter/facets -> EventController.getEventFacets()",
|
|
133
|
+
"GET /api/events/count -> EventController.getEventCount()",
|
|
134
|
+
"GET /api/events/categories -> EventController.getCategories()",
|
|
135
|
+
"GET /api/events/users -> EventController.getEventUsers()",
|
|
136
|
+
"POST /api/events/extension-api-keys -> ExtensionApiKeyManagementController.createApiKey()",
|
|
137
|
+
"GET /api/events/extension-api-keys -> ExtensionApiKeyManagementController.listApiKeys()",
|
|
138
|
+
"GET /api/events/extension-api-keys/id -> ExtensionApiKeyManagementController.getApiKey()",
|
|
139
|
+
"PATCH /api/events/extension-api-keys/{keyId} -> ExtensionApiKeyManagementController.updateApiKey()",
|
|
140
|
+
"POST /api/events/extension-api-keys/{keyId}/revoke -> ExtensionApiKeyManagementController.revokeApiKey()",
|
|
141
|
+
"POST /api/events/extension-api-keys/{keyId}/reactivate -> ExtensionApiKeyManagementController.reactivateApiKey()",
|
|
142
|
+
"DELETE /api/events/extension-api-keys/{keyId} -> ExtensionApiKeyManagementController.deleteApiKey()",
|
|
143
|
+
"POST /api/events/extension-api-keys/{keyId}/rotate -> ExtensionApiKeyManagementController.rotateApiKey()",
|
|
144
|
+
"POST /api/events/extension-api-keys/{keyId}/extend-expiry -> ExtensionApiKeyManagementController.extendExpiry()",
|
|
145
|
+
"GET /api/events/extension-api-keys/{keyId}/rotation-history -> ExtensionApiKeyManagementController.getRotationHistory()",
|
|
146
|
+
"GET /api/events/extension-api-keys/stats -> ExtensionApiKeyManagementController.getStatistics()",
|
|
147
|
+
"GET /api/events/extension-api-keys/settings -> ExtensionApiKeyManagementController.getSettings()",
|
|
148
|
+
"PATCH /api/events/extension-api-keys/settings -> ExtensionApiKeyManagementController.updateSettings()",
|
|
149
|
+
"POST /api/events/public/extension/validate -> ExtensionApiKeyPublicController.validateApiKey() [public]",
|
|
150
|
+
"POST /api/events/public/extension/generate-token -> ExtensionApiKeyPublicController.generateToken() [public]",
|
|
151
|
+
"POST /api/events/public/extension/generate-token-with-steps -> ExtensionApiKeyPublicController.generateTokenWithSteps() [public]",
|
|
152
|
+
"POST /api/events/public/extension/check-expiry -> ExtensionApiKeyPublicController.checkExpiry() [public]",
|
|
153
|
+
"POST /api/events/public/extension/usage-stats -> ExtensionApiKeyPublicController.getUsageStats() [public]",
|
|
154
|
+
"POST /api/events/public/extension/refresh-token -> ExtensionApiKeyPublicController.refreshToken() [public]",
|
|
155
|
+
"GET /api/events/extensions/approval-requests -> ExtensionApprovalController.list()",
|
|
156
|
+
"GET /api/events/extensions/approval-requests/{id} -> ExtensionApprovalController.getOne()",
|
|
157
|
+
"PATCH /api/events/extensions/approval-requests/{id} -> ExtensionApprovalController.decide()",
|
|
158
|
+
"POST /api/events/extensions/sync -> ExtensionController.syncExtensions()",
|
|
159
|
+
"POST /api/events/extensions/static-analysis -> ExtensionController.syncExtensionStaticAnalysis()",
|
|
160
|
+
"POST /api/events/extensions/link-device -> ExtensionController.linkAnonymousDevice()",
|
|
161
|
+
"POST /api/events/extensions/acknowledge-warning -> ExtensionController.acknowledgeWarning()",
|
|
162
|
+
"GET /api/events/extensions/search -> ExtensionController.searchExtensions()",
|
|
163
|
+
"GET /api/events/extensions -> ExtensionController.getAllExtensions()",
|
|
164
|
+
"GET /api/events/extensions/device/{deviceId} -> ExtensionController.getDeviceExtensions()",
|
|
165
|
+
"GET /api/events/extensions/high-risk -> ExtensionController.getHighRiskExtensions()",
|
|
166
|
+
"GET /api/events/extensions/blocked -> ExtensionController.getBlockedExtensions()",
|
|
167
|
+
"GET /api/events/extensions/stats -> ExtensionController.getExtensionStats()",
|
|
168
|
+
"GET /api/events/extensions/events -> ExtensionController.getExtensionEvents()",
|
|
169
|
+
"GET /api/events/extensions/events/device/{deviceId} -> ExtensionController.getDeviceExtensionEvents()",
|
|
170
|
+
"GET /api/events/extensions/dashboard/overview -> ExtensionDashboardController.getOverview()",
|
|
171
|
+
"GET /api/events/extensions/dashboard/inventory -> ExtensionDashboardController.getInventory()",
|
|
172
|
+
"GET /api/events/extensions/dashboard/inventory/{extensionId} -> ExtensionDashboardController.getExtensionDetail()",
|
|
173
|
+
"GET /api/events/extensions/dashboard/trends -> ExtensionDashboardController.getTrends()",
|
|
174
|
+
"GET /api/events/extensions/dashboard/user-risk -> ExtensionDashboardController.getUserRiskProfiles()",
|
|
175
|
+
"GET /api/events/extensions/dashboard/policy-effectiveness -> ExtensionDashboardController.getPolicyEffectiveness()",
|
|
176
|
+
"POST /api/events/public/extension/enroll -> ExtensionEnrollmentPublicController.enroll() [public]",
|
|
177
|
+
"POST /api/events/public/extension/token -> ExtensionEnrollmentPublicController.token() [public]",
|
|
178
|
+
"POST /api/events/incidents -> IncidentController.createIncident()",
|
|
179
|
+
"GET /api/events/incidents -> IncidentController.getIncidents()",
|
|
180
|
+
"GET /api/events/incidents/stats -> IncidentController.getStats()",
|
|
181
|
+
"GET /api/events/incidents/dashboard -> IncidentController.getDashboard()",
|
|
182
|
+
"GET /api/events/incidents/pending-events -> IncidentController.getPendingEventCounts()",
|
|
183
|
+
"POST /api/events/incidents/bulk-dismiss -> IncidentController.bulkDismissOldEvents()",
|
|
184
|
+
"POST /api/events/incidents/bulk-assign -> IncidentController.bulkAssign()",
|
|
185
|
+
"POST /api/events/incidents/bulk-status -> IncidentController.bulkStatusChange()",
|
|
186
|
+
"POST /api/events/incidents/bulk-close -> IncidentController.bulkClose()",
|
|
187
|
+
"POST /api/events/incidents/assignees -> IncidentController.addAssigneeConfig()",
|
|
188
|
+
"POST /api/events/incidents/assignees/bulk-init -> IncidentController.bulkInitAssigneeConfigs()",
|
|
189
|
+
"GET /api/events/incidents/assignees -> IncidentController.listAssigneeConfigs()",
|
|
190
|
+
"PUT /api/events/incidents/assignees/{assigneeId} -> IncidentController.updateAssigneeConfig()",
|
|
191
|
+
"DELETE /api/events/incidents/assignees/{assigneeId} -> IncidentController.removeAssigneeConfig()",
|
|
192
|
+
"POST /api/events/incidents/auto-create -> IncidentController.triggerAutoCreate()",
|
|
193
|
+
"POST /api/events/incidents/{parentId}/merge -> IncidentController.mergeIncidents()",
|
|
194
|
+
"GET /api/events/incidents/{id} -> IncidentController.getIncidentDetail()",
|
|
195
|
+
"PUT /api/events/incidents/{id} -> IncidentController.updateIncident()",
|
|
196
|
+
"PATCH /api/events/incidents/{id}/status -> IncidentController.changeStatus()",
|
|
197
|
+
"PATCH /api/events/incidents/{id}/resolve -> IncidentController.resolveIncident()",
|
|
198
|
+
"PATCH /api/events/incidents/{id}/assign -> IncidentController.assignIncident()",
|
|
199
|
+
"PATCH /api/events/incidents/{id}/priority -> IncidentController.changePriority()",
|
|
200
|
+
"POST /api/events/incidents/{id}/events -> IncidentController.linkEvents()",
|
|
201
|
+
"DELETE /api/events/incidents/{id}/events/{eventId} -> IncidentController.unlinkEvent()",
|
|
202
|
+
"GET /api/events/incidents/{id}/events -> IncidentController.getLinkedEvents()",
|
|
203
|
+
"GET /api/events/incidents/{id}/timeline -> IncidentController.getTimeline()",
|
|
204
|
+
"POST /api/events/incidents/{id}/comments -> IncidentController.addComment()",
|
|
205
|
+
"GET /api/events/machines/{machineId}/deletion-summary -> MachineDeletionController.getDeletionSummary()",
|
|
206
|
+
"DELETE /api/events/machines/{machineId} -> MachineDeletionController.deleteMachine()",
|
|
207
|
+
"GET /api/events/alerts -> ManagedAlertsController.getManagedAlerts()",
|
|
208
|
+
"PATCH /api/events/alerts/{eventId}/status -> ManagedAlertsController.updateAlertStatus()",
|
|
209
|
+
"GET /api/events/alerts/tenant-summaries -> ManagedAlertsController.getTenantSummaries()",
|
|
210
|
+
"POST /api/events/alerts/incidents -> ManagedAlertsController.createManagedIncident()",
|
|
211
|
+
"POST /api/events/alerts/incidents/from-events -> ManagedAlertsController.createManagedIncidentFromEvents()",
|
|
212
|
+
"GET /api/events/alerts/incidents -> ManagedAlertsController.getManagedIncidents()",
|
|
213
|
+
"POST /api/events/playbooks -> PlaybookController.createTemplate()",
|
|
214
|
+
"GET /api/events/playbooks -> PlaybookController.listTemplates()",
|
|
215
|
+
"GET /api/events/playbooks/{templateId} -> PlaybookController.getTemplate()",
|
|
216
|
+
"PUT /api/events/playbooks/{templateId} -> PlaybookController.updateTemplate()",
|
|
217
|
+
"DELETE /api/events/playbooks/{templateId} -> PlaybookController.deleteTemplate()",
|
|
218
|
+
"POST /api/events/incidents/{incidentId}/playbooks -> PlaybookController.attachPlaybook()",
|
|
219
|
+
"GET /api/events/incidents/{incidentId}/playbooks -> PlaybookController.getIncidentPlaybooks()",
|
|
220
|
+
"PATCH /api/events/incidents/{incidentId}/playbooks/{playbookId}/steps/{stepId}/complete -> PlaybookController.completeStep()",
|
|
221
|
+
"PATCH /api/events/incidents/{incidentId}/playbooks/{playbookId}/steps/{stepId}/uncomplete -> PlaybookController.uncompleteStep()",
|
|
222
|
+
"POST /api/public/extension/register-device -> PublicExtensionController.registerAnonymousDevice() [public]",
|
|
223
|
+
"POST /api/public/extension/sync -> PublicExtensionController.syncExtensionsAnonymous() [public]",
|
|
224
|
+
"POST /api/public/extension/approval-request -> PublicExtensionController.requestApproval() [public]",
|
|
225
|
+
"GET /api/public/extension/health -> PublicExtensionController.healthCheck() [public]",
|
|
226
|
+
"POST /api/public/extension/heartbeat -> PublicExtensionController.heartbeat() [public]",
|
|
227
|
+
"GET /api/events/user-activity -> UserActivityController.getUserList()",
|
|
228
|
+
"GET /api/events/user-activity/search -> UserActivityController.searchUsers()",
|
|
229
|
+
"GET /api/events/user-activity/{userId} -> UserActivityController.getUserDetails()",
|
|
230
|
+
"GET /api/events/user-activity/{userId}/devices -> UserActivityController.getUserDevices()",
|
|
231
|
+
"GET /api/events/user-activity/{userId}/extensions -> UserActivityController.getUserExtensions()",
|
|
232
|
+
"GET /api/events/user-activity/{userId}/extension-events -> UserActivityController.getUserExtensionEvents()",
|
|
233
|
+
"GET /api/events/user-activity/stats -> UserActivityController.getUserStats()",
|
|
234
|
+
"GET /api/events/ai/inventory/summary -> AiInventoryController.summary()",
|
|
235
|
+
"GET /api/events/ai/inventory/hosts -> AiInventoryController.hosts()",
|
|
236
|
+
"GET /api/events/ai/inventory/mcp-servers -> AiInventoryController.mcpServers()",
|
|
237
|
+
"GET /api/events/ai/inventory/extensibility-items -> AiInventoryController.extensibilityItems()",
|
|
238
|
+
"POST /api/events/ai/inventory -> AiInventoryController.uploadInventory()",
|
|
239
|
+
"GET /api/events/ai/inventory/instruction-files -> AiInventoryController.instructionFiles()",
|
|
240
|
+
"GET /api/events/ai/inventory/local-models -> AiInventoryController.localModels()",
|
|
241
|
+
"GET /api/events/ai/inventory/devices/{deviceId} -> AiInventoryController.device()",
|
|
242
|
+
"GET /api/events/ai/inventory/changes -> AiInventoryController.changes()",
|
|
243
|
+
"GET /api/events/ai/inventory/coverage -> AiInventoryController.coverage()",
|
|
244
|
+
"POST /api/events/public/ai/inventory -> AiInventoryPublicController.uploadInventory() [public]",
|
|
245
|
+
"GET /api/events/public/ai/tool-bundle -> AiInventoryPublicController.toolBundle() [public]",
|
|
246
|
+
"GET /api/events/ai/tool-bundle -> AiToolBundleController.bundle()"
|
|
247
|
+
],
|
|
248
|
+
"calls_services": [],
|
|
249
|
+
"kafka_produces": ["quickstart-events", "device-deleted", "extension-approval-decided"],
|
|
250
|
+
"kafka_consumes": ["device-registration", "quickstart-events", "extension-policy-changed"],
|
|
251
|
+
"keycloak_realm": "not_found (no static realm; per-tenant issuer resolved dynamically from DB via DbJwtAuthenticationManagerResolver)",
|
|
252
|
+
"keycloak_client_id": "not_found (tenant.client-id/tenant.client-secret env-driven properties exist)",
|
|
253
|
+
"flyway_next_version": "V38 (highest found: V37__endpoint_ai_inventory.sql; sequential V1-V37, no gaps). NOTE: no Flyway Maven dependency in pom.xml despite this migrations directory existing — flagged inconsistency.",
|
|
254
|
+
"test_framework": "JUnit 5 (Jupiter) + Mockito + AssertJ + Testcontainers (Postgres) for integration tests",
|
|
255
|
+
"swagger_url": "/api/events/swagger-ui (springdoc.swagger-ui.path explicit; api-docs at /api/events/v3/api-docs)",
|
|
256
|
+
"spring_boot_version": "3.4.12",
|
|
257
|
+
"java_version": "17"
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
"sfn-gateway-api": {
|
|
261
|
+
"artifact_id": "api-gateway (project name: sfn-gateway-api)",
|
|
262
|
+
"port": 8083,
|
|
263
|
+
"eureka_name": "API-GATEWAY-SERVICE",
|
|
264
|
+
"domain": "Reactive API gateway (Spring Cloud Gateway) — routes /api/iam/**, /api/tenants/**, /api/events/**, /api/policies/** to their respective services via Eureka load-balancing; also aggregates downstream Swagger docs.",
|
|
265
|
+
"owns_tables": [],
|
|
266
|
+
"exposes_endpoints": [
|
|
267
|
+
"* /fallback -> FallbackController [Resilience4j fallback, 503 JSON]",
|
|
268
|
+
"ROUTE /api/iam/** -> lb://IAM-SERVICE",
|
|
269
|
+
"ROUTE /api/tenants/** -> lb://TENANT-SERVICE",
|
|
270
|
+
"ROUTE /api/events/** -> lb://EVENTS-SERVICE",
|
|
271
|
+
"ROUTE /api/policies/** -> lb://POLICY-SERVICE",
|
|
272
|
+
"ROUTE /openapi/iam -> IAM_SERVICE_URI (SetPath /api/iam/v3/api-docs)",
|
|
273
|
+
"ROUTE /openapi/tenant -> TENANT_SERVICE_URI (SetPath /v3/api-docs)",
|
|
274
|
+
"ROUTE /openapi/events -> EVENTS_SERVICE_URI (SetPath /v3/api-docs)",
|
|
275
|
+
"ROUTE /openapi/policy -> POLICY_SERVICE_URI (SetPath /v3/api-docs)"
|
|
276
|
+
],
|
|
277
|
+
"calls_services": ["IAM-SERVICE", "TENANT-SERVICE", "EVENTS-SERVICE", "POLICY-SERVICE"],
|
|
278
|
+
"kafka_produces": [],
|
|
279
|
+
"kafka_consumes": [],
|
|
280
|
+
"keycloak_realm": "not_found",
|
|
281
|
+
"keycloak_client_id": "not_found",
|
|
282
|
+
"flyway_next_version": "not_found (n/a, no database)",
|
|
283
|
+
"test_framework": "JUnit 5 (single contextLoads() smoke test only)",
|
|
284
|
+
"swagger_url": "/swagger-ui.html (springdoc-openapi-starter-webflux-ui 2.5.0; aggregates all 4 downstream services' OpenAPI specs into one dropdown)",
|
|
285
|
+
"spring_boot_version": "3.2.4",
|
|
286
|
+
"java_version": "17"
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
"sfn-iam-api": {
|
|
290
|
+
"artifact_id": "IAM (project name: sfn-iam-api)",
|
|
291
|
+
"port": 8084,
|
|
292
|
+
"eureka_name": "IAM-SERVICE",
|
|
293
|
+
"domain": "Identity & access management: users, groups, roles, scopes, features/packages/subscriptions, SSO configuration, Azure AD group sync, login/device audit. Richest tenant-isolation implementation (TenantAccessInterceptor + ParentChainAuthorizer). Heavy table/endpoint overlap with sfn-auth-api (see manual_review_flags).",
|
|
294
|
+
"owns_tables": ["access_level", "addon_pricing", "Address", "api_flags", "audit_log", "auth_provider_config", "billing_cycle", "browserpolicy", "cities", "country", "device_user", "events", "events_group", "events_group_history", "extension_policy", "features", "feature_group", "feature_types", "groups", "industry", "login_audit_event", "networkpolicy", "package", "package_feature_mapping", "package_pricing", "package_type", "policy_assignments", "region", "retention_period", "roles", "scopes", "sso_configurations", "sso_provider_url_config", "states", "Tenant", "tenant_addon_feature", "tenant_api_mappings", "tenant_subscription", "tenant_types", "Users", "user_device_login"],
|
|
295
|
+
"exposes_endpoints": [
|
|
296
|
+
"GET /access-levels -> AccessLevelController.getAll()",
|
|
297
|
+
"GET /access-levels/active -> AccessLevelController.getActive()",
|
|
298
|
+
"POST /admin/api-flags/apis -> ApiFlagAdminController.createApi()",
|
|
299
|
+
"PUT /admin/api-flags/tenants -> ApiFlagAdminController.toggleApiForTenant()",
|
|
300
|
+
"GET /admin/api-flags/mappings -> ApiFlagAdminController.getAllMappings()",
|
|
301
|
+
"GET /tenant-config/v1 -> AuthController.getTenantConfig()",
|
|
302
|
+
"GET /tenant-config -> AuthController.getTenantConfig()",
|
|
303
|
+
"GET /auth-config/preview -> AuthController.previewImpersonationAuthConfig()",
|
|
304
|
+
"POST /login -> AuthController.login()",
|
|
305
|
+
"POST /login/extension -> AuthController.extensionLogin()",
|
|
306
|
+
"POST /login/sso -> AuthController.ssoLogin()",
|
|
307
|
+
"GET /azure/admin-consent/status -> AzureAdminConsentController.status()",
|
|
308
|
+
"GET /azure/admin-consent/complete -> AzureAdminConsentController.complete() [public]",
|
|
309
|
+
"GET /regions -> DropdownsController.getRegions()",
|
|
310
|
+
"GET /countries -> DropdownsController.getCountriesByRegion()",
|
|
311
|
+
"GET /states -> DropdownsController.getStatesByCountry()",
|
|
312
|
+
"GET /cities -> DropdownsController.getCitiesByStateId()",
|
|
313
|
+
"GET /industries -> DropdownsController.getAllIndustries()",
|
|
314
|
+
"GET /tenants/billing -> DropdownsController.getBillingTypes()",
|
|
315
|
+
"GET /tenants/types -> DropdownsController.getAllTenantTypes()",
|
|
316
|
+
"GET /groups/dropdown -> DropdownsController.getGroupsForDropdown()",
|
|
317
|
+
"GET /roles/dropdown -> DropdownsController.getRolesForDropdown()",
|
|
318
|
+
"GET /package-types -> DropdownsController.getPackageTypes()",
|
|
319
|
+
"GET /billing-cycles -> DropdownsController.getBillingCycles()",
|
|
320
|
+
"GET /events-groups -> EventsGroupController.getAllGroups()",
|
|
321
|
+
"GET /events-groups/{groupId} -> EventsGroupController.getGroupById()",
|
|
322
|
+
"POST /events-groups -> EventsGroupController.createGroup()",
|
|
323
|
+
"PUT /events-groups/{groupId} -> EventsGroupController.updateGroup()",
|
|
324
|
+
"PUT /events-groups/priority-order -> EventsGroupController.reorderPriorities()",
|
|
325
|
+
"PUT /events-groups/groups/{groupId}/{action} -> EventsGroupController.setAuthorization()",
|
|
326
|
+
"DELETE /events-groups/{groupId} -> EventsGroupController.deleteGroup()",
|
|
327
|
+
"POST /events-groups/{groupId}/authorize-extension -> EventsGroupController.authorizeForExtension()",
|
|
328
|
+
"DELETE /events-groups/{groupId}/authorize-extension -> EventsGroupController.revokeExtensionAccess()",
|
|
329
|
+
"POST /events-groups/{groupId}/device-users -> EventsGroupController.assignDeviceUsers()",
|
|
330
|
+
"DELETE /events-groups/{groupId}/device-users/{deviceUserId} -> EventsGroupController.removeDeviceUser()",
|
|
331
|
+
"GET /events-groups/{groupId}/device-users -> EventsGroupController.getDeviceUsersInGroup()",
|
|
332
|
+
"GET /events-groups/{groupId}/policies -> EventsGroupController.getGroupPolicies()",
|
|
333
|
+
"POST /events-groups/sync-members -> EventsGroupController.syncAzureGroupMembers()",
|
|
334
|
+
"GET /events-groups/stats/memberships -> EventsGroupController.getGroupMembershipStats()",
|
|
335
|
+
"GET /events-groups/device-users/stats/memberships -> EventsGroupController.getUserMembershipStats()",
|
|
336
|
+
"GET /events-groups/device-users/{deviceUserId}/policies -> EventsGroupController.getPoliciesForDeviceUser()",
|
|
337
|
+
"GET /events-groups/device-users/{deviceUserId}/groups -> EventsGroupController.getGroupsForDeviceUser()",
|
|
338
|
+
"GET /events-groups/{groupId}/history -> EventsGroupController.getGroupHistory()",
|
|
339
|
+
"GET /events-groups/history -> EventsGroupController.getTenantHistory()",
|
|
340
|
+
"POST /extension/auth -> ExtensionAuthController.authenticateExtension()",
|
|
341
|
+
"POST /features -> FeatureController.createFeature()",
|
|
342
|
+
"GET /features -> FeatureController.getAllFeatures()",
|
|
343
|
+
"GET /features/{id} -> FeatureController.getFeature()",
|
|
344
|
+
"PUT /features/{id} -> FeatureController.updateFeature()",
|
|
345
|
+
"DELETE /features/{id} -> FeatureController.deleteFeature()",
|
|
346
|
+
"PUT /features/{id}/deactivate -> FeatureController.deactivateFeature()",
|
|
347
|
+
"PUT /features/{id}/activate -> FeatureController.activateFeature()",
|
|
348
|
+
"POST /feature-groups -> FeatureGroupController.create()",
|
|
349
|
+
"PUT /feature-groups/{id} -> FeatureGroupController.update()",
|
|
350
|
+
"GET /feature-groups/{id} -> FeatureGroupController.getById()",
|
|
351
|
+
"GET /feature-groups/code/{code} -> FeatureGroupController.getByCode()",
|
|
352
|
+
"GET /feature-groups -> FeatureGroupController.getAll()",
|
|
353
|
+
"GET /feature-groups/active -> FeatureGroupController.getActiveGroups()",
|
|
354
|
+
"DELETE /feature-groups/{id} -> FeatureGroupController.delete()",
|
|
355
|
+
"PATCH /feature-groups/{id}/activate -> FeatureGroupController.activate()",
|
|
356
|
+
"PATCH /feature-groups/{id}/deactivate -> FeatureGroupController.deactivate()",
|
|
357
|
+
"POST /groups -> GroupController.createGroup()",
|
|
358
|
+
"PUT /groups/{id} -> GroupController.updateGroup()",
|
|
359
|
+
"GET /groups/id -> GroupController.getGroupById()",
|
|
360
|
+
"GET /groups -> GroupController.getAllGroups()",
|
|
361
|
+
"GET /groups/tenant -> GroupController.getGroupsByTenant()",
|
|
362
|
+
"GET /audit/login/events -> LoginAuditController.getEvents()",
|
|
363
|
+
"GET /audit/login/events/user/{userId} -> LoginAuditController.getEventsByUser()",
|
|
364
|
+
"GET /audit/login/events/type/{eventType} -> LoginAuditController.getEventsByType()",
|
|
365
|
+
"GET /audit/login/events/source/{sourceService} -> LoginAuditController.getEventsBySourceService()",
|
|
366
|
+
"GET /audit/login/events/timerange -> LoginAuditController.getEventsByTimeRange()",
|
|
367
|
+
"GET /audit/login/events/session/{sessionId} -> LoginAuditController.getSessionEvents()",
|
|
368
|
+
"GET /audit/login/events/users -> LoginAuditController.getUserManagementEvents()",
|
|
369
|
+
"GET /audit/login/events/roles -> LoginAuditController.getRoleManagementEvents()",
|
|
370
|
+
"GET /audit/login/events/groups -> LoginAuditController.getGroupManagementEvents()",
|
|
371
|
+
"GET /audit/login/stats/by-source -> LoginAuditController.getStatsBySourceService()",
|
|
372
|
+
"DELETE /audit/login/events/cleanup -> LoginAuditController.deleteOldEvents()",
|
|
373
|
+
"GET /audit/login/devices/user/{userId} -> LoginAuditController.getUserDevices()",
|
|
374
|
+
"GET /audit/login/devices/user/{userId}/active -> LoginAuditController.getActiveUserDevices()",
|
|
375
|
+
"GET /audit/login/devices/user/{userId}/trusted -> LoginAuditController.getTrustedDevices()",
|
|
376
|
+
"GET /audit/login/devices -> LoginAuditController.getTenantDevices()",
|
|
377
|
+
"GET /audit/login/devices/status/{status} -> LoginAuditController.getDevicesByStatus()",
|
|
378
|
+
"GET /audit/login/devices/{deviceId}/events -> LoginAuditController.getDeviceLoginEvents()",
|
|
379
|
+
"GET /audit/login/devices/fingerprint/{fingerprint}/events -> LoginAuditController.getDeviceLoginEventsByFingerprint()",
|
|
380
|
+
"GET /audit/login/devices/search -> LoginAuditController.searchDevices()",
|
|
381
|
+
"POST /audit/login/devices/user/{userId}/trust -> LoginAuditController.trustDevice()",
|
|
382
|
+
"POST /audit/login/devices/user/{userId}/block -> LoginAuditController.blockDevice()",
|
|
383
|
+
"POST /audit/login/devices/user/{userId}/revoke-all -> LoginAuditController.revokeAllUserDevices()",
|
|
384
|
+
"GET /audit/login/devices/new-logins -> LoginAuditController.getNewDeviceLogins()",
|
|
385
|
+
"GET /audit/login/devices/stats -> LoginAuditController.getDeviceLoginStats()",
|
|
386
|
+
"GET /audit/login/devices/high-failures -> LoginAuditController.getDevicesWithHighFailures()",
|
|
387
|
+
"GET /audit/login/devices/inactive -> LoginAuditController.getInactiveDevices()",
|
|
388
|
+
"GET /audit/login/devices/user/{userId}/count -> LoginAuditController.countUserDevices()",
|
|
389
|
+
"POST /admin/migrations/groups-scope/{tenantId} -> MigrationController.migrateOne()",
|
|
390
|
+
"POST /admin/migrations/groups-scope -> MigrationController.migrateAll()",
|
|
391
|
+
"POST /packages -> PackageController.createPackage()",
|
|
392
|
+
"PUT /packages/{id} -> PackageController.updatePackage()",
|
|
393
|
+
"GET /packages/{id} -> PackageController.getPackage()",
|
|
394
|
+
"GET /packages -> PackageController.getAllPackages()",
|
|
395
|
+
"DELETE /packages/{id} -> PackageController.deletePackage()",
|
|
396
|
+
"POST /package-feature-mappings -> PackageFeatureMappingController.create()",
|
|
397
|
+
"POST /package-feature-mappings/bulk -> PackageFeatureMappingController.bulkCreate()",
|
|
398
|
+
"PUT /package-feature-mappings/{id} -> PackageFeatureMappingController.update()",
|
|
399
|
+
"DELETE /package-feature-mappings/{id} -> PackageFeatureMappingController.delete()",
|
|
400
|
+
"DELETE /package-feature-mappings/package/{packageId} -> PackageFeatureMappingController.deleteByPackage()",
|
|
401
|
+
"GET /package-feature-mappings/{id} -> PackageFeatureMappingController.getById()",
|
|
402
|
+
"GET /package-feature-mappings/package/{packageId} -> PackageFeatureMappingController.getByPackage()",
|
|
403
|
+
"GET /package-feature-mappings/matrix/{packageId} -> PackageFeatureMappingController.getPackageMatrix()",
|
|
404
|
+
"GET /package-feature-mappings/matrices -> PackageFeatureMappingController.getAllMatrices()",
|
|
405
|
+
"GET /package-feature-mappings/check-access -> PackageFeatureMappingController.checkAccess()",
|
|
406
|
+
"GET /package-feature-mappings/access-level -> PackageFeatureMappingController.getAccessLevel()",
|
|
407
|
+
"GET /package-feature-mappings/retention -> PackageFeatureMappingController.getRetention()",
|
|
408
|
+
"GET /retention-periods -> RetentionPeriodController.getAll()",
|
|
409
|
+
"GET /retention-periods/active -> RetentionPeriodController.getActive()",
|
|
410
|
+
"POST /roles -> RolesController.createRole()",
|
|
411
|
+
"PUT /roles/{id} -> RolesController.updateRole()",
|
|
412
|
+
"GET /roles -> RolesController.getAllRoles()",
|
|
413
|
+
"GET /roles/{id} -> RolesController.getRoleById()",
|
|
414
|
+
"POST /roles/activate -> RolesController.activateRole()",
|
|
415
|
+
"GET /scopes -> ScopesController.getAllScopes()",
|
|
416
|
+
"PUT /scopes/{scopeId}/tenant-types -> ScopesController.updateScopeTenantTypes()",
|
|
417
|
+
"GET /scopes/{scopeId} -> ScopesController.getScopeById()",
|
|
418
|
+
"GET /scopes/menu/{menuName} -> ScopesController.getScopesByMenu()",
|
|
419
|
+
"GET /scopes/menu/{menuName}/submenu/{subMenu} -> ScopesController.getScopesByMenuAndSubMenu()",
|
|
420
|
+
"POST /scopes -> ScopesController.createScope()",
|
|
421
|
+
"PUT /scopes/{scopeId} -> ScopesController.updateScope()",
|
|
422
|
+
"DELETE /scopes/{scopeId} -> ScopesController.deleteScope()",
|
|
423
|
+
"POST /sso-configurations -> SsoConfigurationController.create()",
|
|
424
|
+
"GET /sso-configurations -> SsoConfigurationController.getAll()",
|
|
425
|
+
"GET /sso-configurations/{id} -> SsoConfigurationController.getById()",
|
|
426
|
+
"PUT /sso-configurations/{id} -> SsoConfigurationController.update()",
|
|
427
|
+
"DELETE /sso-configurations/{id} -> SsoConfigurationController.delete()",
|
|
428
|
+
"PUT /sso-configurations/{id}/activate -> SsoConfigurationController.activate()",
|
|
429
|
+
"GET /sso-configurations/roles -> SsoConfigurationController.getAvailableAppRoles()",
|
|
430
|
+
"GET /sso-configurations/groups -> SsoConfigurationController.searchGroups()",
|
|
431
|
+
"POST /tenant-addon-features -> TenantAddonFeatureController.createAddonFeature()",
|
|
432
|
+
"POST /tenant-addon-features/bulk -> TenantAddonFeatureController.createBulkAddonFeatures()",
|
|
433
|
+
"GET /tenant-addon-features/{addonId} -> TenantAddonFeatureController.getAddonById()",
|
|
434
|
+
"GET /tenant-addon-features/tenant/{tenantId} -> TenantAddonFeatureController.getAddonsByTenant()",
|
|
435
|
+
"GET /tenant-addon-features/tenant/{tenantId}/active -> TenantAddonFeatureController.getActiveAddonsByTenant()",
|
|
436
|
+
"GET /tenant-addon-features/tenant/{tenantId}/feature/{featureCode} -> TenantAddonFeatureController.getAddonByTenantAndFeature()",
|
|
437
|
+
"GET /tenant-addon-features/tenant/{tenantId}/has-addon/{featureCode} -> TenantAddonFeatureController.hasActiveAddon()",
|
|
438
|
+
"PUT /tenant-addon-features/{addonId} -> TenantAddonFeatureController.updateAddonFeature()",
|
|
439
|
+
"PATCH /tenant-addon-features/{addonId}/toggle -> TenantAddonFeatureController.toggleAddonFeature()",
|
|
440
|
+
"DELETE /tenant-addon-features/{addonId} -> TenantAddonFeatureController.deleteAddonFeature()",
|
|
441
|
+
"DELETE /tenant-addon-features/tenant/{tenantId}/feature/{featureId} -> TenantAddonFeatureController.deleteAddonByTenantAndFeature()",
|
|
442
|
+
"POST /tenant-addon-features/tenant/{tenantId}/purchase -> TenantAddonFeatureController.purchaseAddon()",
|
|
443
|
+
"POST /tenant-addon-features/{addonId}/renew -> TenantAddonFeatureController.renewAddon()",
|
|
444
|
+
"POST /tenant-addon-features/{addonId}/convert-trial -> TenantAddonFeatureController.convertAddonTrial()",
|
|
445
|
+
"GET /tenant-addon-features/tenant/{tenantId}/billing-summary -> TenantAddonFeatureController.getAddonBillingSummary()",
|
|
446
|
+
"GET /tenant-features/{tenantId} -> TenantFeatureController.getTenantFeatures()",
|
|
447
|
+
"GET /tenant-features/{tenantId}/check/{featureCode} -> TenantFeatureController.checkFeatureAccess()",
|
|
448
|
+
"GET /tenant-features/{tenantId}/access-level/{featureCode} -> TenantFeatureController.getAccessLevel()",
|
|
449
|
+
"GET /tenant-features/{tenantId}/retention/{featureCode} -> TenantFeatureController.getRetentionDays()",
|
|
450
|
+
"PUT /tenant-features/{tenantId}/package -> TenantFeatureController.updateTenantPackage()",
|
|
451
|
+
"PATCH /tenants/{tenantId}/privacy-level -> TenantPrivacyLevelController.updatePrivacyLevel()",
|
|
452
|
+
"POST /subscriptions -> TenantSubscriptionController.createSubscription()",
|
|
453
|
+
"POST /subscriptions/default/{tenantId} -> TenantSubscriptionController.createDefaultSubscription()",
|
|
454
|
+
"GET /subscriptions/tenant/{tenantId} -> TenantSubscriptionController.getActiveSubscription()",
|
|
455
|
+
"GET /subscriptions/tenant/{tenantId}/history -> TenantSubscriptionController.getSubscriptionHistory()",
|
|
456
|
+
"POST /subscriptions/tenant/{tenantId}/upgrade -> TenantSubscriptionController.upgradeSubscription()",
|
|
457
|
+
"POST /subscriptions/tenant/{tenantId}/downgrade -> TenantSubscriptionController.downgradeSubscription()",
|
|
458
|
+
"POST /subscriptions/tenant/{tenantId}/convert-trial -> TenantSubscriptionController.convertTrial()",
|
|
459
|
+
"POST /subscriptions/tenant/{tenantId}/cancel -> TenantSubscriptionController.cancelSubscription()",
|
|
460
|
+
"POST /subscriptions/tenant/{tenantId}/renew -> TenantSubscriptionController.renewSubscription()",
|
|
461
|
+
"GET /subscriptions/pricing/packages -> TenantSubscriptionController.getAllPackagesWithPricing()",
|
|
462
|
+
"GET /subscriptions/pricing/package/{packageId} -> TenantSubscriptionController.getPackagePricing()",
|
|
463
|
+
"GET /subscriptions/pricing/addons -> TenantSubscriptionController.getAllAddonFeaturesWithPricing()",
|
|
464
|
+
"GET /subscriptions/pricing/addon/{featureCode} -> TenantSubscriptionController.getAddonPricingByFeatureCode()",
|
|
465
|
+
"GET /subscriptions/pricing/addon/{featureCode}/cycle/{billingCycleCode} -> TenantSubscriptionController.getAddonPricing()",
|
|
466
|
+
"POST /users/{tenantId} -> UserController.createUser()",
|
|
467
|
+
"POST /users -> UserController.createUserByTenant()",
|
|
468
|
+
"GET /users/{userId} -> UserController.getUser()",
|
|
469
|
+
"GET /users/userId -> UserController.getUserByParents()",
|
|
470
|
+
"GET /users -> UserController.getAllUsers()",
|
|
471
|
+
"GET /users/tenant/{tenantId} -> UserController.getUsersByTenant()",
|
|
472
|
+
"PUT /users/{userId} -> UserController.updateUser()",
|
|
473
|
+
"PUT /users/update -> UserController.updateUserByParents()",
|
|
474
|
+
"DELETE /users/{userId} -> UserController.deleteUser()",
|
|
475
|
+
"GET /users/check -> UserController.uniqueValidations()",
|
|
476
|
+
"POST /users/tenants/{tenantId}/users/{userId}/resend-verification -> UserController.resendVerification()"
|
|
477
|
+
],
|
|
478
|
+
"calls_services": [],
|
|
479
|
+
"kafka_produces": ["device-registration"],
|
|
480
|
+
"kafka_consumes": ["device-deleted"],
|
|
481
|
+
"keycloak_realm": "not_found (no static realm; per-tenant DB-driven JwtDecoder resolution via DbJwtAuthenticationManagerResolver; admin-client realm=${KC_ADMIN_REALM})",
|
|
482
|
+
"keycloak_client_id": "not_found (env-driven ${KC_ADMIN_CLIENT_ID}); Keycloak Admin REST client + Microsoft Graph SDK used for Azure AD group sync",
|
|
483
|
+
"flyway_next_version": "V56 (highest found: V55__seed_ai_policy_scopes.sql; non-contiguous range, V41-V47 and V52 missing). NOTE: no Flyway Maven dependency declared despite this migrations directory — flagged inconsistency.",
|
|
484
|
+
"test_framework": "JUnit 5 + Mockito (transitive) + Testcontainers (Postgres); 41 test classes incl. *Test, *IntegrationTest, *BaselineTest, *MigrationTest naming patterns",
|
|
485
|
+
"swagger_url": "not_found (default springdoc path under inferred context-path /api/iam; no custom springdoc.swagger-ui.path configured)",
|
|
486
|
+
"spring_boot_version": "3.4.12",
|
|
487
|
+
"java_version": "17"
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
"sfn-policy-api": {
|
|
491
|
+
"artifact_id": "sfn-policy-api",
|
|
492
|
+
"port": 8089,
|
|
493
|
+
"eureka_name": "POLICY-SERVICE",
|
|
494
|
+
"domain": "Browser/network/extension/AI policy CRUD and effective-policy resolution for the browser extension; rules-engine bundle publishing. Heavy table/endpoint/Kafka-topic overlap with sfn-tenants-api (see manual_review_flags) — near-duplicate policy domain.",
|
|
495
|
+
"owns_tables": ["Address", "api_flags", "audit_log", "auth_provider_config", "billing_cycle", "devices", "events", "browserpolicy", "compliancerules", "device_user", "dlp", "events_groups", "events_groups_device_user_map", "extension_api_keys", "extension_detail", "extension_policy", "groups", "homepage", "idp_provider_default_hostname", "landingpage", "managed_extensions", "networkconfiguration", "networkpolicy", "package_pricing", "package_type", "policy_assignments", "roles", "rules_engine_bundle", "scopes", "shortcut", "smtp_config", "subscription_history", "package", "Tenant", "tenant_api_mappings", "tenant_subscription", "tenant_types", "urlfilter", "Users", "watermarking", "webhook_configs"],
|
|
496
|
+
"exposes_endpoints": [
|
|
497
|
+
"POST /browser-policy -> BrowserPolicyController.createPolicy()",
|
|
498
|
+
"GET /browser-policy/{id} -> BrowserPolicyController.getPolicy()",
|
|
499
|
+
"GET /browser-policy -> BrowserPolicyController.getAllPolicies()",
|
|
500
|
+
"PUT /browser-policy/{id} -> BrowserPolicyController.updatePolicy()",
|
|
501
|
+
"DELETE /browser-policy/{id} -> BrowserPolicyController.deletePolicy()",
|
|
502
|
+
"GET /browser-policy/{id}/revisions -> BrowserPolicyController.getPolicyRevisions()",
|
|
503
|
+
"GET /browser-policy/{id}/revisions/{rev} -> BrowserPolicyController.getPolicyAtRevision()",
|
|
504
|
+
"GET /browser-policy/all -> BrowserPolicyController.getAllPoliciesForTenant()",
|
|
505
|
+
"GET /browser-policy/all/combined -> BrowserPolicyController.getAllPoliciesCombined()",
|
|
506
|
+
"GET /browser-policy/default -> BrowserPolicyController.getDefaultPolicyForTenant()",
|
|
507
|
+
"GET /browser-policy/{id}/with-assignments -> BrowserPolicyController.getPolicyWithAssignments()",
|
|
508
|
+
"GET /browser-policy/all/with-assignments -> BrowserPolicyController.getAllPoliciesWithAssignments()",
|
|
509
|
+
"GET /browser-policy/effective/all -> BrowserPolicyController.getAllEffectivePolicies()",
|
|
510
|
+
"GET /browser-policy/effective/with-assignments -> BrowserPolicyController.getEffectivePolicyWithAssignments()",
|
|
511
|
+
"GET /browser-policy/effective/versions -> BrowserPolicyController.getPolicyVersions()",
|
|
512
|
+
"POST /extension-policy -> ExtensionPolicyController.createPolicy()",
|
|
513
|
+
"GET /extension-policy/{id} -> ExtensionPolicyController.getPolicy()",
|
|
514
|
+
"GET /extension-policy -> ExtensionPolicyController.getAllPolicies()",
|
|
515
|
+
"PUT /extension-policy/{id} -> ExtensionPolicyController.updatePolicy()",
|
|
516
|
+
"DELETE /extension-policy/{id} -> ExtensionPolicyController.deletePolicy()",
|
|
517
|
+
"GET /extension-policy/mapping -> ExtensionPolicyController.getGroupMappedPolicies()",
|
|
518
|
+
"POST /extension-policy/managed-extensions/bulk -> ExtensionPolicyController.bulkModifyManagedExtensions()",
|
|
519
|
+
"GET /extension-policy/picker-summary -> ExtensionPolicyController.getPickerSummary()",
|
|
520
|
+
"GET /extension-policy/default -> ExtensionPolicyController.getDefaultPolicy()",
|
|
521
|
+
"GET /network-policy/idp-defaults -> IdpDefaultsController.list()",
|
|
522
|
+
"POST /landing-pages -> LandingPageController.createLandingPage()",
|
|
523
|
+
"GET /landing-pages -> LandingPageController.getAllLandingPages()",
|
|
524
|
+
"GET /landing-pages/{id} -> LandingPageController.getLandingPageById()",
|
|
525
|
+
"PUT /landing-pages/{id} -> LandingPageController.updateLandingPage()",
|
|
526
|
+
"DELETE /landing-pages/{id} -> LandingPageController.deleteLandingPage()",
|
|
527
|
+
"POST /network-config -> NetworkConfigController.fetch() [SfBrowser bootstrap, azp-gated]",
|
|
528
|
+
"POST /network-policy -> NetworkPolicyController.create()",
|
|
529
|
+
"GET /network-policy/{id} -> NetworkPolicyController.getById()",
|
|
530
|
+
"GET /network-policy -> NetworkPolicyController.getAll()",
|
|
531
|
+
"PUT /network-policy/{id} -> NetworkPolicyController.update()",
|
|
532
|
+
"DELETE /network-policy/{id} -> NetworkPolicyController.delete()",
|
|
533
|
+
"POST /mapping/{policyType} -> PolicyMappingController.assignPolicy()",
|
|
534
|
+
"POST /mapping/{policyType}/{policyId} -> PolicyMappingController.addSingleAssignment()",
|
|
535
|
+
"PUT /mapping/{id} -> PolicyMappingController.updateAssignment()",
|
|
536
|
+
"GET /mapping -> PolicyMappingController.getAllMappings()",
|
|
537
|
+
"GET /mapping/{id} -> PolicyMappingController.getMappingById()",
|
|
538
|
+
"DELETE /mapping/{id} -> PolicyMappingController.deleteAssignment()",
|
|
539
|
+
"POST /mapping/groups -> PolicyMappingController.assignPoliciesToGroups()",
|
|
540
|
+
"GET /policy/rules-engine -> RulesEnginePolicyController.getRulesEngineBundle()",
|
|
541
|
+
"POST /policy/rules-engine/bundles/global -> RulesEnginePolicyController.updateGlobalRulesEngineBundle() [MASTER MSSP ADMIN only]",
|
|
542
|
+
"POST /policy/rules-engine/bundles/tenants/{tenantId} -> RulesEnginePolicyController.updateTenantRulesEngineBundle() [MSSP ADMIN, rate-limited]",
|
|
543
|
+
"GET /.well-known/sfbrowser-bootstrap-jwks -> SfBrowserBootstrapJwksController.jwks() [public]"
|
|
544
|
+
],
|
|
545
|
+
"calls_services": [],
|
|
546
|
+
"kafka_produces": ["policy-events", "extension-policy-changed"],
|
|
547
|
+
"kafka_consumes": ["extension-approval-decided"],
|
|
548
|
+
"keycloak_realm": "not_found (no static realm; per-tenant DB-driven JwtDecoder resolution)",
|
|
549
|
+
"keycloak_client_id": "not_found (env-driven ${KC_ADMIN_CLIENT_ID}); sfbrowser.bootstrap.expected-azp=sfbrowser-bootstrap for the SfBrowser network-config bootstrap flow",
|
|
550
|
+
"flyway_next_version": "not_found (no db/migration directory and no Flyway dependency in this repo — shares the Postgres schema managed/migrated elsewhere, likely sfn-tenants-api)",
|
|
551
|
+
"test_framework": "JUnit 5 (single contextLoads() smoke test only — no controller/service/repository tests exist despite Mockito/Testcontainers/JaCoCo being on the classpath)",
|
|
552
|
+
"swagger_url": "not_found (default springdoc path under inferred context-path /api/policy; permitAll'd but path not explicitly configured)",
|
|
553
|
+
"spring_boot_version": "3.4.12",
|
|
554
|
+
"java_version": "17"
|
|
555
|
+
},
|
|
556
|
+
|
|
557
|
+
"sfn-tenants-api": {
|
|
558
|
+
"artifact_id": "sfn-tenants-api",
|
|
559
|
+
"port": "CONFLICT: 9001 (local dev default found in local/.env.local.example within the repo) vs 8085 (TENANT_SERVICE_PORT in shared deployment env config supplied by user) — likely a per-developer local override vs. the shared dev-environment value; flagged for manual verification",
|
|
560
|
+
"eureka_name": "TENANT-SERVICE",
|
|
561
|
+
"domain": "Owns the canonical Tenant entity and lifecycle/onboarding (incl. per-tenant Keycloak realm provisioning), sessions/auth, dashboards, webhooks, SMTP config, subscriptions, plus a second copy of the browser/network/extension/AI policy domain (see manual_review_flags for overlap with sfn-policy-api).",
|
|
562
|
+
"owns_tables": ["Tenant", "Users", "auth_provider_config", "ai_policy", "audit_log", "events", "device_user", "devices", "browserpolicy", "events_group", "groups", "extension_api_keys", "login_audit_event", "extension_policy", "notifications", "landingpage", "notification_preferences", "networkpolicy", "policy_assignments", "roles", "rules_engine_bundle", "smtp_config", "tenant_api_mappings", "tenant_subscription", "subscription_history", "webhook_configs", "webhook_delivery_logs", "Address", "api_flags", "billing_cycle", "country", "cities", "states", "region", "compliancerules", "extension_detail", "managed_extensions", "package_pricing", "package_type", "package", "scopes", "TenantTypes", "idp_provider_default_hostname", "events_groups_device_user_map"],
|
|
563
|
+
"exposes_endpoints": [
|
|
564
|
+
"POST /api/tenants -> TenantController.createTenant()",
|
|
565
|
+
"GET /api/tenants/{id}/provisioning-status -> TenantController.getProvisioningStatus()",
|
|
566
|
+
"POST /api/tenants/{id}/retry-provisioning -> TenantController.retryProvisioning()",
|
|
567
|
+
"POST /api/tenants/repair-active-tenants -> TenantController.repairActiveTenants() [platform admin]",
|
|
568
|
+
"POST /api/tenants/{id}/repair -> TenantController.repairTenant()",
|
|
569
|
+
"POST /api/tenants/normalize-realm-names -> TenantController.normalizeRealmNames() [platform admin]",
|
|
570
|
+
"POST /api/tenants/{id}/resend-welcome-email -> TenantController.resendWelcomeEmail()",
|
|
571
|
+
"POST /api/tenants/{id}/resend-reset-password -> TenantController.resendResetPasswordEmail()",
|
|
572
|
+
"POST /api/tenants/{id}/admin-user/set-temp-password -> TenantController.setAdminTemporaryPassword()",
|
|
573
|
+
"POST /api/tenants/{id}/sync-self-managed-roles -> TenantController.syncSelfManagedRoles()",
|
|
574
|
+
"GET /api/tenants/{id} -> TenantController.getTenant()",
|
|
575
|
+
"GET /api/tenants -> TenantController.getAll()",
|
|
576
|
+
"PUT /api/tenants/{id} -> TenantController.updateTenant()",
|
|
577
|
+
"DELETE /api/tenants/{id} -> TenantController.deleteTenant()",
|
|
578
|
+
"GET /api/tenants/check -> TenantController.checkTenant()",
|
|
579
|
+
"POST /api/tenants/clients -> TenantController.createClientForTenant()",
|
|
580
|
+
"GET /api/tenants/tenant-config/v1 -> TenantController.getTenantConfig() [public]",
|
|
581
|
+
"GET /api/tenants/tenant-config -> TenantController.getTenantConfig() [public]",
|
|
582
|
+
"GET /api/tenants/{realmName}/diagnose-sso -> TenantController.diagnoseSso()",
|
|
583
|
+
"POST /api/tenants/{realmName}/repair-sso -> TenantController.repairAzureSso()",
|
|
584
|
+
"POST /api/tenants/policy -> BrowserPolicyController.createPolicy()",
|
|
585
|
+
"GET /api/tenants/policy/{id} -> BrowserPolicyController.getPolicy()",
|
|
586
|
+
"GET /api/tenants/policy -> BrowserPolicyController.getAllPolicies()",
|
|
587
|
+
"PUT /api/tenants/policy/{id} -> BrowserPolicyController.updatePolicy()",
|
|
588
|
+
"DELETE /api/tenants/policy/{id} -> BrowserPolicyController.deletePolicy()",
|
|
589
|
+
"GET /api/tenants/policy/{id}/revisions -> BrowserPolicyController.getPolicyRevisions()",
|
|
590
|
+
"GET /api/tenants/policy/{id}/revisions/{rev} -> BrowserPolicyController.getPolicyAtRevision()",
|
|
591
|
+
"GET /api/tenants/policy/all -> BrowserPolicyController.getAllPoliciesForTenant()",
|
|
592
|
+
"GET /api/tenants/policy/all/combined -> BrowserPolicyController.getAllPoliciesCombined()",
|
|
593
|
+
"GET /api/tenants/policy/default -> BrowserPolicyController.getDefaultPolicyForTenant()",
|
|
594
|
+
"GET /api/tenants/policy/{id}/with-assignments -> BrowserPolicyController.getPolicyWithAssignments()",
|
|
595
|
+
"GET /api/tenants/policy/all/with-assignments -> BrowserPolicyController.getAllPoliciesWithAssignments()",
|
|
596
|
+
"GET /api/tenants/policy/effective/all -> BrowserPolicyController.getAllEffectivePolicies()",
|
|
597
|
+
"GET /api/tenants/policy/effective/with-assignments -> BrowserPolicyController.getEffectivePolicyWithAssignments()",
|
|
598
|
+
"GET /api/tenants/policy/effective/versions -> BrowserPolicyController.getPolicyVersions()",
|
|
599
|
+
"POST /api/tenants/network-config -> NetworkConfigController.fetch() [SfBrowser bootstrap]",
|
|
600
|
+
"POST /api/tenants/landing-pages -> LandingPageController.createLandingPage()",
|
|
601
|
+
"GET /api/tenants/landing-pages -> LandingPageController.getAllLandingPages()",
|
|
602
|
+
"GET /api/tenants/landing-pages/{id} -> LandingPageController.getLandingPageById()",
|
|
603
|
+
"PUT /api/tenants/landing-pages/{id} -> LandingPageController.updateLandingPage()",
|
|
604
|
+
"DELETE /api/tenants/landing-pages/{id} -> LandingPageController.deleteLandingPage()",
|
|
605
|
+
"GET /api/tenants/network-policy/idp-defaults -> IdpDefaultsController.list()",
|
|
606
|
+
"POST /api/tenants/ai-policy -> AiPolicyController.createPolicy()",
|
|
607
|
+
"GET /api/tenants/ai-policy/{id} -> AiPolicyController.getPolicy()",
|
|
608
|
+
"GET /api/tenants/ai-policy -> AiPolicyController.getAllPolicies()",
|
|
609
|
+
"PUT /api/tenants/ai-policy/{id} -> AiPolicyController.updatePolicy()",
|
|
610
|
+
"DELETE /api/tenants/ai-policy/{id} -> AiPolicyController.deletePolicy()",
|
|
611
|
+
"GET /api/tenants/ai-policy/default -> AiPolicyController.getDefaultPolicy()",
|
|
612
|
+
"POST /api/tenants/extension-policy -> ExtensionPolicyController.createPolicy()",
|
|
613
|
+
"GET /api/tenants/extension-policy/{id} -> ExtensionPolicyController.getPolicy()",
|
|
614
|
+
"GET /api/tenants/extension-policy -> ExtensionPolicyController.getAllPolicies()",
|
|
615
|
+
"PUT /api/tenants/extension-policy/{id} -> ExtensionPolicyController.updatePolicy()",
|
|
616
|
+
"DELETE /api/tenants/extension-policy/{id} -> ExtensionPolicyController.deletePolicy()",
|
|
617
|
+
"GET /api/tenants/extension-policy/mapping -> ExtensionPolicyController.getGroupMappedPolicies()",
|
|
618
|
+
"POST /api/tenants/extension-policy/managed-extensions/bulk -> ExtensionPolicyController.bulkModifyManagedExtensions()",
|
|
619
|
+
"GET /api/tenants/extension-policy/picker-summary -> ExtensionPolicyController.getPickerSummary()",
|
|
620
|
+
"GET /api/tenants/extension-policy/default -> ExtensionPolicyController.getDefaultPolicy()",
|
|
621
|
+
"GET /api/tenants/audit-logs -> AuditLogController.getAuditLogs()",
|
|
622
|
+
"GET /api/tenants/audit-logs/entity/{entityName}/{entityId} -> AuditLogController.getEntityHistory()",
|
|
623
|
+
"GET /api/tenants/audit-logs/entity-type/{entityName} -> AuditLogController.getAuditLogsByEntityType()",
|
|
624
|
+
"GET /api/tenants/audit-logs/date-range -> AuditLogController.getAuditLogsByDateRange()",
|
|
625
|
+
"GET /api/tenants/audit-logs/operation/{operation} -> AuditLogController.getAuditLogsByOperation()",
|
|
626
|
+
"GET /api/tenants/audit-logs/user/{username} -> AuditLogController.getAuditLogsByUser()",
|
|
627
|
+
"GET /api/tenants/audit/login/... -> LoginAuditController [~14 endpoints: events/stats/security-failures]",
|
|
628
|
+
"GET|POST /api/tenants/auth/... -> AuthController [~19 endpoints: logout, session mgmt, token introspect/refresh, password change/reset, API-key exchange]",
|
|
629
|
+
"GET /api/tenants/dashboard/overview -> DashboardController.overview()",
|
|
630
|
+
"GET /api/tenants/dashboard/stats/{tenants|users|sessions|logins|security|policies|licenses|range} -> DashboardController [dual-mapped also under /api/tenants/{actingTenantId}/dashboard/...]",
|
|
631
|
+
"GET /api/tenants/dashboard/tenant-stats -> DashboardController.tenantStats()",
|
|
632
|
+
"POST /api/tenants/webhooks -> WebhookController.createWebhook()",
|
|
633
|
+
"GET /api/tenants/webhooks -> WebhookController.listWebhooks()",
|
|
634
|
+
"GET /api/tenants/webhooks/logs -> WebhookController.getDeliveryLogs()",
|
|
635
|
+
"POST|GET|PUT|DELETE /api/tenants/smtp-config/... -> SmtpConfigController [CRUD + test/test-email/default/sync-keycloak]",
|
|
636
|
+
"GET|POST /api/tenants/sessions/... -> SessionController [~20 endpoints: listing/search/termination, self-service 'me' endpoints]",
|
|
637
|
+
"POST /api/tenants/policy/mapping/{policyType} -> PolicyMappingController.assignPolicy()",
|
|
638
|
+
"POST /api/tenants/policy/mapping/{policyType}/{policyId} -> PolicyMappingController.addSingleAssignment()",
|
|
639
|
+
"PUT /api/tenants/policy/mapping/{id} -> PolicyMappingController.updateAssignment()",
|
|
640
|
+
"GET /api/tenants/policy/mapping -> PolicyMappingController.getAllMappings()",
|
|
641
|
+
"GET /api/tenants/policy/mapping/{id} -> PolicyMappingController.getMappingById()",
|
|
642
|
+
"DELETE /api/tenants/policy/mapping/{id} -> PolicyMappingController.deleteAssignment()",
|
|
643
|
+
"POST /api/tenants/policy/mapping/groups -> PolicyMappingController.assignPoliciesToGroups()",
|
|
644
|
+
"GET /api/tenants/.well-known/sfbrowser-bootstrap-jwks -> SfBrowserBootstrapJwksController.jwks() [public]",
|
|
645
|
+
"GET /api/tenants/subscriptions/{tenantId} -> SubscriptionController.getActiveSubscription()",
|
|
646
|
+
"GET /api/tenants/subscriptions/{tenantId}/history -> SubscriptionController.getSubscriptionHistory()",
|
|
647
|
+
"GET /api/tenants/subscriptions/{tenantId}/history/filter -> SubscriptionController.getSubscriptionHistoryByAction()",
|
|
648
|
+
"PUT /api/tenants/subscriptions/{tenantId}/upgrade -> SubscriptionController.upgradeSubscription()",
|
|
649
|
+
"PUT /api/tenants/subscriptions/{tenantId}/convert-trial -> SubscriptionController.convertTrial()",
|
|
650
|
+
"PUT /api/tenants/subscriptions/{tenantId}/cancel -> SubscriptionController.cancelSubscription()",
|
|
651
|
+
"POST /api/tenants/policy/rules-engine/bundles -> RulesEnginePolicyController.updateGlobalRulesEngineBundle() [pipeline-publisher-only]",
|
|
652
|
+
"POST /api/tenants/policy/rules-engine/bundles/{tenantId} -> RulesEnginePolicyController.updateTenantRulesEngineBundle()",
|
|
653
|
+
"GET /api/tenants/policy/rules-engine -> RulesEnginePolicyController.getRulesEngineBundle()",
|
|
654
|
+
"POST /api/tenants/network-policy -> NetworkPolicyController.create()",
|
|
655
|
+
"GET /api/tenants/network-policy/{id} -> NetworkPolicyController.getById()",
|
|
656
|
+
"GET /api/tenants/network-policy -> NetworkPolicyController.getAll()",
|
|
657
|
+
"PUT /api/tenants/network-policy/{id} -> NetworkPolicyController.update()",
|
|
658
|
+
"DELETE /api/tenants/network-policy/{id} -> NetworkPolicyController.delete()",
|
|
659
|
+
"GET /api/tenants/notifications -> NotificationController.getUserNotifications()",
|
|
660
|
+
"GET /api/tenants/notifications/unread-count -> NotificationController.getUnreadCount()",
|
|
661
|
+
"PATCH /api/tenants/notifications/{notificationId}/read -> NotificationController.markAsRead()",
|
|
662
|
+
"PATCH /api/tenants/notifications/read-all -> NotificationController.markAllAsRead()",
|
|
663
|
+
"GET /api/tenants/notifications/preferences -> NotificationController.getPreferences()",
|
|
664
|
+
"PUT /api/tenants/notifications/preferences -> NotificationController.updatePreferences()"
|
|
665
|
+
],
|
|
666
|
+
"calls_services": [],
|
|
667
|
+
"kafka_produces": ["policy-events", "extension-policy-changed"],
|
|
668
|
+
"kafka_consumes": ["extension-approval-decided"],
|
|
669
|
+
"keycloak_realm": "not_found (no static realm; owns the realm-provisioning logic itself — creates a new Keycloak realm per tenant onboarding, stored in Tenant.realmName)",
|
|
670
|
+
"keycloak_client_id": "not_found (env-driven ${KC_ADMIN_CLIENT_ID}); sfbrowser.bootstrap.master-realm-issuer for SfBrowser bootstrap service-account flow",
|
|
671
|
+
"flyway_next_version": "V64 (highest found: V63__tenant_principal_gate.sql; full V1-V35 then gap, V41-V63 present, V36-V40 missing). NOTE: no Flyway Maven dependency declared despite this migrations directory — flagged inconsistency (consistent with the same pattern in sfn-iam-api and sfn-events-api).",
|
|
672
|
+
"test_framework": "JUnit 5 + Mockito (transitive) + Testcontainers (Postgres); 58 test classes incl. *Test, *IntegrationTest, *BaselineTest, *GuardFilterTest/*AccessGuardTest, *CoverageTest, *RegressionTest naming patterns",
|
|
673
|
+
"swagger_url": "/api/tenants/swagger-ui (springdoc.swagger-ui.path explicit; api-docs at /api/tenants/v3/api-docs)",
|
|
674
|
+
"spring_boot_version": "3.4.12",
|
|
675
|
+
"java_version": "17"
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
|
|
679
|
+
"infrastructure": {
|
|
680
|
+
"source_note": "Ports/hosts/credentials in this section were confirmed via a shared deployment .env supplied directly by the user in-conversation (not discovered by the repo file scan itself). Repo-scan-only findings from earlier phases are preserved alongside as cross-checks where they differ.",
|
|
681
|
+
"eureka": {
|
|
682
|
+
"port": 8761,
|
|
683
|
+
"internal_url": "http://localhost:8761/eureka/",
|
|
684
|
+
"register_with_eureka": true,
|
|
685
|
+
"fetch_registry": true,
|
|
686
|
+
"prefer_ip_address": true,
|
|
687
|
+
"instance_ip_address": "127.0.0.1"
|
|
688
|
+
},
|
|
689
|
+
"gateway": { "port": 8083, "service_name": "API-GATEWAY-SERVICE" },
|
|
690
|
+
"keycloak": {
|
|
691
|
+
"admin_server_url": "https://auth.motivitylabs.net",
|
|
692
|
+
"admin_realm": "master",
|
|
693
|
+
"admin_client_id": "admin-cli",
|
|
694
|
+
"admin_username": "admin",
|
|
695
|
+
"admin_password": "Co3mPl3xPq$5c0d3",
|
|
696
|
+
"realm_url": "https://auth.motivitylabs.net/realms/secufusion/protocol/openid-connect/token",
|
|
697
|
+
"redirect_url": "https://auth.motivitylabs.net/users/auth/token",
|
|
698
|
+
"jwk_set_uri": "https://auth.motivitylabs.net/realms/secufusion/protocol/openid-connect/certs",
|
|
699
|
+
"realm": "not_found as a single fixed application realm (platform is multi-tenant with per-tenant realms resolved dynamically from DB per request; 'master' above is only the admin-operations realm)"
|
|
700
|
+
},
|
|
701
|
+
"kafka": {
|
|
702
|
+
"port": 9092,
|
|
703
|
+
"host_shared_dev": "172.178.13.199 (from shared deployment env config; this is what KAFKA_SERVER actually resolves to in the shared dev environment)",
|
|
704
|
+
"host_local_override": "localhost (sfn-tenants-api's own local/.env.local.example shows a per-developer local override of localhost:9092, distinct from the shared dev value above)",
|
|
705
|
+
"host_docker": "not_found (no docker-compose.yml exists anywhere in the workspace)",
|
|
706
|
+
"kafka_schedule_time": 2
|
|
707
|
+
},
|
|
708
|
+
"postgres": {
|
|
709
|
+
"host": "48.194.68.89",
|
|
710
|
+
"port_local": 5432,
|
|
711
|
+
"db_name": "develop",
|
|
712
|
+
"username": "postgres",
|
|
713
|
+
"password": "Motivity@123",
|
|
714
|
+
"hikari_pool": {
|
|
715
|
+
"max_lifetime_ms": 540000,
|
|
716
|
+
"connection_timeout_ms": 30000,
|
|
717
|
+
"idle_timeout_ms": 300000,
|
|
718
|
+
"max_pool_size": 30,
|
|
719
|
+
"min_idle": 5,
|
|
720
|
+
"keepalive_time_ms": 300000,
|
|
721
|
+
"pool_name": "HikariPool",
|
|
722
|
+
"leak_detection_threshold_ms": 20000
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
"smtp": {
|
|
726
|
+
"note": "Real SMTP (Azure Communication Services), not Mailhog — no Mailhog container/config found anywhere in the workspace.",
|
|
727
|
+
"host": "smtp.azurecomm.net",
|
|
728
|
+
"port": 587,
|
|
729
|
+
"auth": true,
|
|
730
|
+
"starttls": true,
|
|
731
|
+
"username": "secufusion-dev-smtp-username",
|
|
732
|
+
"password": "Cgg8Q~QubDEAPlIqDn8s2azy126H0~oQ-IH-CbWb",
|
|
733
|
+
"from_address": "donotreply@motivitylabs.net"
|
|
734
|
+
},
|
|
735
|
+
"mailhog": { "smtp_port": 0, "ui_port": 0, "note": "not used by this platform — see infrastructure.smtp instead" },
|
|
736
|
+
"docker_network": "not_found (no docker-compose.yml exists anywhere in the workspace; services run individually against externally-hosted shared infra — remote Postgres, remote Kafka broker, hosted Keycloak, Azure Communication Services SMTP)",
|
|
737
|
+
"context_paths": {
|
|
738
|
+
"server_root": "/",
|
|
739
|
+
"iam": "/api/iam",
|
|
740
|
+
"tenants": "/api/tenants",
|
|
741
|
+
"events": "/api/events",
|
|
742
|
+
"policy": "/api/policy"
|
|
743
|
+
},
|
|
744
|
+
"product_service": {
|
|
745
|
+
"service_name": "PRODUCT-SERVICE",
|
|
746
|
+
"port": 8088,
|
|
747
|
+
"uri": "lb://PRODUCT-SERVICE",
|
|
748
|
+
"note": "Defined in the shared deployment env config (PRODUCT_SERVICE_NAME/PRODUCT_SERVICE_PORT/PRODUCT_SERVICE_URI) but NO corresponding repository exists anywhere in this 18-repo workspace, and no route to it was found in sfn-gateway-api's application.properties. See manual_review_flags."
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
|
|
752
|
+
"frontend": {
|
|
753
|
+
"repo": "sfn-web-ui",
|
|
754
|
+
"dev_port": 5173,
|
|
755
|
+
"framework": "React 19.1.1 (Vite 7.1.7 build tool, @vitejs/plugin-react + @vitejs/plugin-basic-ssl for local HTTPS)",
|
|
756
|
+
"ui_library": "MUI (Material UI) v7.3.5 + Emotion styling engine, custom theme in src/theme.js",
|
|
757
|
+
"state_management": "Redux Toolkit 2.10.1 + react-redux 9.2.0 (primary global state: src/store/slices/ — auth, feature, notification, tenant, ui); React Context used narrowly (src/contexts/TenantContextProvider.jsx)",
|
|
758
|
+
"api_base_url_pattern": "axios instance (src/utils/axiosConfig.js) with host-derived baseURL: relative/empty on localhost (routed through Vite dev proxy to VITE_API_TARGET env var), or https://<current-host>/ in production (same-origin)",
|
|
759
|
+
"auth_token_attachment": "axios request interceptor: tries dynamicKeycloakService.getToken() (silent refresh) first, falls back to Redux auth.token, sets Authorization: Bearer <token>. Matching response interceptor handles 401 via Keycloak refresh or POST /api/tenants/auth/token/refresh, with a failed-request queue to avoid parallel refresh storms.",
|
|
760
|
+
"tenant_id_in_requests": "Hybrid: normally tenantId is embedded as a URL path segment (derived from Redux auth.user.tenantId) per service's own routing convention. For MSSP 'acting tenant' impersonation, the same axios interceptor adds an X-Acting-Tenant header (for tenant-agnostic-path APIs) AND rewrites /api/tenants/... URL path segments to swap in the acting tenant's UUID (implemented in src/services/tenantImpersonation.js)."
|
|
761
|
+
},
|
|
762
|
+
|
|
763
|
+
"chrome_extension": {
|
|
764
|
+
"repo": "snf-browser-extn",
|
|
765
|
+
"manifest_version": 3,
|
|
766
|
+
"extension_version": "2.5.47 (manifest.json) / npm package version 2.0.1 (package.json) — two independent version numbers coexist",
|
|
767
|
+
"env_switch_mechanism": "chrome.storage.managed (populated via Windows registry/GPO/MDM per storage.managed_schema pointing at schema.json), read into chrome.storage.local. appEnv value ('DEV'|'UAT'|'TR'|'PROD'|'LOCAL') looked up against a hardcoded ENV_DOMAINS map in background.js (DEV->motivitylabs.net, UAT->agenticworkspace.ai, TR->motivitylabs.ai, PROD->secufusion.com; LOCAL routes to http://localhost:9000), combined with a per-tenant tenantHost managed-storage value to build the API base URL.",
|
|
768
|
+
"registry_key_path": "not_found in this repo's extension JS (background.js comments confirm registry reads for Safe Browsing detection, e.g. HKLM\\SOFTWARE\\Policies\\Google\\Chrome\\SafeBrowsingProtectionLevel, are handled by the separate native Windows agent (Go), not the extension; the actual registry key that seeds chrome.storage.managed is written by the MSI installer's Product.wxs, which is outside this repo's scanned scope)"
|
|
769
|
+
},
|
|
770
|
+
|
|
771
|
+
"auth_flow": {
|
|
772
|
+
"type": "OAuth2/OIDC via Keycloak, multi-tenant (one Keycloak realm provisioned per tenant by sfn-tenants-api at onboarding)",
|
|
773
|
+
"token_type": "JWT (Bearer)",
|
|
774
|
+
"tenant_id_extraction": "JWT 'iss' (issuer) claim is matched against a DB-stored per-tenant AuthProviderConfig to dynamically resolve a JwtDecoder (custom DbJwtAuthenticationManagerResolver pattern, repeated identically across sfn-auth-api, sfn-events-api, sfn-iam-api, sfn-policy-api, sfn-tenants-api). Caller tenant is then attached via TenantContextHolder. MSSP cross-tenant 'acting tenant' override is supported via X-Acting-Tenant header or a /api/tenants/{actingTenantId}/... URL prefix, validated by ParentChainAuthorizer.isCallerAncestorOrSelf + PrivacyLevelEnforcer.canImpersonate before being honored.",
|
|
775
|
+
"service_account_pattern": "Non-user JWTs authenticated by azp (authorized party) claim + a specific issuer, e.g. sfbrowser.bootstrap.expected-azp=sfbrowser-bootstrap validated against sfbrowser.bootstrap.master-realm-issuer for the SfBrowser network-config bootstrap flow, and a similar pattern for the rules-engine bundle publisher service account.",
|
|
776
|
+
"admin_role": "No single literal 'admin' role found. Closest equivalents: authority strings like 'all-tenants.create'/'all-tenants.update'/'all-tenants.delete' (platform-wide tenant management, sfn-tenants-api), TenantTypeEnum.PLATFORM_ADMIN (sfn-iam-api's requireCallerIsPlatformAdmin()), and role names 'MASTER MSSP ADMIN' / 'MSSP ADMIN' checked via requireMasterMsspAdmin()/requireTenantBundleAdmin() in sfn-policy-api's RulesEnginePolicyController.",
|
|
777
|
+
"tenant_oauth_client": {
|
|
778
|
+
"client_id": "secufusion",
|
|
779
|
+
"client_secret": "PuExdrzPcRCKS4aJgPcutpPuHMSQ0t0P",
|
|
780
|
+
"domain_extension": ".motivitylabs.net"
|
|
781
|
+
},
|
|
782
|
+
"platform_bootstrap_admin": {
|
|
783
|
+
"note": "Master/platform admin account seeded at environment bootstrap (from shared deployment env config, key names MASTER_ADMIN_*)",
|
|
784
|
+
"username": "softwareadmin",
|
|
785
|
+
"password": "Admin@123",
|
|
786
|
+
"first_name": "Software",
|
|
787
|
+
"last_name": "Admin",
|
|
788
|
+
"domain": "master.motivitylabs.net",
|
|
789
|
+
"email": "no-reply@motivitylabs.net",
|
|
790
|
+
"phone": "1234567890",
|
|
791
|
+
"tenant_name": "secufusion"
|
|
792
|
+
},
|
|
793
|
+
"azure_ad_sso": {
|
|
794
|
+
"note": "Ties to the root-level AzureSSO_deep.md / azureSS0_fix.md / sso_final-handoff.md / reconciliation_sso.md documentation of an active Azure AD SSO group-sync bug investigation (see manual_review_flags). alias=microsoft is used with sfn-iam-api's AzureGraphService/AzureGroupSyncService (Microsoft Graph SDK) for group membership sync.",
|
|
795
|
+
"client_id": "c4af10ad-da27-48ed-b95c-b726af96932b",
|
|
796
|
+
"client_secret": "3Fd8Q~Sp~87qO9l4KHdLorAW1y7Hl.FLz35w4c3d",
|
|
797
|
+
"sso_alias": "microsoft",
|
|
798
|
+
"mismatch_validation": true
|
|
799
|
+
}
|
|
800
|
+
},
|
|
801
|
+
|
|
802
|
+
"coding_patterns": {
|
|
803
|
+
"transactional_placement": "Method level only (never class level). @Transactional(readOnly = true) on read methods, plain @Transactional on write methods. Bulk-operation methods catch Exception per-item internally to accumulate a partial-failure result rather than aborting the whole batch.",
|
|
804
|
+
"exception_handling": "Custom unchecked exceptions (e.g. ResourceNotFoundException, ResourceConflictException) thrown from services, translated centrally by a @ControllerAdvice GlobalExceptionHandler into a flat JSON error body. Controllers additionally often wrap logic in try/catch to log with tenant/id context before rethrowing, which is somewhat redundant with the global handler.",
|
|
805
|
+
"tenant_id_passing": "Explicit String tenantId method parameter into services (not read from ThreadLocal inside the service itself); the controller resolves it once via TenantContextHolder.getActingTenantId() and passes it down.",
|
|
806
|
+
"dto_mapping": "Manual mapping via private convertToDTO()/convertToXxxDTO() helper methods in the service, building DTOs with Lombok @Builder. No MapStruct or ModelMapper used anywhere observed.",
|
|
807
|
+
"dto_suffix": "Inconsistent/mixed: DTO (uppercase) is most common for core domain response objects (e.g. IncidentDTO), Dto (mixed case) also appears (e.g. EventDto), Request is the standard suffix for inbound payloads (e.g. CreateIncidentRequest), Response appears in some newer modules (e.g. ExtensionApiKeyResponse).",
|
|
808
|
+
"lombok_style": "@Data + @Builder + @NoArgsConstructor + @AllArgsConstructor is the standard combination for request/response DTOs; @RequiredArgsConstructor for constructor-injected services; @Slf4j for logging on nearly every service/controller class.",
|
|
809
|
+
"response_wrapping": "Custom generic ResponseDto<T> { results, message, code } wrapper, always returned inside ResponseEntity<ResponseDto<T>>.",
|
|
810
|
+
"api_versioning": "None — base paths follow /api/<domain>/<resource> with no /v1/ or similar version segment anywhere across the platform.",
|
|
811
|
+
"test_naming_convention": "Not fully uniform. Predominantly camelCase descriptive sentence-style method names (e.g. refreshReplacesStaleSecretOnExistingRowAndPrimesCache); some newer test classes instead use snake_case sentence names with @Nested + @DisplayName groupings (e.g. blacklisted_extension_is_blocked_regardless_of_risk). Class-name suffixes are meaningful and consistent: *Test (unit), *IntegrationTest (Testcontainers-backed), *BaselineTest / *RegressionTest / *GuardrailTest (behavior-pinning regression tests), *ContractTest (Kafka payload contracts), *MigrationTest (Flyway migration verification).",
|
|
812
|
+
"assertion_library": "AssertJ (assertThat(...)) is the house standard; plain JUnit assertEquals/assertTrue not used in the sampled files.",
|
|
813
|
+
"query_style": "Mixed within the same repository interfaces: derived query methods (Spring Data naming, incl. nested-property navigation like findByXAndTenant_TenantID), plus @Query using both JPQL and native SQL (native used especially for optional-filter idioms and GROUP BY/aggregate stats queries).",
|
|
814
|
+
"tenant_filter_pattern": "No single universal mechanism. Combination of: (1) explicit tenantId predicate inside @Query/derived repository methods for most tenant-owned entities, (2) a centralized fail-closed HandlerInterceptor (TenantAccessInterceptor, most fully built out in sfn-iam-api and sfn-tenants-api) that validates any tenantId path-variable/query-param against the JWT-resolved acting tenant before the controller runs, (3) explicit service/controller-level TenantAccessGuard.requireCallerCanAccess(...) checks for endpoints addressed by a non-tenant id (e.g. a bare userId), and (4) a small number of repositories/entities are deliberately global/unscoped reference data (countries, package catalogs, scopes, etc.) — see manual_review_flags for entities where this determination could not be fully verified from code alone."
|
|
815
|
+
},
|
|
816
|
+
|
|
817
|
+
"golden_rules": {
|
|
818
|
+
"tenant_isolation": "Multi-layered, not enforced by a single mechanism: JWT issuer resolves caller tenant via per-tenant DB-stored Keycloak config (DbJwtAuthenticationManagerResolver, duplicated across 5 services) -> TenantContextHolder holds the acting tenant for the request -> optional MSSP 'acting tenant' impersonation override via X-Acting-Tenant header or /api/tenants/{tenantId}/... path prefix, gated by ParentChainAuthorizer.isCallerAncestorOrSelf + PrivacyLevelEnforcer.canImpersonate -> TenantAccessInterceptor/TenantAccessGuard perform a fail-closed check of any tenantId path-var/query-param against the acting tenant (most complete in sfn-iam-api and sfn-tenants-api) -> individual repository query methods additionally filter by tenantId for tenant-owned entities. No .rejected-patterns.json file exists anywhere in the workspace to source a canonical, explicitly-authored rule set from — this description is reconstructed from code, not an authored policy document.",
|
|
819
|
+
"rejected_patterns": [],
|
|
820
|
+
"authority_rules": [
|
|
821
|
+
"Spring Security @PreAuthorize(\"hasAuthority('<kebab-domain>.<action>')\") is the dominant authorization pattern, e.g. 'incidents.create', 'user-access.update', 'browser-policy.delete', 'subscription.update', 'extension-policy.create' — domain name matches the resource's URL segment, action is one of create/update/delete/read (read is often simply left unannotated rather than using a '.read' authority).",
|
|
822
|
+
"Platform-wide/cross-tenant operations bypass the per-domain authority scheme and instead use bespoke role/type checks: requirePlatformAdmin() and TenantTypeEnum.PLATFORM_ADMIN (sfn-iam-api), requireMasterMsspAdmin()/requireTenantBundleAdmin() checking for role strings 'MASTER MSSP ADMIN'/'MSSP ADMIN' (sfn-policy-api's rules-engine bundle publishing).",
|
|
823
|
+
"Service-account/machine-to-machine requests (browser extension bootstrap, rules-engine publisher, public device/extension enrollment endpoints) are authenticated by JWT azp + issuer matching rather than a user authority, and are explicitly permitAll'd in each service's SecurityConfig under /api/*/public/** or /.well-known/** paths."
|
|
824
|
+
],
|
|
825
|
+
"explicitly_banned": []
|
|
826
|
+
},
|
|
827
|
+
|
|
828
|
+
"current_state": {
|
|
829
|
+
"active_branch": "Multi-repo — no single active branch. sfn-events-api and sfn-web-ui were checked out on 'develop' at analysis time; snf-browser-extn was checked out on 'main'.",
|
|
830
|
+
"pending_acs": [],
|
|
831
|
+
"completed_acs": [],
|
|
832
|
+
"next_step": "not_found (no .secufusion-state.json exists anywhere in the workspace)"
|
|
833
|
+
},
|
|
834
|
+
|
|
835
|
+
"manual_review_flags": [
|
|
836
|
+
"sfn-auth-api and sfn-iam-api both define an AuthController with near-identical endpoints (GET /tenant-config/v1, GET /tenant-config, POST /login) and both own JPA entities for the same tables (Address, auth_provider_config, groups, roles, scopes, Tenant, Users). It is not determinable from code alone which service is canonical/current and which is legacy or superseded — recommend asking the team directly.",
|
|
837
|
+
"sfn-tenants-api and sfn-policy-api both implement a near-identical browser/network/extension/AI-policy domain (BrowserPolicyController, ExtensionPolicyController, NetworkPolicyController, LandingPageController, RulesEnginePolicyController, PolicyMappingController, NetworkConfigController, SfBrowserBootstrapJwksController — same class names, same entity/table names, same Kafka topics produced (policy-events, extension-policy-changed) and consumed (extension-approval-decided)). sfn-tenants-api exposes these under /api/tenants/policy/... and additionally owns Tenant lifecycle/onboarding; sfn-policy-api exposes the same functionality under its own /api/policy/... (or similar, context-path is env-driven and not confirmed literally) with no Tenant-ownership responsibilities and only a single smoke test. This strongly suggests one of these two either supersedes the other or was split out and the old copy was never removed — recommend asking the team which is authoritative before adding new policy functionality to either.",
|
|
838
|
+
"Four services (sfn-events-api, sfn-iam-api, sfn-policy-api, sfn-tenants-api) all declare `db/migration` Flyway SQL scripts (V1 through V37/V55/none-found/V63 respectively) but NONE of them declare a Flyway Maven dependency in pom.xml, and test profiles explicitly set spring.flyway.enabled=false. It is unclear from the code how/when these migrations are actually applied in real environments (a separate CI/ops migration step is the likely explanation, but no such script was found in sfn-devops-templates or sfn-devops-terraform during this pass) — recommend confirming the actual migration-application mechanism before assuming `mvn spring-boot:run` alone will produce a correct schema.",
|
|
839
|
+
"Extensive JPA entity/table overlap across sfn-auth-api, sfn-iam-api, sfn-events-api, sfn-policy-api, and sfn-tenants-api for a shared set of tables (Tenant, Users, roles, groups, auth_provider_config, browserpolicy, networkpolicy, extension_policy, policy_assignments, smtp_config, tenant_api_mappings, scopes, package/package_pricing/package_type, Address, api_flags, billing_cycle, country/cities/states/region, tenant_types) — consistent with all services sharing one physical Postgres schema rather than each owning an exclusive schema. True system-of-record ownership per table could not be determined from code alone (each service that maps an @Entity has read/write JPA access regardless of 'ownership'); the 'owns_tables' field in each service entry below reflects 'has a JPA @Entity mapping for', not verified exclusive ownership.",
|
|
840
|
+
"sfn-eureka-api has a literal spring.application.name mismatch between application.properties ('sfn-eureka-server') and application.yml ('discovery-server') — both files are loaded and Spring Boot's property-source precedence would need to be checked at runtime to know which one actually wins.",
|
|
841
|
+
"snf-browser-extn/manifest.json embeds a private RSA key in plaintext (the \"key\" field used to pin the extension's Chrome ID) directly in source control — this is a real security exposure independent of this cataloging task and worth flagging to the team.",
|
|
842
|
+
"sfn-gateway-api's JwtAuthServiceValidationFilter (JWT validation at the edge) and CorsGlobalConfiguration are both present in source but entirely commented out / dead code, and Spring Security's own auto-configuration is explicitly excluded (spring.autoconfigure.exclude=...SecurityAutoConfiguration) — the gateway currently appears to enforce no authentication of its own at the edge, relying entirely on each downstream service to validate the JWT independently. Confirm this is the intended design before treating the gateway as a security boundary.",
|
|
843
|
+
"sfn-notification-api and sfn-packages-api exist as repos (with README.md files matching the generic unfilled Azure DevOps template) but contain no pom.xml or any source code — they are stubs/placeholders despite their '-api' naming, not active services. Excluded from the microservices catalog above.",
|
|
844
|
+
"sfn-gateway-api's server.port/spring.application.name/Eureka defaultZone are all environment-variable placeholders with no literal default in application.properties; the only concrete values found (8080, API-GATEWAY-SERVICE, http://localhost:8761/eureka/) come from an unwired reference file (api-gateway.properties at the repo root) that is not demonstrably loaded by Spring at runtime — treat the port:8080 value in this JSON as a best-available data point, not a confirmed running value.",
|
|
845
|
+
"Root-level *.md files (AzureSSO_deep.md, azureSS0_fix.md, sso_final-handoff.md, reconciliation_sso.md, readmeGuideforSSo.md, postmanAzure.md, EXTENSION_AUTH_BUG_RCA.md) document an active/recent Azure AD SSO group-sync bug investigation and a browser-extension user-attribution bug — these were not deeply read for this pass (only characterized at a theme level) and may contain still-relevant context for anyone touching sfn-iam-api's Azure Graph integration or the extension's login flow.",
|
|
846
|
+
"PRODUCT_SERVICE_NAME/PRODUCT_SERVICE_PORT(8088)/PRODUCT_SERVICE_URI are defined in the shared deployment env config the user supplied, but no repository named anything like 'product' exists anywhere in this 18-repo workspace, and no route to PRODUCT-SERVICE was found in sfn-gateway-api's application.properties (only IAM/TENANT/EVENTS/POLICY routes exist there). Likely explanations: the repo isn't checked out in this workspace, was renamed/retired, or the env var is stale. Recommend asking the team.",
|
|
847
|
+
"The shared deployment env config also defines AUTH_SERVICE_URI=lb://AUTH-SERVICE, but sfn-gateway-api's own application.properties routes list (read directly from the repo) has no /api/auth/** route — only IAM/TENANT/EVENTS/POLICY are routed. Combined with the sfn-auth-api/sfn-iam-api duplicate-AuthController flag above, this is additional evidence that sfn-auth-api may be legacy/unrouted in the current gateway config.",
|
|
848
|
+
"Two literal port conflicts surfaced when cross-checking the shared deployment env config against values read directly from the repos: sfn-auth-api hardcodes server.port=8081 in application.properties (not wired to any env var) while the shared config defines AUTH_SERVICE_PORT=8087; and sfn-tenants-api's own local/.env.local.example defaults to port 9001 while the shared config defines TENANT_SERVICE_PORT=8085. Both are recorded as CONFLICT values in the affected microservice entries rather than silently picking one — recommend confirming which is actually live in each real environment."
|
|
849
|
+
]
|
|
850
|
+
}
|