Added support for private storage. (#16878)

* Addded support for private storage.

Include 'private' flag parameters to the Store class and save_json function.
Updated various authentication and onboarding classes to use private stores.
Fixed unit test for emulated_hue which used a mock patch on save_json().

* Fixed Hound formatting issues not detected by local linting.
This commit is contained in:
Nicko van Someren 2018-09-26 04:24:32 -04:00 committed by Paulus Schoutsen
parent e205092693
commit e5861241c7
8 changed files with 64 additions and 53 deletions

View file

@ -28,7 +28,8 @@ class AuthStore:
"""Initialize the auth store.""" """Initialize the auth store."""
self.hass = hass self.hass = hass
self._users = None # type: Optional[Dict[str, models.User]] self._users = None # type: Optional[Dict[str, models.User]]
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
private=True)
async def async_get_users(self) -> List[models.User]: async def async_get_users(self) -> List[models.User]:
"""Retrieve all users.""" """Retrieve all users."""

View file

@ -85,7 +85,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
super().__init__(hass, config) super().__init__(hass, config)
self._user_settings = None # type: Optional[_UsersDict] self._user_settings = None # type: Optional[_UsersDict]
self._user_store = hass.helpers.storage.Store( self._user_store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY) STORAGE_VERSION, STORAGE_KEY, private=True)
self._include = config.get(CONF_INCLUDE, []) self._include = config.get(CONF_INCLUDE, [])
self._exclude = config.get(CONF_EXCLUDE, []) self._exclude = config.get(CONF_EXCLUDE, [])
self._message_template = config[CONF_MESSAGE] self._message_template = config[CONF_MESSAGE]

View file

@ -67,7 +67,7 @@ class TotpAuthModule(MultiFactorAuthModule):
super().__init__(hass, config) super().__init__(hass, config)
self._users = None # type: Optional[Dict[str, str]] self._users = None # type: Optional[Dict[str, str]]
self._user_store = hass.helpers.storage.Store( self._user_store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY) STORAGE_VERSION, STORAGE_KEY, private=True)
@property @property
def input_schema(self) -> vol.Schema: def input_schema(self) -> vol.Schema:

View file

