diff --git a/homeassistant/auth/auth_store.py b/homeassistant/auth/auth_store.py index c170344746e..54c34d8ec2c 100644 --- a/homeassistant/auth/auth_store.py +++ b/homeassistant/auth/auth_store.py @@ -1,9 +1,9 @@ """Storage for auth models.""" from collections import OrderedDict from datetime import timedelta +import hmac from logging import getLogger from typing import Any, Dict, List, Optional # noqa: F401 -import hmac from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION from homeassistant.core import HomeAssistant, callback @@ -214,14 +214,24 @@ class AuthStore: if self._users is not None: return - users = OrderedDict() # type: Dict[str, models.User] - if data is None: - self._users = users + self._set_defaults() return + users = OrderedDict() # type: Dict[str, models.User] + + # When creating objects we mention each attribute explicetely. This + # prevents crashing if user rolls back HA version after a new property + # was added. + for user_dict in data['users']: - users[user_dict['id']] = models.User(**user_dict) + users[user_dict['id']] = models.User( + name=user_dict['name'], + id=user_dict['id'], + is_owner=user_dict['is_owner'], + is_active=user_dict['is_active'], + system_generated=user_dict['system_generated'], + ) for cred_dict in data['credentials']: users[cred_dict['user_id']].credentials.append(models.Credentials( @@ -341,3 +351,7 @@ class AuthStore: 'credentials': credentials, 'refresh_tokens': refresh_tokens, } + + def _set_defaults(self) -> None: + """Set default values for auth store.""" + self._users = OrderedDict() # type: Dict[str, models.User] diff --git a/homeassistant/auth/models.py b/homeassistant/auth/models.py index b0f4024c3ab..bd00ca72b83 100644 --- a/homeassistant/auth/models.py +++ b/homeassistant/auth/models.py @@ -19,19 +19,19 @@ class User: """A user.""" name = attr.ib(type=str) # type: Optional[str] - id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex)) + id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex) is_owner = attr.ib(type=bool, default=False) is_active = attr.ib(type=bool, default=False) system_generated = attr.ib(type=bool, default=False) # List of credentials of a user. credentials = attr.ib( - type=list, default=attr.Factory(list), cmp=False + type=list, factory=list, cmp=False ) # type: List[Credentials] # Tokens associated with a user. refresh_tokens = attr.ib( - type=dict, default=attr.Factory(dict), cmp=False + type=dict, factory=dict, cmp=False ) # type: Dict[str, RefreshToken] @@ -48,12 +48,10 @@ class RefreshToken: validator=attr.validators.in_(( TOKEN_TYPE_NORMAL, TOKEN_TYPE_SYSTEM, TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN))) - id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex)) - created_at = attr.ib(type=datetime, default=attr.Factory(dt_util.utcnow)) - token = attr.ib(type=str, - default=attr.Factory(lambda: generate_secret(64))) - jwt_key = attr.ib(type=str, - default=attr.Factory(lambda: generate_secret(64))) + id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex) + created_at = attr.ib(type=datetime, factory=dt_util.utcnow) + token = attr.ib(type=str, factory=lambda: generate_secret(64)) + jwt_key = attr.ib(type=str, factory=lambda: generate_secret(64)) last_used_at = attr.ib(type=Optional[datetime], default=None) last_used_ip = attr.ib(type=Optional[str], default=None) @@ -69,7 +67,7 @@ class Credentials: # Allow the auth provider to store data to represent their auth. data = attr.ib(type=dict) - id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex)) + id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex) is_new = attr.ib(type=bool, default=True) diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py index 7d867b50ec2..cfe73d6d147 100644 --- a/homeassistant/helpers/storage.py +++ b/homeassistant/helpers/storage.py @@ -2,7 +2,7 @@ import asyncio import logging import os -from typing import Dict, Optional, Callable +from typing import Dict, Optional, Callable, Any from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback @@ -63,7 +63,7 @@ class Store: """Return the config path.""" return self.hass.config.path(STORAGE_DIR, self.key) - async def async_load(self): + async def async_load(self) -> Optional[Dict[str, Any]]: """Load data. If the expected version does not match the given version, the migrate diff --git a/tests/common.py b/tests/common.py index 0cb15d683b5..ee181cfa2e9 100644 --- a/tests/common.py +++ b/tests/common.py @@ -392,7 +392,7 @@ def ensure_auth_manager_loaded(auth_mgr): """Ensure an auth manager is considered loaded.""" store = auth_mgr._store if store._users is None: - store._users = OrderedDict() + store._set_defaults() class MockModule: