Browse Source

Merge branch 'master' of github.com:thingsboard/thingsboard into enhacements/air-quility-images

pull/9838/head
Dmitriymush 3 years ago
parent
commit
262cd9c8a4
  1. 14
      application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_preparing.md
  2. 2
      application/src/main/data/json/system/widget_types/battery_level.json
  3. 4
      application/src/main/data/json/system/widget_types/efficiency_card.json
  4. 6
      application/src/main/data/json/system/widget_types/efficiency_card_with_background.json
  5. 6
      application/src/main/data/json/system/widget_types/efficiency_chart_card.json
  6. 6
      application/src/main/data/json/system/widget_types/efficiency_chart_card_with_background.json
  7. 4
      application/src/main/data/json/system/widget_types/efficiency_progress_bar.json
  8. 6
      application/src/main/data/json/system/widget_types/efficiency_progress_bar_with_background.json
  9. 4
      application/src/main/data/json/system/widget_types/efficiency_range_chart.json
  10. 6
      application/src/main/data/json/system/widget_types/efficiency_range_chart_with_background.json
  11. 4
      application/src/main/data/json/system/widget_types/flow_rate_card.json
  12. 4
      application/src/main/data/json/system/widget_types/flow_rate_card_with_background.json
  13. 4
      application/src/main/data/json/system/widget_types/flow_rate_chart_card.json
  14. 4
      application/src/main/data/json/system/widget_types/flow_rate_chart_card_with_background.json
  15. 4
      application/src/main/data/json/system/widget_types/flow_rate_gauge.json
  16. 2
      application/src/main/data/json/system/widget_types/flow_rate_progress_bar.json
  17. 4
      application/src/main/data/json/system/widget_types/flow_rate_progress_bar_with_background.json
  18. 2
      application/src/main/data/json/system/widget_types/flow_rate_range_chart.json
  19. 4
      application/src/main/data/json/system/widget_types/flow_rate_range_chart_with_background.json
  20. 2
      application/src/main/data/json/system/widget_types/fluid_pressure_card.json
  21. 4
      application/src/main/data/json/system/widget_types/fluid_pressure_card_with_background.json
  22. 4
      application/src/main/data/json/system/widget_types/fluid_pressure_chart_card.json
  23. 4
      application/src/main/data/json/system/widget_types/fluid_pressure_chart_card_with_background.json
  24. 4
      application/src/main/data/json/system/widget_types/fluid_pressure_gauge.json
  25. 2
      application/src/main/data/json/system/widget_types/fluid_pressure_progress_bar.json
  26. 4
      application/src/main/data/json/system/widget_types/fluid_pressure_progress_bar_with_background.json
  27. 2
      application/src/main/data/json/system/widget_types/fluid_pressure_range_chart.json
  28. 4
      application/src/main/data/json/system/widget_types/fluid_pressure_range_chart_with_background.json
  29. 4
      application/src/main/data/json/system/widget_types/horizontal_efficiency_card.json
  30. 6
      application/src/main/data/json/system/widget_types/horizontal_efficiency_card_with_background.json
  31. 4
      application/src/main/data/json/system/widget_types/horizontal_flow_rate_card.json
  32. 4
      application/src/main/data/json/system/widget_types/horizontal_flow_rate_card_with_background.json
  33. 2
      application/src/main/data/json/system/widget_types/horizontal_fluid_pressure_card.json
  34. 4
      application/src/main/data/json/system/widget_types/horizontal_fluid_pressure_card_with_background.json
  35. 2
      application/src/main/data/json/system/widget_types/horizontal_power_consumption_card.json
  36. 4
      application/src/main/data/json/system/widget_types/horizontal_power_consumption_card_with_background.json
  37. 2
      application/src/main/data/json/system/widget_types/horizontal_pump_vibration_card.json
  38. 4
      application/src/main/data/json/system/widget_types/horizontal_pump_vibration_card_with_background.json
  39. 2
      application/src/main/data/json/system/widget_types/horizontal_rotational_speed_card.json
  40. 4
      application/src/main/data/json/system/widget_types/horizontal_rotational_speed_card_with_background.json
  41. 4
      application/src/main/data/json/system/widget_types/indoor_temperature_gauge.json
  42. 2
      application/src/main/data/json/system/widget_types/indoor_temperature_range_chart.json
  43. 4
      application/src/main/data/json/system/widget_types/indoor_temperature_range_chart_with_background.json
  44. 2
      application/src/main/data/json/system/widget_types/power_consumption_card.json
  45. 4
      application/src/main/data/json/system/widget_types/power_consumption_card_with_background.json
  46. 4
      application/src/main/data/json/system/widget_types/power_consumption_chart_card.json
  47. 4
      application/src/main/data/json/system/widget_types/power_consumption_chart_card_with_background.json
  48. 2
      application/src/main/data/json/system/widget_types/power_consumption_range_chart.json
  49. 4
      application/src/main/data/json/system/widget_types/power_consumption_range_chart_with_background.json
  50. 2
      application/src/main/data/json/system/widget_types/pump_vibration_card.json
  51. 4
      application/src/main/data/json/system/widget_types/pump_vibration_card_with_background.json
  52. 4
      application/src/main/data/json/system/widget_types/pump_vibration_chart_card.json
  53. 4
      application/src/main/data/json/system/widget_types/pump_vibration_chart_card_with_background.json
  54. 4
      application/src/main/data/json/system/widget_types/pump_vibration_range_chart.json
  55. 4
      application/src/main/data/json/system/widget_types/pump_vibration_range_chart_with_background.json
  56. 2
      application/src/main/data/json/system/widget_types/rotational_speed_card.json
  57. 4
      application/src/main/data/json/system/widget_types/rotational_speed_card_with_background.json
  58. 4
      application/src/main/data/json/system/widget_types/rotational_speed_chart_card.json
  59. 6
      application/src/main/data/json/system/widget_types/rotational_speed_chart_card_with_background.json
  60. 4
      application/src/main/data/json/system/widget_types/rotational_speed_gauge.json
  61. 2
      application/src/main/data/json/system/widget_types/rotational_speed_progress_bar.json
  62. 4
      application/src/main/data/json/system/widget_types/rotational_speed_progress_bar_with_background.json
  63. 2
      application/src/main/data/json/system/widget_types/rotational_speed_range_chart.json
  64. 4
      application/src/main/data/json/system/widget_types/rotational_speed_range_chart_with_background.json
  65. 6
      application/src/main/data/json/system/widget_types/simple_efficiency_chart_card.json
  66. 6
      application/src/main/data/json/system/widget_types/simple_efficiency_chart_card_with_background.json
  67. 4
      application/src/main/data/json/system/widget_types/simple_flow_rate_chart_card.json
  68. 4
      application/src/main/data/json/system/widget_types/simple_flow_rate_chart_card_with_background.json
  69. 4
      application/src/main/data/json/system/widget_types/simple_fluid_pressure_chart_card.json
  70. 4
      application/src/main/data/json/system/widget_types/simple_fluid_pressure_chart_card_with_background.json
  71. 4
      application/src/main/data/json/system/widget_types/simple_power_consumption_chart_card.json
  72. 4
      application/src/main/data/json/system/widget_types/simple_power_consumption_chart_card_with_background.json
  73. 4
      application/src/main/data/json/system/widget_types/simple_pump_vibration_chart_card.json
  74. 4
      application/src/main/data/json/system/widget_types/simple_pump_vibration_chart_card_with_background.json
  75. 4
      application/src/main/data/json/system/widget_types/simple_rotational_speed_chart_card.json
  76. 4
      application/src/main/data/json/system/widget_types/simple_rotational_speed_chart_card_with_background.json
  77. 4
      application/src/main/data/json/system/widget_types/temperature_gauge.json
  78. 2
      application/src/main/data/json/system/widget_types/temperature_range_chart.json
  79. 4
      application/src/main/data/json/system/widget_types/temperature_range_chart_with_background.json
  80. 2
      application/src/main/data/upgrade/3.6.1/schema_update.sql
  81. 6
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  82. 6
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  83. 24
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  84. 51
      application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java
  85. 4
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  86. 8
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  87. 8
      application/src/main/java/org/thingsboard/server/controller/ImageController.java
  88. 320
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
  89. 33
      application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsViolationResponse.java
  90. 4
      application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
  91. 7
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  92. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
  93. 6
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV1.java
  94. 6
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV1.java
  95. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java
  96. 7
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructorV1.java
  97. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java
  98. 7
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV1.java
  99. 49
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseResourceEdgeEventFetcher.java
  100. 34
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/SystemResourcesEdgeEventFetcher.java