@ -52,7 +52,8 @@ class Data:
def __init__(self, hass: HomeAssistant) -> None: def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the user data store.""" """Initialize the user data store."""
self.hass = hass self.hass = hass
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
private=True)
self._data = None # type: Optional[Dict[str, Any]] self._data = None # type: Optional[Dict[str, Any]]
async def async_load(self) -> None: async def async_load(self) -> None:

View file

@ -23,7 +23,8 @@ def async_is_onboarded(hass):
async def async_setup(hass, config): async def async_setup(hass, config):
"""Set up the onboarding component.""" """Set up the onboarding component."""
store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
private=True)
data = await store.async_load() data = await store.async_load()
if data is None: if data is None:

View file

@ -46,11 +46,12 @@ async def async_migrator(hass, old_path, store, *,
class Store: class Store:
"""Class to help storing data.""" """Class to help storing data."""
def __init__(self, hass, version: int, key: str): def __init__(self, hass, version: int, key: str, private: bool = False):
"""Initialize storage class.""" """Initialize storage class."""
self.version = version self.version = version
self.key = key self.key = key
self.hass = hass self.hass = hass
self._private = private
self._data = None self._data = None
self._unsub_delay_listener = None self._unsub_delay_listener = None
self._unsub_stop_listener = None self._unsub_stop_listener = None
@ -186,7 +187,7 @@ class Store:
os.makedirs(os.path.dirname(path)) os.makedirs(os.path.dirname(path))
_LOGGER.debug('Writing data for %s', self.key) _LOGGER.debug('Writing data for %s', self.key)
json.save_json(path, data) json.save_json(path, data, self._private)
async def _async_migrate_func(self, old_version, old_data): async def _async_migrate_func(self, old_version, old_data):
"""Migrate to the new version.""" """Migrate to the new version."""

View file

@ -3,6 +3,7 @@ import logging
from typing import Union, List, Dict from typing import Union, List, Dict
import json import json
import os
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
@ -38,14 +39,17 @@ def load_json(filename: str, default: Union[List, Dict, None] = None) \
return {} if default is None else default return {} if default is None else default
def save_json(filename: str, data: Union[List, Dict]) -> None: def save_json(filename: str, data: Union[List, Dict],
private: bool = False) -> None:
"""Save JSON data to a file. """Save JSON data to a file.
Returns True on success. Returns True on success.
""" """
try: try:
json_data = json.dumps(data, sort_keys=True, indent=4) json_data = json.dumps(data, sort_keys=True, indent=4)
with open(filename, 'w', encoding='utf-8') as fdesc: mode = 0o600 if private else 0o644
with open(os.open(filename, os.O_WRONLY | os.O_CREAT, mode),
'w', encoding='utf-8') as fdesc:
fdesc.write(json_data) fdesc.write(json_data)
except TypeError as error: except TypeError as error:
_LOGGER.exception('Failed to serialize to JSON: %s', _LOGGER.exception('Failed to serialize to JSON: %s',

View file

@ -16,24 +16,25 @@ def test_config_google_home_entity_id_to_number():
handle = mop() handle = mop()
with patch('homeassistant.util.json.open', mop, create=True): with patch('homeassistant.util.json.open', mop, create=True):
number = conf.entity_id_to_number('light.test') with patch('homeassistant.util.json.os.open', return_value=0):
assert number == '2' number = conf.entity_id_to_number('light.test')
assert handle.write.call_count == 1 assert number == '2'
assert json.loads(handle.write.mock_calls[0][1][0]) == { assert handle.write.call_count == 1
'1': 'light.test2', assert json.loads(handle.write.mock_calls[0][1][0]) == {
'2': 'light.test', '1': 'light.test2',
} '2': 'light.test',
}
number = conf.entity_id_to_number('light.test') number = conf.entity_id_to_number('light.test')
assert number == '2' assert number == '2'
assert handle.write.call_count == 1 assert handle.write.call_count == 1
number = conf.entity_id_to_number('light.test2') number = conf.entity_id_to_number('light.test2')
assert number == '1' assert number == '1'
assert handle.write.call_count == 1 assert handle.write.call_count == 1
entity_id = conf.number_to_entity_id('1') entity_id = conf.number_to_entity_id('1')
assert entity_id == 'light.test2' assert entity_id == 'light.test2'
def test_config_google_home_entity_id_to_number_altered(): def test_config_google_home_entity_id_to_number_altered():
@ -46,24 +47,25 @@ def test_config_google_home_entity_id_to_number_altered():
handle = mop() handle = mop()
with patch('homeassistant.util.json.open', mop, create=True): with patch('homeassistant.util.json.open', mop, create=True):
number = conf.entity_id_to_number('light.test') with patch('homeassistant.util.json.os.open', return_value=0):
assert number == '22' number = conf.entity_id_to_number('light.test')
assert handle.write.call_count == 1 assert number == '22'
assert json.loads(handle.write.mock_calls[0][1][0]) == { assert handle.write.call_count == 1
'21': 'light.test2', assert json.loads(handle.write.mock_calls[0][1][0]) == {
'22': 'light.test', '21': 'light.test2',
} '22': 'light.test',
}
number = conf.entity_id_to_number('light.test') number = conf.entity_id_to_number('light.test')
assert number == '22' assert number == '22'
assert handle.write.call_count == 1 assert handle.write.call_count == 1
number = conf.entity_id_to_number('light.test2') number = conf.entity_id_to_number('light.test2')
assert number == '21' assert number == '21'
assert handle.write.call_count == 1 assert handle.write.call_count == 1
entity_id = conf.number_to_entity_id('21') entity_id = conf.number_to_entity_id('21')
assert entity_id == 'light.test2' assert entity_id == 'light.test2'
def test_config_google_home_entity_id_to_number_empty(): def test_config_google_home_entity_id_to_number_empty():
@ -76,23 +78,24 @@ def test_config_google_home_entity_id_to_number_empty():
handle = mop() handle = mop()
with patch('homeassistant.util.json.open', mop, create=True): with patch('homeassistant.util.json.open', mop, create=True):
number = conf.entity_id_to_number('light.test') with patch('homeassistant.util.json.os.open', return_value=0):
assert number == '1' number = conf.entity_id_to_number('light.test')
assert handle.write.call_count == 1 assert number == '1'
assert json.loads(handle.write.mock_calls[0][1][0]) == { assert handle.write.call_count == 1
'1': 'light.test', assert json.loads(handle.write.mock_calls[0][1][0]) == {
} '1': 'light.test',
}
number = conf.entity_id_to_number('light.test') number = conf.entity_id_to_number('light.test')
assert number == '1' assert number == '1'
assert handle.write.call_count == 1 assert handle.write.call_count == 1
number = conf.entity_id_to_number('light.test2') number = conf.entity_id_to_number('light.test2')
assert number == '2' assert number == '2'
assert handle.write.call_count == 2 assert handle.write.call_count == 2
entity_id = conf.number_to_entity_id('2') entity_id = conf.number_to_entity_id('2')
assert entity_id == 'light.test2' assert entity_id == 'light.test2'
def test_config_alexa_entity_id_to_number(): def test_config_alexa_entity_id_to_number():