2018-07-13 09:43:08 +00:00
|
|
|
"""Provide an authentication layer for Home Assistant."""
|
|
|
|
import asyncio
|
|
|
|
import logging
|
|
|
|
from collections import OrderedDict
|
2018-09-11 10:05:15 +00:00
|
|
|
from datetime import timedelta
|
2018-08-21 17:48:24 +00:00
|
|
|
from typing import Any, Dict, List, Optional, Tuple, cast
|
2018-07-13 09:43:08 +00:00
|
|
|
|
2018-08-14 19:14:12 +00:00
|
|
|
import jwt
|
|
|
|
|
2018-07-13 09:43:08 +00:00
|
|
|
from homeassistant import data_entry_flow
|
2018-09-11 10:05:15 +00:00
|
|
|
from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION
|
2018-07-31 14:00:17 +00:00
|
|
|
from homeassistant.core import callback, HomeAssistant
|
2018-08-14 19:14:12 +00:00
|
|
|
from homeassistant.util import dt as dt_util
|
2018-07-13 09:43:08 +00:00
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
from . import auth_store, models
|
2018-08-22 07:52:34 +00:00
|
|
|
from .mfa_modules import auth_mfa_module_from_config, MultiFactorAuthModule
|
|
|
|
from .providers import auth_provider_from_config, AuthProvider, LoginFlow
|
2018-07-13 09:43:08 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2018-08-22 07:52:34 +00:00
|
|
|
_MfaModuleDict = Dict[str, MultiFactorAuthModule]
|
2018-08-16 20:25:41 +00:00
|
|
|
_ProviderKey = Tuple[str, Optional[str]]
|
|
|
|
_ProviderDict = Dict[_ProviderKey, AuthProvider]
|
2018-07-13 09:43:08 +00:00
|
|
|
|
|
|
|
|
2018-07-31 14:00:17 +00:00
|
|
|
async def auth_manager_from_config(
|
|
|
|
hass: HomeAssistant,
|
2018-08-22 07:52:34 +00:00
|
|
|
provider_configs: List[Dict[str, Any]],
|
|
|
|
module_configs: List[Dict[str, Any]]) -> 'AuthManager':
|
2018-08-28 18:54:01 +00:00
|
|
|
"""Initialize an auth manager from config.
|
|
|
|
|
|
|
|
CORE_CONFIG_SCHEMA will make sure do duplicated auth providers or
|
|
|
|
mfa modules exist in configs.
|
|
|
|
"""
|
2018-07-13 09:43:08 +00:00
|
|
|
store = auth_store.AuthStore(hass)
|
|
|
|
if provider_configs:
|
|
|
|
providers = await asyncio.gather(
|
|
|
|
*[auth_provider_from_config(hass, store, config)
|
|
|
|
for config in provider_configs])
|
|
|
|
else:
|
2018-08-16 20:25:41 +00:00
|
|
|
providers = ()
|
2018-07-13 09:43:08 +00:00
|
|
|
# So returned auth providers are in same order as config
|
2018-08-16 20:25:41 +00:00
|
|
|
provider_hash = OrderedDict() # type: _ProviderDict
|
2018-07-13 09:43:08 +00:00
|
|
|
for provider in providers:
|
|
|
|
key = (provider.type, provider.id)
|
|
|
|
provider_hash[key] = provider
|
2018-08-22 07:52:34 +00:00
|
|
|
|
|
|
|
if module_configs:
|
|
|
|
modules = await asyncio.gather(
|
|
|
|
*[auth_mfa_module_from_config(hass, config)
|
|
|
|
for config in module_configs])
|
|
|
|
else:
|
|
|
|
modules = ()
|
|
|
|
# So returned auth modules are in same order as config
|
|
|
|
module_hash = OrderedDict() # type: _MfaModuleDict
|
|
|
|
for module in modules:
|
|
|
|
module_hash[module.id] = module
|
|
|
|
|
|
|
|
manager = AuthManager(hass, store, provider_hash, module_hash)
|
2018-07-13 09:43:08 +00:00
|
|
|
return manager
|
|
|
|
|
|
|
|
|
|
|
|
class AuthManager:
|
|
|
|
"""Manage the authentication for Home Assistant."""
|
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
def __init__(self, hass: HomeAssistant, store: auth_store.AuthStore,
|
2018-08-22 07:52:34 +00:00
|
|
|
providers: _ProviderDict, mfa_modules: _MfaModuleDict) \
|
|
|
|
-> None:
|
2018-07-13 09:43:08 +00:00
|
|
|
"""Initialize the auth manager."""
|
2018-08-22 07:52:34 +00:00
|
|
|
self.hass = hass
|
2018-07-13 09:43:08 +00:00
|
|
|
self._store = store
|
|
|
|
self._providers = providers
|
2018-08-22 07:52:34 +00:00
|
|
|
self._mfa_modules = mfa_modules
|
2018-07-13 09:43:08 +00:00
|
|
|
self.login_flow = data_entry_flow.FlowManager(
|
|
|
|
hass, self._async_create_login_flow,
|
|
|
|
self._async_finish_login_flow)
|
|
|
|
|
|
|
|
@property
|
2018-08-16 20:25:41 +00:00
|
|
|
def active(self) -> bool:
|
2018-07-13 09:43:08 +00:00
|
|
|
"""Return if any auth providers are registered."""
|
|
|
|
return bool(self._providers)
|
|
|
|
|
|
|
|
@property
|
2018-08-16 20:25:41 +00:00
|
|
|
def support_legacy(self) -> bool:
|
2018-07-13 09:43:08 +00:00
|
|
|
"""
|
|
|
|
Return if legacy_api_password auth providers are registered.
|
|
|
|
|
|
|
|
Should be removed when we removed legacy_api_password auth providers.
|
|
|
|
"""
|
|
|
|
for provider_type, _ in self._providers:
|
|
|
|
if provider_type == 'legacy_api_password':
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
@property
|
2018-08-16 20:25:41 +00:00
|
|
|
def auth_providers(self) -> List[AuthProvider]:
|
2018-07-13 09:43:08 +00:00
|
|
|
"""Return a list of available auth providers."""
|
2018-07-13 13:31:20 +00:00
|
|
|
return list(self._providers.values())
|
|
|
|
|
2018-08-22 07:52:34 +00:00
|
|
|
@property
|
|
|
|
def auth_mfa_modules(self) -> List[MultiFactorAuthModule]:
|
|
|
|
"""Return a list of available auth modules."""
|
|
|
|
return list(self._mfa_modules.values())
|
|
|
|
|
|
|
|
def get_auth_mfa_module(self, module_id: str) \
|
|
|
|
-> Optional[MultiFactorAuthModule]:
|
|
|
|
"""Return an multi-factor auth module, None if not found."""
|
|
|
|
return self._mfa_modules.get(module_id)
|
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def async_get_users(self) -> List[models.User]:
|
2018-07-13 13:31:20 +00:00
|
|
|
"""Retrieve all users."""
|
|
|
|
return await self._store.async_get_users()
|
2018-07-13 09:43:08 +00:00
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def async_get_user(self, user_id: str) -> Optional[models.User]:
|
2018-07-13 09:43:08 +00:00
|
|
|
"""Retrieve a user."""
|
|
|
|
return await self._store.async_get_user(user_id)
|
|
|
|
|
2018-08-22 07:52:34 +00:00
|
|
|
async def async_get_user_by_credentials(
|
|
|
|
self, credentials: models.Credentials) -> Optional[models.User]:
|
|
|
|
"""Get a user by credential, return None if not found."""
|
|
|
|
for user in await self.async_get_users():
|
|
|
|
for creds in user.credentials:
|
|
|
|
if creds.id == credentials.id:
|
|
|
|
return user
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def async_create_system_user(self, name: str) -> models.User:
|
2018-07-13 09:43:08 +00:00
|
|
|
"""Create a system user."""
|
|
|
|
return await self._store.async_create_user(
|
|
|
|
name=name,
|
|
|
|
system_generated=True,
|
|
|
|
is_active=True,
|
|
|
|
)
|
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def async_create_user(self, name: str) -> models.User:
|
2018-07-13 13:31:20 +00:00
|
|
|
"""Create a user."""
|
2018-07-15 18:46:15 +00:00
|
|
|
kwargs = {
|
|
|
|
'name': name,
|
|
|
|
'is_active': True,
|
2018-08-16 20:25:41 +00:00
|
|
|
} # type: Dict[str, Any]
|
2018-07-15 18:46:15 +00:00
|
|
|
|
|
|
|
if await self._user_should_be_owner():
|
|
|
|
kwargs['is_owner'] = True
|
|
|
|
|
|
|
|
return await self._store.async_create_user(**kwargs)
|
2018-07-13 13:31:20 +00:00
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def async_get_or_create_user(self, credentials: models.Credentials) \
|
|
|
|
-> models.User:
|
2018-07-13 09:43:08 +00:00
|
|
|
"""Get or create a user."""
|
|
|
|
if not credentials.is_new:
|
2018-08-22 07:52:34 +00:00
|
|
|
user = await self.async_get_user_by_credentials(credentials)
|
|
|
|
if user is None:
|
|
|
|
raise ValueError('Unable to find the user.')
|
|
|
|
else:
|
|
|
|
return user
|
2018-07-13 09:43:08 +00:00
|
|
|
|
|
|
|
auth_provider = self._async_get_auth_provider(credentials)
|
2018-07-13 13:31:20 +00:00
|
|
|
|
|
|
|
if auth_provider is None:
|
|
|
|
raise RuntimeError('Credential with unknown provider encountered')
|
|
|
|
|
2018-07-13 09:43:08 +00:00
|
|
|
info = await auth_provider.async_user_meta_for_credentials(
|
|
|
|
credentials)
|
|
|
|
|
2018-07-15 18:46:15 +00:00
|
|
|
return await self._store.async_create_user(
|
|
|
|
credentials=credentials,
|
2018-08-16 20:25:41 +00:00
|
|
|
name=info.name,
|
|
|
|
is_active=info.is_active,
|
2018-07-15 18:46:15 +00:00
|
|
|
)
|
2018-07-13 09:43:08 +00:00
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def async_link_user(self, user: models.User,
|
|
|
|
credentials: models.Credentials) -> None:
|
2018-07-13 09:43:08 +00:00
|
|
|
"""Link credentials to an existing user."""
|
|
|
|
await self._store.async_link_user(user, credentials)
|
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def async_remove_user(self, user: models.User) -> None:
|
2018-07-13 09:43:08 +00:00
|
|
|
"""Remove a user."""
|
2018-07-13 13:31:20 +00:00
|
|
|
tasks = [
|
|
|
|
self.async_remove_credentials(credentials)
|
|
|
|
for credentials in user.credentials
|
|
|
|
]
|
|
|
|
|
|
|
|
if tasks:
|
|
|
|
await asyncio.wait(tasks)
|
|
|
|
|
2018-07-13 09:43:08 +00:00
|
|
|
await self._store.async_remove_user(user)
|
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def async_activate_user(self, user: models.User) -> None:
|
2018-07-15 18:46:15 +00:00
|
|
|
"""Activate a user."""
|
|
|
|
await self._store.async_activate_user(user)
|
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def async_deactivate_user(self, user: models.User) -> None:
|
2018-07-15 18:46:15 +00:00
|
|
|
"""Deactivate a user."""
|
2018-07-15 21:09:05 +00:00
|
|
|
if user.is_owner:
|
|
|
|
raise ValueError('Unable to deactive the owner')
|
2018-07-15 18:46:15 +00:00
|
|
|
await self._store.async_deactivate_user(user)
|
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def async_remove_credentials(
|
|
|
|
self, credentials: models.Credentials) -> None:
|
2018-07-13 13:31:20 +00:00
|
|
|
"""Remove credentials."""
|
|
|
|
provider = self._async_get_auth_provider(credentials)
|
|
|
|
|
|
|
|
if (provider is not None and
|
|
|
|
hasattr(provider, 'async_will_remove_credentials')):
|
2018-08-16 20:25:41 +00:00
|
|
|
# https://github.com/python/mypy/issues/1424
|
|
|
|
await provider.async_will_remove_credentials( # type: ignore
|
|
|
|
credentials)
|
2018-07-13 13:31:20 +00:00
|
|
|
|
|
|
|
await self._store.async_remove_credentials(credentials)
|
|
|
|
|
2018-08-22 07:52:34 +00:00
|
|
|
async def async_enable_user_mfa(self, user: models.User,
|
|
|
|
mfa_module_id: str, data: Any) -> None:
|
|
|
|
"""Enable a multi-factor auth module for user."""
|
|
|
|
if user.system_generated:
|
|
|
|
raise ValueError('System generated users cannot enable '
|
|
|
|
'multi-factor auth module.')
|
|
|
|
|
|
|
|
module = self.get_auth_mfa_module(mfa_module_id)
|
|
|
|
if module is None:
|
|
|
|
raise ValueError('Unable find multi-factor auth module: {}'
|
|
|
|
.format(mfa_module_id))
|
|
|
|
|
|
|
|
await module.async_setup_user(user.id, data)
|
|
|
|
|
|
|
|
async def async_disable_user_mfa(self, user: models.User,
|
|
|
|
mfa_module_id: str) -> None:
|
|
|
|
"""Disable a multi-factor auth module for user."""
|
|
|
|
if user.system_generated:
|
|
|
|
raise ValueError('System generated users cannot disable '
|
|
|
|
'multi-factor auth module.')
|
|
|
|
|
|
|
|
module = self.get_auth_mfa_module(mfa_module_id)
|
|
|
|
if module is None:
|
|
|
|
raise ValueError('Unable find multi-factor auth module: {}'
|
|
|
|
.format(mfa_module_id))
|
|
|
|
|
|
|
|
await module.async_depose_user(user.id)
|
|
|
|
|
2018-08-26 20:38:52 +00:00
|
|
|
async def async_get_enabled_mfa(self, user: models.User) -> Dict[str, str]:
|
2018-08-22 07:52:34 +00:00
|
|
|
"""List enabled mfa modules for user."""
|
2018-08-26 20:38:52 +00:00
|
|
|
modules = OrderedDict() # type: Dict[str, str]
|
2018-08-22 07:52:34 +00:00
|
|
|
for module_id, module in self._mfa_modules.items():
|
|
|
|
if await module.async_is_user_setup(user.id):
|
2018-08-26 20:38:52 +00:00
|
|
|
modules[module_id] = module.name
|
|
|
|
return modules
|
2018-08-22 07:52:34 +00:00
|
|
|
|
2018-09-11 10:05:15 +00:00
|
|
|
async def async_create_refresh_token(
|
|
|
|
self, user: models.User, client_id: Optional[str] = None,
|
|
|
|
client_name: Optional[str] = None,
|
|
|
|
client_icon: Optional[str] = None,
|
|
|
|
token_type: Optional[str] = None,
|
|
|
|
access_token_expiration: timedelta = ACCESS_TOKEN_EXPIRATION) \
|
2018-08-16 20:25:41 +00:00
|
|
|
-> models.RefreshToken:
|
2018-07-13 09:43:08 +00:00
|
|
|
"""Create a new refresh token for a user."""
|
|
|
|
if not user.is_active:
|
|
|
|
raise ValueError('User is not active')
|
|
|
|
|
|
|
|
if user.system_generated and client_id is not None:
|
|
|
|
raise ValueError(
|
|
|
|
'System generated users cannot have refresh tokens connected '
|
|
|
|
'to a client.')
|
|
|
|
|
2018-09-11 10:05:15 +00:00
|
|
|
if token_type is None:
|
|
|
|
if user.system_generated:
|
|
|
|
token_type = models.TOKEN_TYPE_SYSTEM
|
|
|
|
else:
|
|
|
|
token_type = models.TOKEN_TYPE_NORMAL
|
|
|
|
|
|
|
|
if user.system_generated != (token_type == models.TOKEN_TYPE_SYSTEM):
|
|
|
|
raise ValueError(
|
|
|
|
'System generated users can only have system type '
|
|
|
|
'refresh tokens')
|
|
|
|
|
|
|
|
if token_type == models.TOKEN_TYPE_NORMAL and client_id is None:
|
2018-07-13 09:43:08 +00:00
|
|
|
raise ValueError('Client is required to generate a refresh token.')
|
|
|
|
|
2018-09-11 10:05:15 +00:00
|
|
|
if (token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN and
|
|
|
|
client_name is None):
|
|
|
|
raise ValueError('Client_name is required for long-lived access '
|
|
|
|
'token')
|
|
|
|
|
|
|
|
if token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN:
|
|
|
|
for token in user.refresh_tokens.values():
|
|
|
|
if (token.client_name == client_name and token.token_type ==
|
|
|
|
models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN):
|
|
|
|
# Each client_name can only have one
|
|
|
|
# long_lived_access_token type of refresh token
|
|
|
|
raise ValueError('{} already exists'.format(client_name))
|
|
|
|
|
|
|
|
return await self._store.async_create_refresh_token(
|
|
|
|
user, client_id, client_name, client_icon,
|
|
|
|
token_type, access_token_expiration)
|
2018-07-13 09:43:08 +00:00
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def async_get_refresh_token(
|
|
|
|
self, token_id: str) -> Optional[models.RefreshToken]:
|
2018-08-14 19:14:12 +00:00
|
|
|
"""Get refresh token by id."""
|
|
|
|
return await self._store.async_get_refresh_token(token_id)
|
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def async_get_refresh_token_by_token(
|
|
|
|
self, token: str) -> Optional[models.RefreshToken]:
|
2018-07-13 09:43:08 +00:00
|
|
|
"""Get refresh token by token."""
|
2018-08-14 19:14:12 +00:00
|
|
|
return await self._store.async_get_refresh_token_by_token(token)
|
2018-07-13 09:43:08 +00:00
|
|
|
|
2018-08-21 18:02:55 +00:00
|
|
|
async def async_remove_refresh_token(self,
|
|
|
|
refresh_token: models.RefreshToken) \
|
|
|
|
-> None:
|
|
|
|
"""Delete a refresh token."""
|
|
|
|
await self._store.async_remove_refresh_token(refresh_token)
|
|
|
|
|
2018-07-13 09:43:08 +00:00
|
|
|
@callback
|
2018-08-16 20:25:41 +00:00
|
|
|
def async_create_access_token(self,
|
2018-09-12 11:24:16 +00:00
|
|
|
refresh_token: models.RefreshToken,
|
|
|
|
remote_ip: Optional[str] = None) -> str:
|
2018-07-13 09:43:08 +00:00
|
|
|
"""Create a new access token."""
|
2018-09-12 11:24:16 +00:00
|
|
|
self._store.async_log_refresh_token_usage(refresh_token, remote_ip)
|
|
|
|
|
2018-08-14 19:14:12 +00:00
|
|
|
# pylint: disable=no-self-use
|
2018-09-11 10:05:15 +00:00
|
|
|
now = dt_util.utcnow()
|
2018-08-14 19:14:12 +00:00
|
|
|
return jwt.encode({
|
|
|
|
'iss': refresh_token.id,
|
2018-09-11 10:05:15 +00:00
|
|
|
'iat': now,
|
|
|
|
'exp': now + refresh_token.access_token_expiration,
|
2018-08-14 19:14:12 +00:00
|
|
|
}, refresh_token.jwt_key, algorithm='HS256').decode()
|
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def async_validate_access_token(
|
|
|
|
self, token: str) -> Optional[models.RefreshToken]:
|
2018-08-21 18:02:55 +00:00
|
|
|
"""Return refresh token if an access token is valid."""
|
2018-08-14 19:14:12 +00:00
|
|
|
try:
|
|
|
|
unverif_claims = jwt.decode(token, verify=False)
|
|
|
|
except jwt.InvalidTokenError:
|
|
|
|
return None
|
2018-07-13 09:43:08 +00:00
|
|
|
|
2018-08-14 19:14:12 +00:00
|
|
|
refresh_token = await self.async_get_refresh_token(
|
2018-08-16 20:25:41 +00:00
|
|
|
cast(str, unverif_claims.get('iss')))
|
2018-08-14 19:14:12 +00:00
|
|
|
|
|
|
|
if refresh_token is None:
|
|
|
|
jwt_key = ''
|
|
|
|
issuer = ''
|
|
|
|
else:
|
|
|
|
jwt_key = refresh_token.jwt_key
|
|
|
|
issuer = refresh_token.id
|
|
|
|
|
|
|
|
try:
|
|
|
|
jwt.decode(
|
|
|
|
token,
|
|
|
|
jwt_key,
|
|
|
|
leeway=10,
|
|
|
|
issuer=issuer,
|
|
|
|
algorithms=['HS256']
|
|
|
|
)
|
|
|
|
except jwt.InvalidTokenError:
|
2018-07-13 09:43:08 +00:00
|
|
|
return None
|
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
if refresh_token is None or not refresh_token.user.is_active:
|
2018-07-13 09:43:08 +00:00
|
|
|
return None
|
|
|
|
|
2018-08-14 19:14:12 +00:00
|
|
|
return refresh_token
|
2018-07-13 09:43:08 +00:00
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def _async_create_login_flow(
|
|
|
|
self, handler: _ProviderKey, *, context: Optional[Dict],
|
|
|
|
data: Optional[Any]) -> data_entry_flow.FlowHandler:
|
2018-07-13 09:43:08 +00:00
|
|
|
"""Create a login flow."""
|
|
|
|
auth_provider = self._providers[handler]
|
|
|
|
|
2018-08-21 18:03:38 +00:00
|
|
|
return await auth_provider.async_login_flow(context)
|
2018-07-13 09:43:08 +00:00
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def _async_finish_login_flow(
|
2018-08-22 07:52:34 +00:00
|
|
|
self, flow: LoginFlow, result: Dict[str, Any]) \
|
2018-08-21 17:48:24 +00:00
|
|
|
-> Dict[str, Any]:
|
2018-08-21 08:18:04 +00:00
|
|
|
"""Return a user as result of login flow."""
|
2018-07-13 09:43:08 +00:00
|
|
|
if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
|
2018-08-21 17:48:24 +00:00
|
|
|
return result
|
2018-07-13 09:43:08 +00:00
|
|
|
|
2018-08-22 07:52:34 +00:00
|
|
|
# we got final result
|
|
|
|
if isinstance(result['data'], models.User):
|
|
|
|
result['result'] = result['data']
|
|
|
|
return result
|
|
|
|
|
2018-07-13 09:43:08 +00:00
|
|
|
auth_provider = self._providers[result['handler']]
|
2018-08-21 17:48:24 +00:00
|
|
|
credentials = await auth_provider.async_get_or_create_credentials(
|
2018-07-13 09:43:08 +00:00
|
|
|
result['data'])
|
|
|
|
|
2018-08-21 17:48:24 +00:00
|
|
|
if flow.context is not None and flow.context.get('credential_only'):
|
|
|
|
result['result'] = credentials
|
|
|
|
return result
|
2018-08-21 08:18:04 +00:00
|
|
|
|
2018-08-22 07:52:34 +00:00
|
|
|
# multi-factor module cannot enabled for new credential
|
|
|
|
# which has not linked to a user yet
|
|
|
|
if auth_provider.support_mfa and not credentials.is_new:
|
|
|
|
user = await self.async_get_user_by_credentials(credentials)
|
|
|
|
if user is not None:
|
|
|
|
modules = await self.async_get_enabled_mfa(user)
|
|
|
|
|
|
|
|
if modules:
|
|
|
|
flow.user = user
|
|
|
|
flow.available_mfa_modules = modules
|
|
|
|
return await flow.async_step_select_mfa_module()
|
|
|
|
|
|
|
|
result['result'] = await self.async_get_or_create_user(credentials)
|
2018-08-21 17:48:24 +00:00
|
|
|
return result
|
2018-08-21 08:18:04 +00:00
|
|
|
|
2018-07-13 09:43:08 +00:00
|
|
|
@callback
|
2018-08-16 20:25:41 +00:00
|
|
|
def _async_get_auth_provider(
|
|
|
|
self, credentials: models.Credentials) -> Optional[AuthProvider]:
|
2018-08-24 08:28:43 +00:00
|
|
|
"""Get auth provider from a set of credentials."""
|
2018-07-13 09:43:08 +00:00
|
|
|
auth_provider_key = (credentials.auth_provider_type,
|
|
|
|
credentials.auth_provider_id)
|
2018-07-13 13:31:20 +00:00
|
|
|
return self._providers.get(auth_provider_key)
|
2018-07-15 18:46:15 +00:00
|
|
|
|
2018-08-16 20:25:41 +00:00
|
|
|
async def _user_should_be_owner(self) -> bool:
|
2018-07-15 18:46:15 +00:00
|
|
|
"""Determine if user should be owner.
|
|
|
|
|
|
|
|
A user should be an owner if it is the first non-system user that is
|
|
|
|
being created.
|
|
|
|
"""
|
|
|
|
for user in await self._store.async_get_users():
|
|
|
|
if not user.system_generated:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|