Get user information and prevent using admin-only calls (#3417)

* Get and cache user information

 - Add function to get information about the current user
 - Cache user information alongside server in database

* Replace/prevent admin only calls
This commit is contained in:
Joris Pelgröm 2023-03-16 08:10:27 +01:00 committed by GitHub
parent 421f38f1c2
commit 8d2c9f1cb4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1104 additions and 25 deletions

View file

@ -29,6 +29,7 @@ class LaunchPresenterImpl @Inject constructor(
)
)
serverManager.integrationRepository(it.id).getConfig() // Update cached data
serverManager.webSocketRepository(it.id).getCurrentUser() // Update cached data
} catch (e: Exception) {
Log.e(TAG, "Issue updating Registration", e)
}

View file

@ -23,7 +23,7 @@ class MatterManagerImpl @Inject constructor(
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
override suspend fun coreSupportsCommissioning(serverId: Int): Boolean {
if (!serverManager.isRegistered() || serverManager.getServer(serverId) == null) return false
if (!serverManager.isRegistered() || serverManager.getServer(serverId)?.user?.isAdmin != true) return false
val config = serverManager.webSocketRepository(serverId).getConfig()
return config != null && config.components.contains("matter")
}

View file

@ -37,6 +37,7 @@ import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.database.server.Server
import io.homeassistant.companion.android.database.server.ServerConnectionInfo
import io.homeassistant.companion.android.database.server.ServerSessionInfo
import io.homeassistant.companion.android.database.server.ServerUserInfo
import io.homeassistant.companion.android.matter.MatterCommissioningViewModel.CommissioningFlowStep
import kotlin.math.min
import io.homeassistant.companion.android.common.R as commonR
@ -221,7 +222,7 @@ fun PreviewMatterCommissioningView(
step = step,
deviceName = "Manufacturer Matter Light",
servers = listOf(
Server(id = 0, _name = "Home", listOrder = -1, connection = ServerConnectionInfo(externalUrl = ""), session = ServerSessionInfo())
Server(id = 0, _name = "Home", listOrder = -1, connection = ServerConnectionInfo(externalUrl = ""), session = ServerSessionInfo(), user = ServerUserInfo())
),
onSelectServer = { },
onConfirmCommissioning = { },

View file

@ -33,7 +33,7 @@ class ThreadManagerImpl @Inject constructor(
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
override suspend fun coreSupportsThread(serverId: Int): Boolean {
if (!serverManager.isRegistered() || serverManager.getServer(serverId) == null) return false
if (!serverManager.isRegistered() || serverManager.getServer(serverId)?.user?.isAdmin != true) return false
val config = serverManager.webSocketRepository(serverId).getConfig()
return config != null &&
config.components.contains("thread") &&

View file

@ -21,6 +21,7 @@ import io.homeassistant.companion.android.database.server.Server
import io.homeassistant.companion.android.database.server.ServerConnectionInfo
import io.homeassistant.companion.android.database.server.ServerSessionInfo
import io.homeassistant.companion.android.database.server.ServerType
import io.homeassistant.companion.android.database.server.ServerUserInfo
import io.homeassistant.companion.android.database.settings.WebsocketSetting
import io.homeassistant.companion.android.onboarding.OnboardApp
import io.homeassistant.companion.android.onboarding.getMessagingToken
@ -149,7 +150,8 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
connection = ServerConnectionInfo(
externalUrl = formattedUrl
),
session = ServerSessionInfo()
session = ServerSessionInfo(),
user = ServerUserInfo()
)
serverId = serverManager.addServer(server)
serverManager.authenticationRepository(serverId).registerAuthorizationCode(authCode)

View file

@ -13,6 +13,7 @@ import io.homeassistant.companion.android.database.server.Server
import io.homeassistant.companion.android.database.server.ServerConnectionInfo
import io.homeassistant.companion.android.database.server.ServerSessionInfo
import io.homeassistant.companion.android.database.server.ServerType
import io.homeassistant.companion.android.database.server.ServerUserInfo
import io.homeassistant.companion.android.database.settings.SensorUpdateFrequencySetting
import io.homeassistant.companion.android.database.settings.Setting
import io.homeassistant.companion.android.database.settings.SettingsDao
@ -146,7 +147,8 @@ class SettingsPresenterImpl @Inject constructor(
connection = ServerConnectionInfo(
externalUrl = formattedUrl
),
session = ServerSessionInfo()
session = ServerSessionInfo(),
user = ServerUserInfo()
)
serverId = serverManager.addServer(server)
serverManager.authenticationRepository(serverId).registerAuthorizationCode(authCode)

View file

@ -22,6 +22,7 @@ class LaunchPresenterImpl @Inject constructor(
)
)
serverManager.integrationRepository(it.id).getConfig() // Update cached data
serverManager.webSocketRepository(it.id).getCurrentUser() // Update cached data
} catch (e: Exception) {
Log.e(TAG, "Issue updating Registration", e)
}

View file

@ -0,0 +1,987 @@
{
"formatVersion": 1,
"database": {
"version": 39,
"identityHash": "ae2f0ecc0bd86c23945ff3ff9151339b",
"entities": [
{
"tableName": "sensor_attributes",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sensor_id` TEXT NOT NULL, `name` TEXT NOT NULL, `value` TEXT NOT NULL, `value_type` TEXT NOT NULL, PRIMARY KEY(`sensor_id`, `name`))",
"fields": [
{
"fieldPath": "sensorId",
"columnName": "sensor_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "valueType",
"columnName": "value_type",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"sensor_id",
"name"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "authentication_list",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`host` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, PRIMARY KEY(`host`))",
"fields": [
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "username",
"columnName": "username",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"host"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sensors",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL, `registered` INTEGER DEFAULT NULL, `state` TEXT NOT NULL, `last_sent_state` TEXT DEFAULT NULL, `last_sent_icon` TEXT DEFAULT NULL, `state_type` TEXT NOT NULL, `type` TEXT NOT NULL, `icon` TEXT NOT NULL, `name` TEXT NOT NULL, `device_class` TEXT, `unit_of_measurement` TEXT, `state_class` TEXT, `entity_category` TEXT, `core_registration` TEXT, `app_registration` TEXT, PRIMARY KEY(`id`, `server_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "serverId",
"columnName": "server_id",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "registered",
"columnName": "registered",
"affinity": "INTEGER",
"notNull": false,
"defaultValue": "NULL"
},
{
"fieldPath": "state",
"columnName": "state",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastSentState",
"columnName": "last_sent_state",
"affinity": "TEXT",
"notNull": false,
"defaultValue": "NULL"
},
{
"fieldPath": "lastSentIcon",
"columnName": "last_sent_icon",
"affinity": "TEXT",
"notNull": false,
"defaultValue": "NULL"
},
{
"fieldPath": "stateType",
"columnName": "state_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "deviceClass",
"columnName": "device_class",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "unitOfMeasurement",
"columnName": "unit_of_measurement",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "stateClass",
"columnName": "state_class",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "entityCategory",
"columnName": "entity_category",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "coreRegistration",
"columnName": "core_registration",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appRegistration",
"columnName": "app_registration",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id",
"server_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sensor_settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sensor_id` TEXT NOT NULL, `name` TEXT NOT NULL, `value` TEXT NOT NULL, `value_type` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `entries` TEXT NOT NULL, PRIMARY KEY(`sensor_id`, `name`))",
"fields": [
{
"fieldPath": "sensorId",
"columnName": "sensor_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "valueType",
"columnName": "value_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "entries",
"columnName": "entries",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"sensor_id",
"name"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "button_widgets",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `icon_id` INTEGER NOT NULL, `domain` TEXT NOT NULL, `service` TEXT NOT NULL, `service_data` TEXT NOT NULL, `label` TEXT, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, `require_authentication` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serverId",
"columnName": "server_id",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "iconId",
"columnName": "icon_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "domain",
"columnName": "domain",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "service",
"columnName": "service",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "serviceData",
"columnName": "service_data",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "backgroundType",
"columnName": "background_type",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'DAYNIGHT'"
},
{
"fieldPath": "textColor",
"columnName": "text_color",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "requireAuthentication",
"columnName": "require_authentication",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "camera_widgets",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `entity_id` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serverId",
"columnName": "server_id",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "entityId",
"columnName": "entity_id",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "media_player_controls_widgets",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `entity_id` TEXT NOT NULL, `label` TEXT, `show_skip` INTEGER NOT NULL, `show_seek` INTEGER NOT NULL, `show_volume` INTEGER NOT NULL, `show_source` INTEGER NOT NULL DEFAULT false, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serverId",
"columnName": "server_id",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "entityId",
"columnName": "entity_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "showSkip",
"columnName": "show_skip",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "showSeek",
"columnName": "show_seek",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "showVolume",
"columnName": "show_volume",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "showSource",
"columnName": "show_source",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "backgroundType",
"columnName": "background_type",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'DAYNIGHT'"
},
{
"fieldPath": "textColor",
"columnName": "text_color",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "static_widget",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `entity_id` TEXT NOT NULL, `attribute_ids` TEXT, `label` TEXT, `text_size` REAL NOT NULL, `state_separator` TEXT NOT NULL, `attribute_separator` TEXT NOT NULL, `last_update` TEXT NOT NULL, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serverId",
"columnName": "server_id",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "entityId",
"columnName": "entity_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "attributeIds",
"columnName": "attribute_ids",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "textSize",
"columnName": "text_size",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "stateSeparator",
"columnName": "state_separator",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "attributeSeparator",
"columnName": "attribute_separator",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastUpdate",
"columnName": "last_update",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "backgroundType",
"columnName": "background_type",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'DAYNIGHT'"
},
{
"fieldPath": "textColor",
"columnName": "text_color",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "template_widgets",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `template` TEXT NOT NULL, `text_size` REAL NOT NULL DEFAULT 12.0, `last_update` TEXT NOT NULL, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serverId",
"columnName": "server_id",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "template",
"columnName": "template",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "textSize",
"columnName": "text_size",
"affinity": "REAL",
"notNull": true,
"defaultValue": "12.0"
},
{
"fieldPath": "lastUpdate",
"columnName": "last_update",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "backgroundType",
"columnName": "background_type",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'DAYNIGHT'"
},
{
"fieldPath": "textColor",
"columnName": "text_color",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "notification_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `received` INTEGER NOT NULL, `message` TEXT NOT NULL, `data` TEXT NOT NULL, `source` TEXT NOT NULL, `server_id` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "received",
"columnName": "received",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "message",
"columnName": "message",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "data",
"columnName": "data",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "serverId",
"columnName": "server_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "qs_tiles",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `tile_id` TEXT NOT NULL, `added` INTEGER NOT NULL DEFAULT 1, `server_id` INTEGER NOT NULL DEFAULT 0, `icon_id` INTEGER, `entity_id` TEXT NOT NULL, `label` TEXT NOT NULL, `subtitle` TEXT, `should_vibrate` INTEGER NOT NULL DEFAULT 0, `auth_required` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "tileId",
"columnName": "tile_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "added",
"columnName": "added",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "serverId",
"columnName": "server_id",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "iconId",
"columnName": "icon_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "entityId",
"columnName": "entity_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "subtitle",
"columnName": "subtitle",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "shouldVibrate",
"columnName": "should_vibrate",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "authRequired",
"columnName": "auth_required",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "favorites",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `position` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "position",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "favorite_cache",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `friendly_name` TEXT NOT NULL, `icon` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "friendlyName",
"columnName": "friendly_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "entity_state_complications",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `entity_id` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "entityId",
"columnName": "entity_id",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "servers",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `_name` TEXT NOT NULL, `name_override` TEXT, `_version` TEXT, `list_order` INTEGER NOT NULL, `device_name` TEXT, `external_url` TEXT NOT NULL, `internal_url` TEXT, `cloud_url` TEXT, `webhook_id` TEXT, `secret` TEXT, `cloudhook_url` TEXT, `use_cloud` INTEGER NOT NULL, `internal_ssids` TEXT NOT NULL, `prioritize_internal` INTEGER NOT NULL, `access_token` TEXT, `refresh_token` TEXT, `token_expiration` INTEGER, `token_type` TEXT, `install_id` TEXT, `user_id` TEXT, `user_name` TEXT, `user_is_owner` INTEGER, `user_is_admin` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "_name",
"columnName": "_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "nameOverride",
"columnName": "name_override",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "_version",
"columnName": "_version",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "listOrder",
"columnName": "list_order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "deviceName",
"columnName": "device_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "connection.externalUrl",
"columnName": "external_url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "connection.internalUrl",
"columnName": "internal_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "connection.cloudUrl",
"columnName": "cloud_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "connection.webhookId",
"columnName": "webhook_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "connection.secret",
"columnName": "secret",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "connection.cloudhookUrl",
"columnName": "cloudhook_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "connection.useCloud",
"columnName": "use_cloud",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "connection.internalSsids",
"columnName": "internal_ssids",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "connection.prioritizeInternal",
"columnName": "prioritize_internal",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "session.accessToken",
"columnName": "access_token",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "session.refreshToken",
"columnName": "refresh_token",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "session.tokenExpiration",
"columnName": "token_expiration",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "session.tokenType",
"columnName": "token_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "session.installId",
"columnName": "install_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "user.id",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "user.name",
"columnName": "user_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "user.isOwner",
"columnName": "user_is_owner",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "user.isAdmin",
"columnName": "user_is_admin",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `websocket_setting` TEXT NOT NULL, `sensor_update_frequency` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "websocketSetting",
"columnName": "websocket_setting",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "sensorUpdateFrequency",
"columnName": "sensor_update_frequency",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ae2f0ecc0bd86c23945ff3ff9151339b')"
]
}
}

View file

@ -103,6 +103,7 @@ class IntegrationRepositoryImpl @AssistedInject constructor(
)
)
getConfig() // To get version, name, etc stored
webSocketRepository.getCurrentUser() // To get user info stored
} catch (e: Exception) {
Log.e(TAG, "Unable to save device registration", e)
}
@ -583,18 +584,33 @@ class IntegrationRepositoryImpl @AssistedInject constructor(
}
override suspend fun getEntityUpdates(entityIds: List<String>): Flow<Entity<*>>? {
return webSocketRepository.getStateChanges(entityIds)
?.filter { it.toState != null }
?.map {
Entity(
it.toState!!.entityId,
it.toState.state,
it.toState.attributes,
it.toState.lastChanged,
it.toState.lastUpdated,
it.toState.context
)
}
return if (server.user.isAdmin == true) {
webSocketRepository.getStateChanges(entityIds)
?.filter { it.toState != null }
?.map {
Entity(
it.toState!!.entityId,
it.toState.state,
it.toState.attributes,
it.toState.lastChanged,
it.toState.lastUpdated,
it.toState.context
)
}
} else {
webSocketRepository.getStateChanges()
?.filter { it.newState != null && entityIds.contains(it.entityId) }
?.map {
Entity(
it.newState!!.entityId,
it.newState.state,
it.newState.attributes,
it.newState.lastChanged,
it.newState.lastUpdated,
it.newState.context
)
}
}
}
override suspend fun registerSensor(sensorRegistration: SensorRegistration<Any>) {

View file

@ -7,6 +7,7 @@ import io.homeassistant.companion.android.common.data.websocket.impl.entities.Ar
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryUpdatedEvent
import io.homeassistant.companion.android.common.data.websocket.impl.entities.CompressedStateChangedEvent
import io.homeassistant.companion.android.common.data.websocket.impl.entities.ConversationResponse
import io.homeassistant.companion.android.common.data.websocket.impl.entities.CurrentUserResponse
import io.homeassistant.companion.android.common.data.websocket.impl.entities.DeviceRegistryResponse
import io.homeassistant.companion.android.common.data.websocket.impl.entities.DeviceRegistryUpdatedEvent
import io.homeassistant.companion.android.common.data.websocket.impl.entities.DomainResponse
@ -25,6 +26,7 @@ interface WebSocketRepository {
fun getConnectionState(): WebSocketState?
fun shutdown()
suspend fun sendPing(): Boolean
suspend fun getCurrentUser(): CurrentUserResponse?
suspend fun getConfig(): GetConfigResponse?
suspend fun getStates(): List<EntityResponse<Any>>?
suspend fun getAreaRegistry(): List<AreaRegistryResponse>?

View file

@ -25,6 +25,7 @@ import io.homeassistant.companion.android.common.data.websocket.impl.entities.Ar
import io.homeassistant.companion.android.common.data.websocket.impl.entities.AreaRegistryUpdatedEvent
import io.homeassistant.companion.android.common.data.websocket.impl.entities.CompressedStateChangedEvent
import io.homeassistant.companion.android.common.data.websocket.impl.entities.ConversationResponse
import io.homeassistant.companion.android.common.data.websocket.impl.entities.CurrentUserResponse
import io.homeassistant.companion.android.common.data.websocket.impl.entities.DeviceRegistryResponse
import io.homeassistant.companion.android.common.data.websocket.impl.entities.DeviceRegistryUpdatedEvent
import io.homeassistant.companion.android.common.data.websocket.impl.entities.DomainResponse
@ -40,6 +41,7 @@ import io.homeassistant.companion.android.common.data.websocket.impl.entities.Th
import io.homeassistant.companion.android.common.data.websocket.impl.entities.ThreadDatasetTlvResponse
import io.homeassistant.companion.android.common.data.websocket.impl.entities.TriggerEvent
import io.homeassistant.companion.android.common.util.toHexString
import io.homeassistant.companion.android.database.server.ServerUserInfo
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -130,6 +132,18 @@ class WebSocketRepositoryImpl @AssistedInject constructor(
return mapResponse(socketResponse)
}
override suspend fun getCurrentUser(): CurrentUserResponse? {
val socketResponse = sendMessage(
mapOf(
"type" to "auth/current_user"
)
)
val response: CurrentUserResponse? = mapResponse(socketResponse)
response?.let { updateServerWithUser(it) }
return response
}
override suspend fun getStates(): List<EntityResponse<Any>>? {
val socketResponse = sendMessage(
mapOf(
@ -446,6 +460,25 @@ class WebSocketRepositoryImpl @AssistedInject constructor(
return response?.success == true
}
/**
* Update this repository's [server] with information from a [CurrentUserResponse] like user
* name and admin status.
*/
private fun updateServerWithUser(user: CurrentUserResponse) {
server?.let {
serverManager.updateServer(
it.copy(
user = ServerUserInfo(
id = user.id,
name = user.name,
isOwner = user.isOwner,
isAdmin = user.isAdmin
)
)
)
}
}
private suspend fun connect(): Boolean {
connectedMutex.withLock {
if (connection != null && connected.isCompleted) {

View file

@ -0,0 +1,11 @@
package io.homeassistant.companion.android.common.data.websocket.impl.entities
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true)
data class CurrentUserResponse(
val id: String,
val name: String,
val isOwner: Boolean,
val isAdmin: Boolean
)

View file

@ -87,7 +87,7 @@ import io.homeassistant.companion.android.common.R as commonR
Server::class,
Setting::class
],
version = 38,
version = 39,
autoMigrations = [
AutoMigration(from = 24, to = 25),
AutoMigration(from = 25, to = 26),
@ -102,7 +102,8 @@ import io.homeassistant.companion.android.common.R as commonR
AutoMigration(from = 34, to = 35),
AutoMigration(from = 35, to = 36),
AutoMigration(from = 36, to = 37, spec = AppDatabase.Companion.Migration36to37::class),
AutoMigration(from = 37, to = 38, spec = AppDatabase.Companion.Migration37to38::class)
AutoMigration(from = 37, to = 38, spec = AppDatabase.Companion.Migration37to38::class),
AutoMigration(from = 38, to = 39)
]
)
@TypeConverters(

View file

@ -26,10 +26,11 @@ data class Server(
@ColumnInfo(name = "device_name")
val deviceName: String? = null,
@Embedded val connection: ServerConnectionInfo,
@Embedded val session: ServerSessionInfo
@Embedded val session: ServerSessionInfo,
@Embedded val user: ServerUserInfo
) {
constructor(id: Int, _name: String, nameOverride: String?, _version: String?, listOrder: Int, deviceName: String?, connection: ServerConnectionInfo, session: ServerSessionInfo) :
this(id, _name, nameOverride, _version, ServerType.DEFAULT, listOrder, deviceName, connection, session)
constructor(id: Int, _name: String, nameOverride: String?, _version: String?, listOrder: Int, deviceName: String?, connection: ServerConnectionInfo, session: ServerSessionInfo, user: ServerUserInfo) :
this(id, _name, nameOverride, _version, ServerType.DEFAULT, listOrder, deviceName, connection, session, user)
val friendlyName: String
get() = nameOverride ?: _name.ifBlank { connection.externalUrl }

View file

@ -0,0 +1,14 @@
package io.homeassistant.companion.android.database.server
import androidx.room.ColumnInfo
data class ServerUserInfo(
@ColumnInfo(name = "user_id")
val id: String? = null,
@ColumnInfo(name = "user_name")
val name: String? = null,
@ColumnInfo(name = "user_is_owner")
val isOwner: Boolean? = null,
@ColumnInfo(name = "user_is_admin")
val isAdmin: Boolean? = null
)

View file

@ -192,6 +192,7 @@ class HomePresenterImpl @Inject constructor(
false
)
)
serverManager.webSocketRepository(it.id).getCurrentUser() // Update cached data
} catch (e: Exception) {
Log.e(TAG, "Issue updating Registration", e)
}

View file

@ -18,6 +18,7 @@ import io.homeassistant.companion.android.database.server.Server
import io.homeassistant.companion.android.database.server.ServerConnectionInfo
import io.homeassistant.companion.android.database.server.ServerSessionInfo
import io.homeassistant.companion.android.database.server.ServerType
import io.homeassistant.companion.android.database.server.ServerUserInfo
import io.homeassistant.companion.android.util.UrlUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -143,7 +144,8 @@ class OnboardingPresenterImpl @Inject constructor(
connection = ServerConnectionInfo(
externalUrl = formattedUrl
),
session = ServerSessionInfo()
session = ServerSessionInfo(),
user = ServerUserInfo()
)
serverId = serverManager.addServer(server)
serverManager.authenticationRepository(serverId).registerAuthorizationCode(code)

View file

@ -14,6 +14,7 @@ import io.homeassistant.companion.android.database.server.Server
import io.homeassistant.companion.android.database.server.ServerConnectionInfo
import io.homeassistant.companion.android.database.server.ServerSessionInfo
import io.homeassistant.companion.android.database.server.ServerType
import io.homeassistant.companion.android.database.server.ServerUserInfo
import io.homeassistant.companion.android.util.UrlUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -102,7 +103,8 @@ class ManualSetupPresenterImpl @Inject constructor(
connection = ServerConnectionInfo(
externalUrl = formattedUrl
),
session = ServerSessionInfo()
session = ServerSessionInfo(),
user = ServerUserInfo()
)
serverId = serverManager.addServer(server)
serverManager.authenticationRepository(serverId).registerAuthorizationCode(code)

View file

@ -22,6 +22,7 @@ import io.homeassistant.companion.android.database.server.Server
import io.homeassistant.companion.android.database.server.ServerConnectionInfo
import io.homeassistant.companion.android.database.server.ServerSessionInfo
import io.homeassistant.companion.android.database.server.ServerType
import io.homeassistant.companion.android.database.server.ServerUserInfo
import io.homeassistant.companion.android.database.wear.FavoritesDao
import io.homeassistant.companion.android.database.wear.getAll
import io.homeassistant.companion.android.database.wear.replaceAll
@ -129,7 +130,8 @@ class PhoneSettingsListener : WearableListenerService(), DataClient.OnDataChange
connection = ServerConnectionInfo(
externalUrl = formattedUrl
),
session = ServerSessionInfo()
session = ServerSessionInfo(),
user = ServerUserInfo()
)
serverId = serverManager.addServer(server)
serverManager.authenticationRepository(serverId).registerAuthorizationCode(authCode)