Mobile apps derive the scheme from their package name, which may contain
an underscore. It cannot introduce an authority, so accepting it keeps
the rule as strong as the RFC 3986 grammar.
The user was looked up by email across the whole platform, so a client
belonging to one tenant could resolve a user of another. New users were also
placed in the tenant named by the provider response rather than in the tenant
that owns the client.
Both now follow the client's own tenant. System clients keep the platform-wide
behaviour they rely on.
A rejected deep link is otherwise invisible to support. AdminController
reuses the prevUri parameter and cookie names instead of its own copies,
and its redirect target moves into a helper so the read side is covered
by a test.
The scheme is restored from the oauth2_auth_request cookie, which the
client can replace, so checking it only while the authorization request
is built still let a forged cookie point the token redirect at any host.
Both handlers now re-check it on read, sharing the rule with
OAuth2AppTokenFactory.
The success handler keeps prevUri out of the base URL as well, so the
error redirect no longer appends /login to an in-app path.
A device-state attribute save can complete after its tenant was deleted.
The post-save WS-update callback then resolves an evicted tenant profile
via partitionService.resolve -> getRoutingInfo and throws
TenantNotFoundException uncaught on the ws-callback thread.
Guard resolve() in forwardToSubscriptionManagerService: a deleted tenant
has no partition to route to and no subscribers to notify, so skip the
forward instead of propagating. Mirrors the existing tenant-gone handling
in DefaultDeviceStateService.checkStates().
* feat: send pseudonymized install report (tenant/user hash, tb version) to IoT Hub
* refactor: source install-report tb version/edition from ProjectInfo
Use the existing ProjectInfo component (getProjectVersion/getProductType)
instead of an ad-hoc optional BuildProperties field and a hard-coded "CE"
edition, so PE reports its own product type and the version is the canonical
cleaned value. Constructor-injected via @RequiredArgsConstructor.
If iotHubInstalledItemService.save throws after the local entity was already created, the entity was orphaned — visible in the tenant but invisible to IoT Hub update/delete. Extracted the per-type entity-deletion dispatch from deleteInstalledItem into a deleteEntityForDescriptor helper and call it from doInstallVersion and registerDeviceInstall on tracking-save failure. registerDeviceInstall now also wraps reportVersionInstalled best-effort to match doInstallVersion.
The post-install Thread.sleep was driven by installTimeoutMs from the uploaded solution.json with no upper bound — a malicious or misconfigured template could pin an HTTP worker thread indefinitely. Cap it at a configurable max (default 60s) exposed as iot-hub.max-install-timeout-ms with an IOT_HUB_MAX_INSTALL_TIMEOUT_MS env override.
Stream entries through a fixed buffer and abort with IOException when the archive crosses any of three configurable thresholds: total uncompressed bytes, per-entry uncompressed bytes, or entry count. Defaults — 200 MiB total, 50 MiB per entry, 10 000 entries — and env-var overrides IOT_HUB_MAX_UNCOMPRESSED_ARCHIVE_BYTES / IOT_HUB_MAX_UNCOMPRESSED_ENTRY_BYTES / IOT_HUB_MAX_ARCHIVE_ENTRY_COUNT are exposed under iot-hub in thingsboard.yml.
Root entries other than WIDGET or SOLUTION_TEMPLATE should always be installed even when a prior version is already installed — only widgets and solution templates are deduplicated by item id at install-plan resolution time.
Route all parse-failure throws through a single parseFailure(action, itemType[, section], cause) helper so the user-facing wording lives in one place and update paths share the template with install paths.
- IotHubRestClient.getVersionFileData now streams the response and
enforces a configurable size cap (iot-hub.max-file-data-size-bytes,
env IOT_HUB_MAX_FILE_DATA_SIZE_BYTES, default 100 MiB). Oversized
payloads are rejected up front via Content-Length and mid-stream
for chunked transfers, preventing OOMs from unknown bodies.
- DefaultIotHubService guards every iotHubRestClient.getVersionInfo
call against a null response, and rejects update attempts where
the installed item's type does not match the new version's type
("Installed item type does not match the new version's item type.").
Solution-template descriptor is also populated with
tenantTelemetryKeys / tenantAttributeKeys captured during install.
Drops the empty updateDeviceProfile stub.
- Add a tenant-scoped findByTenantIdAndId + deleteByTenantIdAndId to
IotHubInstalledItemDao (and the bulk deleteByTenantIdAndIdIn JPA
query). IotHubInstalledItemServiceImpl now uses these so reads
and deletes always honour the tenant boundary.
- Database: add lts schema_update.sql + schema-entities-idx.sql
indexes on iot_hub_installed_item(tenant_id),
(tenant_id, item_type), and (tenant_id, item_id) for the new
tenant-scoped lookups.
- DefaultSolutionService: minor cleanup touched alongside.
- .gitignore trailing-newline fix; remove stale superpowers
plan/spec docs that have been folded into the implementation.