14
application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_preparing.md

@ -70,10 +70,22 @@ Apply a similar update for the PostgreSQL service. Find the section:
And replace it with:
```text
volumes:
- tb-edge-postgres-data/:/var/lib/postgresql/data
- tb-edge-postgres-data:/var/lib/postgresql/data
...
```
Finally, please add next volumes section at the end of the file:
```text
...
volumes:
tb-edge-data:
name: tb-edge-data
tb-edge-logs:
name: tb-edge-logs
tb-edge-postgres-data:
name: tb-edge-postgres-data
```
##### Backup Database
Make a copy of the database volume before upgrading:

2
application/src/main/data/json/system/widget_types/battery_level.json

@ -17,7 +17,7 @@
"settingsDirective": "tb-battery-level-widget-settings",
"hasBasicMode": true,
"basicModeDirective": "tb-battery-level-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"batteryLevel\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 7;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 0;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"layout\":\"vertical_solid\",\"showValue\":true,\"autoScaleValueSize\":true,\"valueFont\":{\"family\":\"Roboto\",\"size\":20,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"24px\"},\"valueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"batteryLevelColor\":{\"color\":\"rgba(92, 223, 144, 1)\",\"type\":\"range\",\"rangeList\":[{\"from\":0,\"to\":25,\"color\":\"rgba(227, 71, 71, 1)\"},{\"from\":25,\"to\":50,\"color\":\"rgba(246, 206, 67, 1)\"},{\"from\":50,\"to\":100,\"color\":\"rgba(92, 223, 144, 1)\"}],\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"batteryShapeColor\":{\"color\":\"rgba(92, 223, 144, 0.32)\",\"type\":\"range\",\"rangeList\":[{\"from\":0,\"to\":25,\"color\":\"rgba(227, 71, 71, 0.32)\"},{\"from\":25,\"to\":50,\"color\":\"rgba(246, 206, 67, 0.32)\"},{\"from\":50,\"to\":100,\"color\":\"rgba(92, 223, 144, 0.32)\"}],\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"sectionsCount\":4},\"title\":\"Battery level\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"%\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"mdi:battery-high\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"18px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"batteryLevel\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 7;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 0;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"alarmFilterConfig\":{\"statusList\":[\"ACTIVE\"]}}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}},\"layout\":\"vertical_solid\",\"showValue\":true,\"autoScaleValueSize\":true,\"valueFont\":{\"family\":\"Roboto\",\"size\":20,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"24px\"},\"valueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"batteryLevelColor\":{\"type\":\"range\",\"color\":\"rgb(224, 224, 224)\",\"rangeList\":[{\"from\":null,\"to\":25,\"color\":\"rgba(227, 71, 71, 1)\"},{\"from\":25,\"to\":50,\"color\":\"rgba(246, 206, 67, 1)\"},{\"from\":50,\"to\":null,\"color\":\"rgba(92, 223, 144, 1)\"}],\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"batteryShapeColor\":{\"type\":\"range\",\"color\":\"rgba(224, 224, 224, 0.32)\",\"rangeList\":[{\"from\":null,\"to\":25,\"color\":\"rgba(227, 71, 71, 0.32)\"},{\"from\":25,\"to\":50,\"color\":\"rgba(246, 206, 67, 0.32)\"},{\"from\":50,\"to\":null,\"color\":\"rgba(92, 223, 144, 0.32)\"}],\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"sectionsCount\":4},\"title\":\"Battery level\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"%\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"configMode\":\"basic\",\"displayTimewindow\":true,\"margin\":\"0px\",\"borderRadius\":\"0px\",\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"showTitleIcon\":false,\"titleTooltip\":\"\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"titleIcon\":\"mdi:battery-high\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"18px\",\"timewindowStyle\":{\"showIcon\":true,\"iconSize\":\"14px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":null,\"weight\":null,\"style\":null,\"lineHeight\":\"1\"},\"color\":null},\"titleColor\":\"rgba(0, 0, 0, 0.87)\"}"
},
"externalId": null,
"tags": [

4
application/src/main/data/json/system/widget_types/efficiency_card.json

File diff suppressed because one or more lines are too long

6
application/src/main/data/json/system/widget_types/efficiency_card_with_background.json

File diff suppressed because one or more lines are too long

6
application/src/main/data/json/system/widget_types/efficiency_chart_card.json

File diff suppressed because one or more lines are too long

6
application/src/main/data/json/system/widget_types/efficiency_chart_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/efficiency_progress_bar.json

File diff suppressed because one or more lines are too long

6
application/src/main/data/json/system/widget_types/efficiency_progress_bar_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/efficiency_range_chart.json

File diff suppressed because one or more lines are too long

6
application/src/main/data/json/system/widget_types/efficiency_range_chart_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/flow_rate_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/flow_rate_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/flow_rate_chart_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/flow_rate_chart_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/flow_rate_gauge.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/flow_rate_progress_bar.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/flow_rate_progress_bar_with_background.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/flow_rate_range_chart.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/flow_rate_range_chart_with_background.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/fluid_pressure_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/fluid_pressure_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/fluid_pressure_chart_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/fluid_pressure_chart_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/fluid_pressure_gauge.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/fluid_pressure_progress_bar.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/fluid_pressure_progress_bar_with_background.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/fluid_pressure_range_chart.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/fluid_pressure_range_chart_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/horizontal_efficiency_card.json

File diff suppressed because one or more lines are too long

6
application/src/main/data/json/system/widget_types/horizontal_efficiency_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/horizontal_flow_rate_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/horizontal_flow_rate_card_with_background.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/horizontal_fluid_pressure_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/horizontal_fluid_pressure_card_with_background.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/horizontal_power_consumption_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/horizontal_power_consumption_card_with_background.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/horizontal_pump_vibration_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/horizontal_pump_vibration_card_with_background.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/horizontal_rotational_speed_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/horizontal_rotational_speed_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/indoor_temperature_gauge.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/indoor_temperature_range_chart.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/indoor_temperature_range_chart_with_background.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/power_consumption_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/power_consumption_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/power_consumption_chart_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/power_consumption_chart_card_with_background.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/power_consumption_range_chart.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/power_consumption_range_chart_with_background.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/pump_vibration_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/pump_vibration_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/pump_vibration_chart_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/pump_vibration_chart_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/pump_vibration_range_chart.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/pump_vibration_range_chart_with_background.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/rotational_speed_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/rotational_speed_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/rotational_speed_chart_card.json

File diff suppressed because one or more lines are too long

6
application/src/main/data/json/system/widget_types/rotational_speed_chart_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/rotational_speed_gauge.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/rotational_speed_progress_bar.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/rotational_speed_progress_bar_with_background.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/rotational_speed_range_chart.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/rotational_speed_range_chart_with_background.json

File diff suppressed because one or more lines are too long

6
application/src/main/data/json/system/widget_types/simple_efficiency_chart_card.json

File diff suppressed because one or more lines are too long

6
application/src/main/data/json/system/widget_types/simple_efficiency_chart_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/simple_flow_rate_chart_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/simple_flow_rate_chart_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/simple_fluid_pressure_chart_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/simple_fluid_pressure_chart_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/simple_power_consumption_chart_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/simple_power_consumption_chart_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/simple_pump_vibration_chart_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/simple_pump_vibration_chart_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/simple_rotational_speed_chart_card.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/simple_rotational_speed_chart_card_with_background.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/temperature_gauge.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/temperature_range_chart.json

File diff suppressed because one or more lines are too long

4
application/src/main/data/json/system/widget_types/temperature_range_chart_with_background.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/upgrade/3.6.1/schema_update.sql

@ -35,3 +35,5 @@ ALTER TABLE resource ADD COLUMN IF NOT EXISTS external_id uuid;
CREATE INDEX IF NOT EXISTS idx_resource_etag ON resource(tenant_id, etag);
-- RESOURCES UPDATE END
CREATE INDEX IF NOT EXISTS idx_edge_event_tenant_id_edge_id_created_time ON edge_event(tenant_id, edge_id, created_time DESC);

6
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -68,6 +68,7 @@ import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor;
@ -455,6 +456,11 @@ public class ActorSystemContext {
@Getter
private WidgetTypeService widgetTypeService;
@Lazy
@Autowired(required = false)
@Getter
private EntityService entityService;
@Value("${actors.session.max_concurrent_sessions_per_device:1}")
@Getter
private long maxConcurrentSessionsPerDevice;

6
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -84,6 +84,7 @@ import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.nosql.CassandraStatementTask;
import org.thingsboard.server.dao.nosql.TbResultSetFuture;
@ -896,6 +897,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getApiUsageStateService();
}
@Override
public EntityService getEntityService() {
return mainCtx.getEntityService();
}
private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) {
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("ruleNodeId", ruleNodeId.toString());

24
application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java

@ -32,14 +32,12 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.header.writers.StaticHeadersWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
@ -63,7 +61,7 @@ import java.util.List;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Order(SecurityProperties.BASIC_AUTH_ORDER)
@TbCoreComponent
public class ThingsboardSecurityConfiguration {
@ -79,7 +77,7 @@ public class ThingsboardSecurityConfiguration {
public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token";
protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**", "/api/license/**"};
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**";
public static final String WS_ENTRY_POINT = "/api/ws/**";
public static final String MAIL_OAUTH2_PROCESSING_ENTRY_POINT = "/api/admin/mail/oauth2/code";
public static final String DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT = "/api/device-connectivity/mqtts/certificate/download";
@ -115,10 +113,6 @@ public class ThingsboardSecurityConfiguration {
@Qualifier("jwtHeaderTokenExtractor")
private TokenExtractor jwtHeaderTokenExtractor;
@Autowired
@Qualifier("jwtQueryTokenExtractor")
private TokenExtractor jwtQueryTokenExtractor;
@Autowired private AuthenticationManager authenticationManager;
@Autowired private RateLimitProcessingFilter rateLimitProcessingFilter;
@ -150,7 +144,7 @@ public class ThingsboardSecurityConfiguration {
protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception {
List<String> pathsToSkip = new ArrayList<>(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS));
pathsToSkip.addAll(Arrays.asList(WS_TOKEN_BASED_AUTH_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT,
pathsToSkip.addAll(Arrays.asList(WS_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT,
PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT, MAIL_OAUTH2_PROCESSING_ENTRY_POINT,
DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT));
SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
@ -167,15 +161,6 @@ public class ThingsboardSecurityConfiguration {
return filter;
}
@Bean
protected JwtTokenAuthenticationProcessingFilter buildWsJwtTokenAuthenticationProcessingFilter() throws Exception {
AntPathRequestMatcher matcher = new AntPathRequestMatcher(WS_TOKEN_BASED_AUTH_ENTRY_POINT);
JwtTokenAuthenticationProcessingFilter filter
= new JwtTokenAuthenticationProcessingFilter(failureHandler, jwtQueryTokenExtractor, matcher);
filter.setAuthenticationManager(this.authenticationManager);
return filter;
}
@Bean
public AuthenticationManager authenticationManager(ObjectPostProcessor<Object> objectPostProcessor) throws Exception {
DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
@ -229,7 +214,7 @@ public class ThingsboardSecurityConfiguration {
.antMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points
.and()
.authorizeRequests()
.antMatchers(WS_TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected WebSocket API End-points
.antMatchers(WS_ENTRY_POINT).permitAll() // WebSocket API End-points
.antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points
.and()
.exceptionHandling().accessDeniedHandler(restAccessDeniedHandler)
@ -238,7 +223,6 @@ public class ThingsboardSecurityConfiguration {
.addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class);
if (oauth2Configuration != null) {
http.oauth2Login()

51
application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java

@ -19,25 +19,13 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.HandshakeInterceptor;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.controller.plugin.TbWebSocketHandler;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.Map;
@Configuration
@TbCoreComponent
@ -46,8 +34,9 @@ import java.util.Map;
@Slf4j
public class WebSocketConfiguration implements WebSocketConfigurer {
public static final String WS_PLUGIN_PREFIX = "/api/ws/plugins/";
private static final String WS_PLUGIN_MAPPING = WS_PLUGIN_PREFIX + "**";
public static final String WS_API_ENDPOINT = "/api/ws";
public static final String WS_PLUGINS_ENDPOINT = "/api/ws/plugins/";
private static final String WS_API_MAPPING = "/api/ws/**";
private final WebSocketHandler wsHandler;
@ -65,39 +54,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer {
log.error("TbWebSocketHandler expected but [{}] provided", wsHandler);
throw new RuntimeException("TbWebSocketHandler expected but " + wsHandler + " provided");
}
registry.addHandler(wsHandler, WS_PLUGIN_MAPPING).setAllowedOriginPatterns("*")
.addInterceptors(new HttpSessionHandshakeInterceptor(), new HandshakeInterceptor() {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
SecurityUser user = null;
try {
user = getCurrentUser();
} catch (ThingsboardException ex) {
}
if (user == null) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return false;
} else {
return true;
}
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception exception) {
//Do nothing
}
});
registry.addHandler(wsHandler, WS_API_MAPPING).setAllowedOriginPatterns("*");
}
protected SecurityUser getCurrentUser() throws ThingsboardException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof SecurityUser) {
return (SecurityUser) authentication.getPrincipal();
} else {
throw new ThingsboardException("You aren't authorized to perform this operation!", ThingsboardErrorCode.AUTHENTICATION);
}
}
}

4
application/src/main/java/org/thingsboard/server/controller/AdminController.java

@ -170,7 +170,7 @@ public class AdminController extends BaseController {
@ResponseBody
public SecuritySettings getSecuritySettings() throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
return checkNotNull(systemSecurityService.getSecuritySettings(TenantId.SYS_TENANT_ID));
return checkNotNull(systemSecurityService.getSecuritySettings());
}
@ApiOperation(value = "Update Security Settings (saveSecuritySettings)",
@ -182,7 +182,7 @@ public class AdminController extends BaseController {
@ApiParam(value = "A JSON value representing the Security Settings.")
@RequestBody SecuritySettings securitySettings) throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE);
securitySettings = checkNotNull(systemSecurityService.saveSecuritySettings(TenantId.SYS_TENANT_ID, securitySettings));
securitySettings = checkNotNull(systemSecurityService.saveSecuritySettings(securitySettings));
return securitySettings;
}

8
application/src/main/java/org/thingsboard/server/controller/AuthController.java

@ -115,7 +115,7 @@ public class AuthController extends BaseController {
if (!passwordEncoder.matches(currentPassword, userCredentials.getPassword())) {
throw new ThingsboardException("Current password doesn't match!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
systemSecurityService.validatePassword(securityUser.getTenantId(), newPassword, userCredentials);
systemSecurityService.validatePassword(newPassword, userCredentials);
if (passwordEncoder.matches(newPassword, userCredentials.getPassword())) {
throw new ThingsboardException("New password should be different from existing!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
@ -135,7 +135,7 @@ public class AuthController extends BaseController {
@ResponseBody
public UserPasswordPolicy getUserPasswordPolicy() throws ThingsboardException {
SecuritySettings securitySettings =
checkNotNull(systemSecurityService.getSecuritySettings(TenantId.SYS_TENANT_ID));
checkNotNull(systemSecurityService.getSecuritySettings());
return securitySettings.getPasswordPolicy();
}
@ -237,7 +237,7 @@ public class AuthController extends BaseController {
HttpServletRequest request) throws ThingsboardException {
String activateToken = activateRequest.getActivateToken();
String password = activateRequest.getPassword();
systemSecurityService.validatePassword(TenantId.SYS_TENANT_ID, password, null);
systemSecurityService.validatePassword(password, null);
String encodedPassword = passwordEncoder.encode(password);
UserCredentials credentials = userService.activateUserCredentials(TenantId.SYS_TENANT_ID, activateToken, encodedPassword);
User user = userService.findUserById(TenantId.SYS_TENANT_ID, credentials.getUserId());
@ -274,7 +274,7 @@ public class AuthController extends BaseController {
String password = resetPasswordRequest.getPassword();
UserCredentials userCredentials = userService.findUserCredentialsByResetToken(TenantId.SYS_TENANT_ID, resetToken);
if (userCredentials != null) {
systemSecurityService.validatePassword(TenantId.SYS_TENANT_ID, password, userCredentials);
systemSecurityService.validatePassword(password, userCredentials);
if (passwordEncoder.matches(password, userCredentials.getPassword())) {
throw new ThingsboardException("New password should be different from existing!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}

8
application/src/main/java/org/thingsboard/server/controller/ImageController.java

@ -50,9 +50,10 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.resource.ImageCacheKey;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.service.validator.ResourceDataValidator;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.dao.resource.ImageCacheKey;
import org.thingsboard.server.service.resource.TbImageService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
@ -77,6 +78,8 @@ public class ImageController extends BaseController {
private final ImageService imageService;
private final TbImageService tbImageService;
private final ResourceDataValidator resourceValidator;
@Value("${cache.image.systemImagesBrowserTtlInMinutes:0}")
private int systemImagesBrowserTtlInMinutes;
@Value("${cache.image.tenantImagesBrowserTtlInMinutes:0}")
@ -94,6 +97,7 @@ public class ImageController extends BaseController {
TbResource image = new TbResource();
image.setTenantId(user.getTenantId());
accessControlService.checkPermission(user, Resource.TB_RESOURCE, Operation.CREATE, null, image);
resourceValidator.validateResourceSize(user.getTenantId(), null, file.getSize());
image.setFileName(file.getOriginalFilename());
if (StringUtils.isNotEmpty(title)) {
@ -115,6 +119,8 @@ public class ImageController extends BaseController {
@PathVariable String key,
@RequestPart MultipartFile file) throws Exception {
TbResourceInfo imageInfo = checkImageInfo(type, key, Operation.WRITE);
resourceValidator.validateResourceSize(getTenantId(), imageInfo.getId(), file.getSize());
TbResource image = new TbResource(imageInfo);
image.setData(file.getBytes());
image.setFileName(file.getOriginalFilename());

320
application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java

@ -15,12 +15,17 @@
*/
package org.thingsboard.server.controller.plugin;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.BeanCreationNotAllowedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.PongMessage;
@ -28,6 +33,7 @@ import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.adapter.NativeWebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.id.CustomerId;
@ -39,49 +45,59 @@ import org.thingsboard.server.config.WebSocketConfiguration;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.util.limits.RateLimitService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.jwt.JwtAuthenticationProvider;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.subscription.SubscriptionErrorCode;
import org.thingsboard.server.service.ws.AuthCmd;
import org.thingsboard.server.service.ws.SessionEvent;
import org.thingsboard.server.service.ws.WebSocketMsgEndpoint;
import org.thingsboard.server.service.ws.WebSocketService;
import org.thingsboard.server.service.ws.WebSocketSessionRef;
import org.thingsboard.server.service.ws.WebSocketSessionType;
import org.thingsboard.server.service.ws.WsCommandsWrapper;
import org.thingsboard.server.service.ws.notification.cmd.NotificationCmdsWrapper;
import org.thingsboard.server.service.ws.telemetry.cmd.TelemetryCmdsWrapper;
import javax.annotation.PostConstruct;
import javax.websocket.RemoteEndpoint;
import javax.websocket.SendHandler;
import javax.websocket.SendResult;
import javax.websocket.Session;
import java.io.IOException;
import java.net.URI;
import java.security.InvalidParameterException;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static org.thingsboard.server.service.ws.DefaultWebSocketService.NUMBER_OF_PING_ATTEMPTS;
@Service
@TbCoreComponent
@Slf4j
@RequiredArgsConstructor
public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocketMsgEndpoint {
private final ConcurrentMap<String, SessionMetaData> internalSessionMap = new ConcurrentHashMap<>();
private final ConcurrentMap<String, String> externalSessionMap = new ConcurrentHashMap<>();
@Autowired @Lazy
private WebSocketService webSocketService;
@Autowired
private TbTenantProfileCache tenantProfileCache;
@Autowired
private RateLimitService rateLimitService;
@Autowired
private JwtAuthenticationProvider authenticationProvider;
@Value("${server.ws.send_timeout:5000}")
private long sendTimeout;
@ -89,6 +105,8 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
private long pingTimeout;
@Value("${server.ws.max_queue_messages_per_session:1000}")
private int wsMaxQueueMessagesPerSession;
@Value("${server.ws.auth_timeout_ms:10000}")
private int authTimeoutMs;
private final ConcurrentMap<String, WebSocketSessionRef> blacklistedSessions = new ConcurrentHashMap<>();
@ -97,28 +115,97 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
private final ConcurrentMap<UserId, Set<String>> regularUserSessionsMap = new ConcurrentHashMap<>();
private final ConcurrentMap<UserId, Set<String>> publicUserSessionsMap = new ConcurrentHashMap<>();
private Cache<String, SessionMetaData> pendingSessions;
@PostConstruct
private void init() {
pendingSessions = Caffeine.newBuilder()
.expireAfterWrite(authTimeoutMs, TimeUnit.MILLISECONDS)
.<String, SessionMetaData>removalListener((sessionId, sessionMd, removalCause) -> {
if (removalCause == RemovalCause.EXPIRED && sessionMd != null) {
try {
close(sessionMd.sessionRef, CloseStatus.POLICY_VIOLATION);
} catch (IOException e) {
log.warn("IO error", e);
}
}
})
.build();
}
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
try {
SessionMetaData sessionMd = internalSessionMap.get(session.getId());
if (sessionMd != null) {
log.trace("[{}][{}] Processing {}", sessionMd.sessionRef.getSecurityCtx().getTenantId(), session.getId(), message.getPayload());
webSocketService.handleWebSocketMsg(sessionMd.sessionRef, message.getPayload());
} else {
SessionMetaData sessionMd = getSessionMd(session.getId());
if (sessionMd == null) {
log.trace("[{}] Failed to find session", session.getId());
session.close(CloseStatus.SERVER_ERROR.withReason("Session not found!"));
return;
}
sessionMd.onMsg(message.getPayload());
} catch (IOException e) {
log.warn("IO error", e);
}
}
void processMsg(SessionMetaData sessionMd, String msg) throws IOException {
WebSocketSessionRef sessionRef = sessionMd.sessionRef;
WsCommandsWrapper cmdsWrapper;
try {
switch (sessionRef.getSessionType()) {
case GENERAL:
cmdsWrapper = JacksonUtil.fromString(msg, WsCommandsWrapper.class);
break;
case TELEMETRY:
cmdsWrapper = JacksonUtil.fromString(msg, TelemetryCmdsWrapper.class).toCommonCmdsWrapper();
break;
case NOTIFICATIONS:
cmdsWrapper = JacksonUtil.fromString(msg, NotificationCmdsWrapper.class).toCommonCmdsWrapper();
break;
default:
return;
}
} catch (Exception e) {
log.debug("{} Failed to decode subscription cmd: {}", sessionRef, e.getMessage(), e);
if (sessionRef.getSecurityCtx() != null) {
webSocketService.sendError(sessionRef, 1, SubscriptionErrorCode.BAD_REQUEST, "Failed to parse the payload");
} else {
close(sessionRef, CloseStatus.BAD_DATA.withReason(e.getMessage()));
}
return;
}
if (sessionRef.getSecurityCtx() != null) {
log.trace("{} Processing {}", sessionRef, msg);
webSocketService.handleCommands(sessionRef, cmdsWrapper);
} else {
AuthCmd authCmd = cmdsWrapper.getAuthCmd();
if (authCmd == null) {
close(sessionRef, CloseStatus.POLICY_VIOLATION.withReason("Auth cmd is missing"));
return;
}
log.trace("{} Authenticating session", sessionRef);
SecurityUser securityCtx;
try {
securityCtx = authenticationProvider.authenticate(authCmd.getToken());
} catch (Exception e) {
close(sessionRef, CloseStatus.BAD_DATA.withReason(e.getMessage()));
return;
}
sessionRef.setSecurityCtx(securityCtx);
pendingSessions.invalidate(sessionMd.session.getId());
establishSession(sessionMd.session, sessionRef, sessionMd);
webSocketService.handleCommands(sessionRef, cmdsWrapper);
}
}
@Override
protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
try {
SessionMetaData sessionMd = internalSessionMap.get(session.getId());
SessionMetaData sessionMd = getSessionMd(session.getId());
if (sessionMd != null) {
log.trace("[{}][{}] Processing pong response {}", sessionMd.sessionRef.getSecurityCtx().getTenantId(), session.getId(), message.getPayload());
log.trace("{} Processing pong response {}", sessionMd.sessionRef, message.getPayload());
sessionMd.processPongMessage(System.currentTimeMillis());
} else {
log.trace("[{}] Failed to find session", session.getId());
@ -139,23 +226,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
nativeSession.getAsyncRemote().setSendTimeout(sendTimeout);
}
}
String internalSessionId = session.getId();
WebSocketSessionRef sessionRef = toRef(session);
String externalSessionId = sessionRef.getSessionId();
if (!checkLimits(session, sessionRef)) {
return;
}
var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef);
int wsTenantProfileQueueLimit = tenantProfileConfiguration != null ?
tenantProfileConfiguration.getWsMsgQueueLimitPerSession() : wsMaxQueueMessagesPerSession;
internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef,
(wsTenantProfileQueueLimit > 0 && wsTenantProfileQueueLimit < wsMaxQueueMessagesPerSession) ?
wsTenantProfileQueueLimit : wsMaxQueueMessagesPerSession));
externalSessionMap.put(externalSessionId, internalSessionId);
processInWebSocketService(sessionRef, SessionEvent.onEstablished());
log.info("[{}][{}][{}] Session is opened from address: {}", sessionRef.getSecurityCtx().getTenantId(), externalSessionId, session.getId(), session.getRemoteAddress());
log.debug("[{}][{}] Session opened from address: {}", sessionRef.getSessionId(), session.getId(), session.getRemoteAddress());
establishSession(session, sessionRef, null);
} catch (InvalidParameterException e) {
log.warn("[{}] Failed to start session", session.getId(), e);
session.close(CloseStatus.BAD_DATA.withReason(e.getMessage()));
@ -165,10 +238,36 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
}
private void establishSession(WebSocketSession session, WebSocketSessionRef sessionRef, SessionMetaData sessionMd) throws IOException {
if (sessionRef.getSecurityCtx() != null) {
if (!checkLimits(session, sessionRef)) {
return;
}
int maxMsgQueueSize = Optional.ofNullable(getTenantProfileConfiguration(sessionRef))
.map(DefaultTenantProfileConfiguration::getWsMsgQueueLimitPerSession)
.filter(profileLimit -> profileLimit > 0 && profileLimit < wsMaxQueueMessagesPerSession)
.orElse(wsMaxQueueMessagesPerSession);
if (sessionMd == null) {
sessionMd = new SessionMetaData(session, sessionRef);
}
sessionMd.setMaxMsgQueueSize(maxMsgQueueSize);
internalSessionMap.put(session.getId(), sessionMd);
externalSessionMap.put(sessionRef.getSessionId(), session.getId());
processInWebSocketService(sessionRef, SessionEvent.onEstablished());
log.info("[{}][{}][{}][{}] Session established from address: {}", sessionRef.getSecurityCtx().getTenantId(),
sessionRef.getSecurityCtx().getId(), sessionRef.getSessionId(), session.getId(), session.getRemoteAddress());
} else {
sessionMd = new SessionMetaData(session, sessionRef);
pendingSessions.put(session.getId(), sessionMd);
externalSessionMap.put(sessionRef.getSessionId(), session.getId());
}
}
@Override
public void handleTransportError(WebSocketSession session, Throwable tError) throws Exception {
super.handleTransportError(session, tError);
SessionMetaData sessionMd = internalSessionMap.get(session.getId());
SessionMetaData sessionMd = getSessionMd(session.getId());
if (sessionMd != null) {
processInWebSocketService(sessionMd.sessionRef, SessionEvent.onError(tError));
} else {
@ -181,63 +280,87 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
super.afterConnectionClosed(session, closeStatus);
SessionMetaData sessionMd = internalSessionMap.remove(session.getId());
if (sessionMd == null) {
sessionMd = pendingSessions.asMap().remove(session.getId());
}
if (sessionMd != null) {
cleanupLimits(session, sessionMd.sessionRef);
externalSessionMap.remove(sessionMd.sessionRef.getSessionId());
processInWebSocketService(sessionMd.sessionRef, SessionEvent.onClosed());
log.info("[{}][{}][{}] Session is closed", sessionMd.sessionRef.getSecurityCtx().getTenantId(), sessionMd.sessionRef.getSessionId(), session.getId());
if (sessionMd.sessionRef.getSecurityCtx() != null) {
cleanupLimits(session, sessionMd.sessionRef);
processInWebSocketService(sessionMd.sessionRef, SessionEvent.onClosed());
}
log.info("{} Session is closed", sessionMd.sessionRef);
} else {
log.info("[{}] Session is closed", session.getId());
}
}
private void processInWebSocketService(WebSocketSessionRef sessionRef, SessionEvent event) {
if (sessionRef.getSecurityCtx() == null) {
return;
}
try {
webSocketService.handleWebSocketSessionEvent(sessionRef, event);
webSocketService.handleSessionEvent(sessionRef, event);
} catch (BeanCreationNotAllowedException e) {
log.warn("[{}] Failed to close session due to possible shutdown state", sessionRef.getSessionId());
log.warn("{} Failed to close session due to possible shutdown state", sessionRef);
}
}
private WebSocketSessionRef toRef(WebSocketSession session) throws IOException {
URI sessionUri = session.getUri();
String path = sessionUri.getPath();
path = path.substring(WebSocketConfiguration.WS_PLUGIN_PREFIX.length());
if (path.length() == 0) {
throw new IllegalArgumentException("URL should contain plugin token!");
private WebSocketSessionRef toRef(WebSocketSession session) {
String path = session.getUri().getPath();
WebSocketSessionType sessionType;
if (path.equals(WebSocketConfiguration.WS_API_ENDPOINT)) {
sessionType = WebSocketSessionType.GENERAL;
} else {
String type = StringUtils.substringAfter(path, WebSocketConfiguration.WS_PLUGINS_ENDPOINT);
sessionType = WebSocketSessionType.forName(type)
.orElseThrow(() -> new InvalidParameterException("Unknown session type"));
}
String[] pathElements = path.split("/");
String serviceToken = pathElements[0];
WebSocketSessionType sessionType = WebSocketSessionType.forName(serviceToken)
.orElseThrow(() -> new InvalidParameterException("Can't find plugin with specified token!"));
SecurityUser currentUser = (SecurityUser) ((Authentication) session.getPrincipal()).getPrincipal();
SecurityUser securityCtx = null;
String token = StringUtils.substringAfter(session.getUri().getQuery(), "token=");
if (StringUtils.isNotEmpty(token)) {
securityCtx = authenticationProvider.authenticate(token);
}
return WebSocketSessionRef.builder()
.sessionId(UUID.randomUUID().toString())
.securityCtx(currentUser)
.securityCtx(securityCtx)
.localAddress(session.getLocalAddress())
.remoteAddress(session.getRemoteAddress())
.sessionType(sessionType)
.build();
}
private SessionMetaData getSessionMd(String internalSessionId) {
SessionMetaData sessionMd = internalSessionMap.get(internalSessionId);
if (sessionMd == null) {
sessionMd = pendingSessions.getIfPresent(internalSessionId);
}
return sessionMd;
}
class SessionMetaData implements SendHandler {
private final WebSocketSession session;
private final RemoteEndpoint.Async asyncRemote;
private final WebSocketSessionRef sessionRef;
final AtomicBoolean isSending = new AtomicBoolean(false);
private final Queue<TbWebSocketMsg<?>> msgQueue;
private final Queue<TbWebSocketMsg<?>> outboundMsgQueue = new ConcurrentLinkedQueue<>();
private final AtomicInteger outboundMsgQueueSize = new AtomicInteger();
@Setter
private int maxMsgQueueSize = wsMaxQueueMessagesPerSession;
private final Queue<String> inboundMsgQueue = new ConcurrentLinkedQueue<>();
private final Lock inboundMsgQueueProcessorLock = new ReentrantLock();
private volatile long lastActivityTime;
SessionMetaData(WebSocketSession session, WebSocketSessionRef sessionRef, int maxMsgQueuePerSession) {
SessionMetaData(WebSocketSession session, WebSocketSessionRef sessionRef) {
super();
this.session = session;
Session nativeSession = ((NativeWebSocketSession) session).getNativeSession(Session.class);
this.asyncRemote = nativeSession.getAsyncRemote();
this.sessionRef = sessionRef;
this.msgQueue = new LinkedBlockingQueue<>(maxMsgQueuePerSession);
this.lastActivityTime = System.currentTimeMillis();
}
@ -245,13 +368,13 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
try {
long timeSinceLastActivity = currentTime - lastActivityTime;
if (timeSinceLastActivity >= pingTimeout) {
log.warn("[{}] Closing session due to ping timeout", session.getId());
log.warn("{} Closing session due to ping timeout", sessionRef);
closeSession(CloseStatus.SESSION_NOT_RELIABLE);
} else if (timeSinceLastActivity >= pingTimeout / NUMBER_OF_PING_ATTEMPTS) {
sendMsg(TbWebSocketPingMsg.INSTANCE);
}
} catch (Exception e) {
log.trace("[{}] Failed to send ping msg", session.getId(), e);
log.trace("{} Failed to send ping msg", sessionRef, e);
closeSession(CloseStatus.SESSION_NOT_RELIABLE);
}
}
@ -260,9 +383,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
try {
close(this.sessionRef, reason);
} catch (IOException ioe) {
log.trace("[{}] Session transport error", session.getId(), ioe);
log.trace("{} Session transport error", sessionRef, ioe);
} finally {
msgQueue.clear();
outboundMsgQueue.clear();
}
}
@ -275,19 +398,14 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
void sendMsg(TbWebSocketMsg<?> msg) {
try {
msgQueue.add(msg);
} catch (RuntimeException e) {
if (log.isTraceEnabled()) {
log.trace("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId(), e);
} else {
log.info("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId());
}
if (outboundMsgQueueSize.get() < maxMsgQueueSize) {
outboundMsgQueue.add(msg);
outboundMsgQueueSize.incrementAndGet();
processNextMsg();
} else {
log.info("{} Session closed due to updates queue size exceeded", sessionRef);
closeSession(CloseStatus.POLICY_VIOLATION.withReason("Max pending updates limit reached!"));
return;
}
processNextMsg();
}
private void sendMsgInternal(TbWebSocketMsg<?> msg) {
@ -303,7 +421,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
processNextMsg();
}
} catch (Exception e) {
log.trace("[{}] Failed to send msg", session.getId(), e);
log.trace("{} Failed to send msg", sessionRef, e);
closeSession(CloseStatus.SESSION_NOT_RELIABLE);
}
}
@ -311,7 +429,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
@Override
public void onResult(SendResult result) {
if (!result.isOK()) {
log.trace("[{}] Failed to send msg", session.getId(), result.getException());
log.trace("{} Failed to send msg", sessionRef, result.getException());
closeSession(CloseStatus.SESSION_NOT_RELIABLE);
return;
}
@ -321,22 +439,45 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
private void processNextMsg() {
if (msgQueue.isEmpty() || !isSending.compareAndSet(false, true)) {
if (outboundMsgQueue.isEmpty() || !isSending.compareAndSet(false, true)) {
return;
}
TbWebSocketMsg<?> msg = msgQueue.poll();
TbWebSocketMsg<?> msg = outboundMsgQueue.poll();
if (msg != null) {
outboundMsgQueueSize.decrementAndGet();
sendMsgInternal(msg);
} else {
isSending.set(false);
}
}
public void onMsg(String msg) throws IOException {
inboundMsgQueue.add(msg);
tryProcessInboundMsgs();
}
void tryProcessInboundMsgs() throws IOException {
while (!inboundMsgQueue.isEmpty()) {
if (inboundMsgQueueProcessorLock.tryLock()) {
try {
String msg;
while ((msg = inboundMsgQueue.poll()) != null) {
processMsg(this, msg);
}
} finally {
inboundMsgQueueProcessorLock.unlock();
}
} else {
return;
}
}
}
}
@Override
public void send(WebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException {
log.debug("{} Sending {}", sessionRef, msg);
String externalId = sessionRef.getSessionId();
log.debug("[{}] Processing {}", externalId, msg);
String internalId = externalSessionMap.get(externalId);
if (internalId != null) {
SessionMetaData sessionMd = internalSessionMap.get(internalId);
@ -344,13 +485,12 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
TenantId tenantId = sessionRef.getSecurityCtx().getTenantId();
if (!rateLimitService.checkRateLimit(LimitedApi.WS_UPDATES_PER_SESSION, tenantId, (Object) sessionRef.getSessionId())) {
if (blacklistedSessions.putIfAbsent(externalId, sessionRef) == null) {
log.info("[{}][{}][{}] Failed to process session update. Max session updates limit reached"
, tenantId, sessionRef.getSecurityCtx().getId(), externalId);
log.info("{} Failed to process session update. Max session updates limit reached", sessionRef);
sessionMd.sendMsg("{\"subscriptionId\":" + subscriptionId + ", \"errorCode\":" + ThingsboardErrorCode.TOO_MANY_UPDATES.getErrorCode() + ", \"errorMsg\":\"Too many updates!\"}");
}
return;
} else {
log.debug("[{}][{}][{}] Session is no longer blacklisted.", tenantId, sessionRef.getSecurityCtx().getId(), externalId);
log.debug("{} Session is no longer blacklisted.", sessionRef);
blacklistedSessions.remove(externalId);
}
sessionMd.sendMsg(msg);
@ -381,10 +521,10 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
@Override
public void close(WebSocketSessionRef sessionRef, CloseStatus reason) throws IOException {
String externalId = sessionRef.getSessionId();
log.debug("[{}] Processing close request", externalId);
log.debug("{} Processing close request", sessionRef.toString());
String internalId = externalSessionMap.get(externalId);
if (internalId != null) {
SessionMetaData sessionMd = internalSessionMap.get(internalId);
SessionMetaData sessionMd = getSessionMd(internalId);
if (sessionMd != null) {
sessionMd.session.close(reason);
} else {
@ -395,7 +535,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
}
private boolean checkLimits(WebSocketSession session, WebSocketSessionRef sessionRef) throws Exception {
private boolean checkLimits(WebSocketSession session, WebSocketSessionRef sessionRef) throws IOException {
var tenantProfileConfiguration = getTenantProfileConfiguration(sessionRef);
if (tenantProfileConfiguration == null) {
return true;
@ -411,10 +551,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
}
if (!limitAllowed) {
log.info("[{}][{}][{}] Failed to start session. Max tenant sessions limit reached"
, sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), sessionId);
session.close(CloseStatus.POLICY_VIOLATION.withReason("Max tenant sessions limit reached!"));
return false;
log.info("{} Failed to start session. Max tenant sessions limit reached", sessionRef.toString());
session.close(CloseStatus.POLICY_VIOLATION.withReason("Max tenant sessions limit reached!"));
return false;
}
}
@ -428,10 +567,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
}
if (!limitAllowed) {
log.info("[{}][{}][{}] Failed to start session. Max customer sessions limit reached"
, sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), sessionId);
session.close(CloseStatus.POLICY_VIOLATION.withReason("Max customer sessions limit reached"));
return false;
log.info("{} Failed to start session. Max customer sessions limit reached", sessionRef.toString());
session.close(CloseStatus.POLICY_VIOLATION.withReason("Max customer sessions limit reached"));
return false;
}
}
if (tenantProfileConfiguration.getMaxWsSessionsPerRegularUser() > 0
@ -444,10 +582,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
}
if (!limitAllowed) {
log.info("[{}][{}][{}] Failed to start session. Max regular user sessions limit reached"
, sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), sessionId);
session.close(CloseStatus.POLICY_VIOLATION.withReason("Max regular user sessions limit reached"));
return false;
log.info("{} Failed to start session. Max regular user sessions limit reached", sessionRef.toString());
session.close(CloseStatus.POLICY_VIOLATION.withReason("Max regular user sessions limit reached"));
return false;
}
}
if (tenantProfileConfiguration.getMaxWsSessionsPerPublicUser() > 0
@ -460,10 +597,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements WebSocke
}
}
if (!limitAllowed) {
log.info("[{}][{}][{}] Failed to start session. Max public user sessions limit reached"
, sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), sessionId);
session.close(CloseStatus.POLICY_VIOLATION.withReason("Max public user sessions limit reached"));
return false;
log.info("{} Failed to start session. Max public user sessions limit reached", sessionRef.toString());
session.close(CloseStatus.POLICY_VIOLATION.withReason("Max public user sessions limit reached"));
return false;
}
}
}

33
application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsViolationResponse.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.exception;
import io.swagger.annotations.ApiModel;
import org.springframework.http.HttpStatus;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@ApiModel
public class ThingsboardCredentialsViolationResponse extends ThingsboardErrorResponse {
protected ThingsboardCredentialsViolationResponse(String message) {
super(message, ThingsboardErrorCode.PASSWORD_VIOLATION, HttpStatus.UNAUTHORIZED);
}
public static ThingsboardCredentialsViolationResponse of(final String message) {
return new ThingsboardCredentialsViolationResponse(message);
}
}

4
application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java

@ -41,6 +41,7 @@ import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
import org.thingsboard.server.service.security.exception.UserPasswordExpiredException;
import org.thingsboard.server.service.security.exception.UserPasswordNotValidException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -191,6 +192,9 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
UserPasswordExpiredException expiredException = (UserPasswordExpiredException) authenticationException;
String resetToken = expiredException.getResetToken();
JacksonUtil.writeValue(response.getWriter(), ThingsboardCredentialsExpiredResponse.of(expiredException.getMessage(), resetToken));
} else if (authenticationException instanceof UserPasswordNotValidException) {
UserPasswordNotValidException expiredException = (UserPasswordNotValidException) authenticationException;
JacksonUtil.writeValue(response.getWriter(), ThingsboardCredentialsViolationResponse.of(expiredException.getMessage()));
} else {
JacksonUtil.writeValue(response.getWriter(), ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
}

7
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -271,9 +271,8 @@ public class ThingsboardInstallService {
case "3.6.1":
log.info("Upgrading ThingsBoard from version 3.6.1 to 3.6.2 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.1");
installScripts.loadSystemImages();
if (!getEnv("SKIP_IMAGES_MIGRATION", false)) {
installScripts.updateImages();
installScripts.setUpdateImages(true);
} else {
log.info("Skipping images migration. Run the upgrade with fromVersion as '3.6.2-images' to migrate");
}
@ -288,6 +287,10 @@ public class ThingsboardInstallService {
dataUpdateService.upgradeRuleNodes();
systemDataLoaderService.loadSystemWidgets();
installScripts.loadSystemLwm2mResources();
installScripts.loadSystemImages();
if (installScripts.isUpdateImages()) {
installScripts.updateImages();
}
}
log.info("Upgrade finished successfully!");

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java

@ -33,7 +33,6 @@ import org.thingsboard.server.service.edge.rpc.fetch.EntityViewsEdgeEventFetcher
import org.thingsboard.server.service.edge.rpc.fetch.OtaPackagesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.QueuesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.RuleChainsEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.SystemResourcesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.SystemWidgetTypesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.SystemWidgetsBundlesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.TenantAdminUsersEdgeEventFetcher;
@ -79,7 +78,6 @@ public class EdgeSyncCursor {
fetchers.add(new SystemWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService()));
fetchers.add(new TenantWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService()));
fetchers.add(new OtaPackagesEdgeEventFetcher(ctx.getOtaPackageService()));
fetchers.add(new SystemResourcesEdgeEventFetcher(ctx.getResourceService()));
fetchers.add(new TenantResourcesEdgeEventFetcher(ctx.getResourceService()));
}
}

6
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/asset/AssetMsgConstructorV1.java

@ -16,10 +16,12 @@
package org.thingsboard.server.service.edge.rpc.constructor.asset;
import com.google.protobuf.ByteString;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
@ -31,6 +33,9 @@ import java.nio.charset.StandardCharsets;
@TbCoreComponent
public class AssetMsgConstructorV1 extends BaseAssetMsgConstructor {
@Autowired
private ImageService imageService;
@Override
public AssetUpdateMsg constructAssetUpdatedMsg(UpdateMsgType msgType, Asset asset) {
AssetUpdateMsg.Builder builder = AssetUpdateMsg.newBuilder()
@ -58,6 +63,7 @@ public class AssetMsgConstructorV1 extends BaseAssetMsgConstructor {
@Override
public AssetProfileUpdateMsg constructAssetProfileUpdatedMsg(UpdateMsgType msgType, AssetProfile assetProfile) {
imageService.inlineImageForEdge(assetProfile);
AssetProfileUpdateMsg.Builder builder = AssetProfileUpdateMsg.newBuilder()
.setMsgType(msgType)
.setIdMSB(assetProfile.getId().getId().getMostSignificantBits())

6
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/dashboard/DashboardMsgConstructorV1.java

@ -15,9 +15,11 @@
*/
package org.thingsboard.server.service.edge.rpc.constructor.dashboard;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -26,8 +28,12 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
@TbCoreComponent
public class DashboardMsgConstructorV1 extends BaseDashboardMsgConstructor {
@Autowired
private ImageService imageService;
@Override
public DashboardUpdateMsg constructDashboardUpdatedMsg(UpdateMsgType msgType, Dashboard dashboard) {
imageService.inlineImagesForEdge(dashboard);
DashboardUpdateMsg.Builder builder = DashboardUpdateMsg.newBuilder()
.setMsgType(msgType)
.setIdMSB(dashboard.getId().getId().getMostSignificantBits())

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/device/DeviceMsgConstructorV1.java

@ -22,6 +22,7 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
@ -38,6 +39,9 @@ public class DeviceMsgConstructorV1 extends BaseDeviceMsgConstructor {
@Autowired
private DataDecodingEncodingService dataDecodingEncodingService;
@Autowired
private ImageService imageService;
@Override
public DeviceUpdateMsg constructDeviceUpdatedMsg(UpdateMsgType msgType, Device device) {
DeviceUpdateMsg.Builder builder = DeviceUpdateMsg.newBuilder()
@ -91,6 +95,7 @@ public class DeviceMsgConstructorV1 extends BaseDeviceMsgConstructor {
@Override
public DeviceProfileUpdateMsg constructDeviceProfileUpdatedMsg(UpdateMsgType msgType, DeviceProfile deviceProfile) {
imageService.inlineImageForEdge(deviceProfile);
DeviceProfileUpdateMsg.Builder builder = DeviceProfileUpdateMsg.newBuilder()
.setMsgType(msgType)
.setIdMSB(deviceProfile.getId().getId().getMostSignificantBits())

7
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructorV1.java

@ -15,8 +15,8 @@
*/
package org.thingsboard.server.service.edge.rpc.constructor.resource;
import com.google.protobuf.ByteString;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg;
@ -29,6 +29,11 @@ public class ResourceMsgConstructorV1 extends BaseResourceMsgConstructor {
@Override
public ResourceUpdateMsg constructResourceUpdatedMsg(UpdateMsgType msgType, TbResource tbResource) {
if (ResourceType.IMAGE.equals(tbResource.getResourceType())) {
// Exclude support for a recently added resource type when dealing with older Edges
// to maintain compatibility and avoid potential issues.
return null;
}
ResourceUpdateMsg.Builder builder = ResourceUpdateMsg.newBuilder()
.setMsgType(msgType)
.setIdMSB(tbResource.getId().getId().getMostSignificantBits())

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java

@ -78,7 +78,7 @@ public class TenantMsgConstructorV1 implements TenantMsgConstructor {
@Override
public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion) {
ByteString profileData = EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_1) ?
ByteString profileData = EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_2) ?
ByteString.empty() : ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile.getProfileData()));
TenantProfileUpdateMsg.Builder builder = TenantProfileUpdateMsg.newBuilder()
.setMsgType(msgType)

7
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/widget/WidgetMsgConstructorV1.java

@ -16,11 +16,13 @@
package org.thingsboard.server.service.edge.rpc.constructor.widget;
import com.google.protobuf.ByteString;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
@ -36,8 +38,12 @@ import java.util.List;
@TbCoreComponent
public class WidgetMsgConstructorV1 extends BaseWidgetMsgConstructor {
@Autowired
private ImageService imageService;
@Override
public WidgetsBundleUpdateMsg constructWidgetsBundleUpdateMsg(UpdateMsgType msgType, WidgetsBundle widgetsBundle, List<String> widgets) {
imageService.inlineImageForEdge(widgetsBundle);
WidgetsBundleUpdateMsg.Builder builder = WidgetsBundleUpdateMsg.newBuilder()
.setMsgType(msgType)
.setIdMSB(widgetsBundle.getId().getId().getMostSignificantBits())
@ -62,6 +68,7 @@ public class WidgetMsgConstructorV1 extends BaseWidgetMsgConstructor {
@Override
public WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetTypeDetails widgetTypeDetails, EdgeVersion edgeVersion) {
imageService.inlineImagesForEdge(widgetTypeDetails);
WidgetTypeUpdateMsg.Builder builder = WidgetTypeUpdateMsg.newBuilder()
.setMsgType(msgType)
.setIdMSB(widgetTypeDetails.getId().getId().getMostSignificantBits())

49
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseResourceEdgeEventFetcher.java

@ -1,49 +0,0 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.fetch;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.resource.ResourceService;
@Slf4j
@AllArgsConstructor
public abstract class BaseResourceEdgeEventFetcher extends BasePageableEdgeEventFetcher<TbResource> {
protected final ResourceService resourceService;
@Override
PageData<TbResource> fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
return findTenantResources(tenantId, pageLink);
}
@Override
EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, TbResource tbResource) {
return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.TB_RESOURCE,
EdgeEventActionType.ADDED, tbResource.getId(), null);
}
protected abstract PageData<TbResource> findTenantResources(TenantId tenantId, PageLink pageLink);
}

34
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/SystemResourcesEdgeEventFetcher.java

@ -1,34 +0,0 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.fetch;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.resource.ResourceService;
public class SystemResourcesEdgeEventFetcher extends BaseResourceEdgeEventFetcher {
public SystemResourcesEdgeEventFetcher(ResourceService resourceService) {
super(resourceService);
}
@Override
protected PageData<TbResource> findTenantResources(TenantId tenantId, PageLink pageLink) {
return resourceService.findAllTenantResources(TenantId.SYS_TENANT_ID, pageLink);
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save