Skip to content

Commit

Permalink
Remove user.mfa_modules
Browse files Browse the repository at this point in the history
Add MultiFactorAuthModule.async_is_user_setup
  • Loading branch information
awarecan committed Jul 18, 2018
1 parent ec21f10 commit 658195c
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 111 deletions.
13 changes: 9 additions & 4 deletions homeassistant/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,7 @@ async def async_enable_user_mfa(self, user, mfa_module_id, data):
except vol.Invalid as err:
raise ValueError('Data does not match schema: {}'.format(err))

result = await module.async_setup_user(user.id, data)
await self._store.async_enable_user_mfa(user, mfa_module_id)
return result
return await module.async_setup_user(user.id, data)

async def async_disable_user_mfa(self, user, mfa_module_id):
"""Disable a multi-factor auth module for user."""
Expand All @@ -237,7 +235,14 @@ async def async_disable_user_mfa(self, user, mfa_module_id):

module = self.get_auth_mfa_module(mfa_module_id)
await module.async_depose_user(user.id)
await self._store.async_disable_user_mfa(user, mfa_module_id)

async def async_get_enabled_mfa(self, user):
"""List enabled mfa modules for user."""
module_ids = []
for module_id, module in self._mfa_modules.items():
if await module.async_is_user_setup(user.id):
module_ids.append(module_id)
return module_ids

async def async_create_refresh_token(self, user, client_id=None):
"""Create a new refresh token for a user."""
Expand Down
19 changes: 0 additions & 19 deletions homeassistant/auth/auth_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,24 +107,6 @@ async def async_remove_credentials(self, credentials):

await self.async_save()

async def async_enable_user_mfa(self, user, mfa_module_id):
"""Enable a mfa module for user."""
local_user = await self.async_get_user(user.id)
if mfa_module_id in local_user.mfa_modules:
return

local_user.mfa_modules.append(mfa_module_id)
await self.async_save()

async def async_disable_user_mfa(self, user, mfa_module_id):
"""Disable a mfa module for user."""
local_user = await self.async_get_user(user.id)
if mfa_module_id not in local_user.mfa_modules:
return

local_user.mfa_modules.remove(mfa_module_id)
await self.async_save()

async def async_create_refresh_token(self, user, client_id=None):
"""Create a new token for a user."""
refresh_token = models.RefreshToken(user=user, client_id=client_id)
Expand Down Expand Up @@ -206,7 +188,6 @@ async def async_save(self):
'is_active': user.is_active,
'name': user.name,
'system_generated': user.system_generated,
'mfa_modules': user.mfa_modules
}
for user in self._users.values()
]
Expand Down
8 changes: 6 additions & 2 deletions homeassistant/auth/mfa_modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
MULTI_FACTOR_AUTH_MODULE_SCHEMA = vol.Schema({
vol.Required(CONF_TYPE): str,
vol.Optional(CONF_NAME): str,
# Specify ID if you have two auth module for same type.
# Specify ID if you have two mfa auth module for same type.
vol.Optional(CONF_ID): str,
}, extra=vol.ALLOW_EXTRA)

Expand Down Expand Up @@ -120,14 +120,18 @@ def setup_schema(self):
"""
return None

async def async_setup_user(self, user_id, data):
async def async_setup_user(self, user_id, setup_data):
"""Setup mfa auth module for user."""
raise NotImplementedError

async def async_depose_user(self, user_id):
"""Remove user from mfa module."""
raise NotImplementedError

async def async_is_user_setup(self, user_id):
"""Return whether user is setup."""
raise NotImplementedError

async def async_validation(self, user_id, user_input):
"""Return True if validation passed."""
raise NotImplementedError
38 changes: 22 additions & 16 deletions homeassistant/auth/mfa_modules/insecure_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
MULTI_FACTOR_AUTH_MODULE_SCHEMA

CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({
vol.Required('users'): [vol.Schema({
vol.Required('data'): [vol.Schema({
vol.Required('user_id'): str,
vol.Required('pin'): str,
})]
Expand All @@ -25,8 +25,7 @@ class InsecureExampleModule(MultiFactorAuthModule):
def __init__(self, hass, config):
"""Initialize the user data store."""
super().__init__(hass, config)
self._data = None
self._users = config['users']
self._data = config['data']

@property
def input_schema(self):
Expand All @@ -38,35 +37,42 @@ def setup_schema(self):
"""Validate async_setup_user input data."""
return vol.Schema({'pin': str})

async def async_setup_user(self, user_id, data):
async def async_setup_user(self, user_id, setup_data):
"""Setup mfa module for user."""
# data shall has been validate in caller
pin = data['pin']
pin = setup_data['pin']

for user in self._users:
if user and user.get('user_id') == user_id:
for data in self._data:
if data['user_id'] == user_id:
# already setup, override
user['pin'] = pin
data['pin'] = pin
return

self._users.append({'user_id': user_id, 'pin': pin})
self._data.append({'user_id': user_id, 'pin': pin})

async def async_depose_user(self, user_id):
"""Remove user from mfa module."""
found = None
for user in self._users:
if user and user.get('user_id') == user_id:
found = user
for data in self._data:
if data['user_id'] == user_id:
found = data
break
if found:
self._users.remove(found)
self._data.remove(found)

async def async_is_user_setup(self, user_id):
"""Return whether user is setup."""
for data in self._data:
if data['user_id'] == user_id:
return True
return False

async def async_validation(self, user_id, user_input):
"""Return True if validation passed."""
for user in self._users:
if user_id == user.get('user_id'):
for data in self._data:
if data['user_id'] == user_id:
# user_input has been validate in caller
if user.get('pin') == user_input['pin']:
if data['pin'] == user_input['pin']:
return True

return False
3 changes: 0 additions & 3 deletions homeassistant/auth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ class User:
# Tokens associated with a user.
refresh_tokens = attr.ib(type=dict, default=attr.Factory(dict), cmp=False)

# Enabled multi-factor auth modules of a user.
mfa_modules = attr.ib(type=list, default=attr.Factory(list), cmp=False)


@attr.s(slots=True)
class RefreshToken:
Expand Down
10 changes: 3 additions & 7 deletions homeassistant/auth/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,9 @@ async def async_start_mfa(self, flow_result):
self._user = await self._auth_manager.\
async_get_user_by_credentials(credentials)

# module in user.mfa_modules may not loaded
# the config may have changed after the user enabled module
# we need double check available mfa_modules for this user
if self._user and self._user.mfa_modules:
modules = [m_id for m_id in self._user.mfa_modules
if self._auth_manager.
get_auth_mfa_module(m_id)]
if self._user is not None:
modules = await self._auth_manager.async_get_enabled_mfa(
self._user)

if modules:
self._auth_modules = modules
Expand Down
1 change: 0 additions & 1 deletion homeassistant/components/config/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ def _user_info(user):
'is_owner': user.is_owner,
'is_active': user.is_active,
'system_generated': user.system_generated,
'enabled_multi_factor_auth': user.mfa_modules,
'credentials': [
{
'type': c.auth_provider_type,
Expand Down
25 changes: 17 additions & 8 deletions tests/auth/mfa_modules/test_insecure_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ async def test_validate(hass):
"""Test validating pin."""
auth_module = await auth_mfa_module_from_config(hass, {
'type': 'insecure_example',
'users': [{'user_id': 'test-user', 'pin': '123456'}]
'data': [{'user_id': 'test-user', 'pin': '123456'}]
})

result = await auth_module.async_validation(
Expand All @@ -29,12 +29,12 @@ async def test_setup_user(hass):
"""Test setup user."""
auth_module = await auth_mfa_module_from_config(hass, {
'type': 'insecure_example',
'users': []
'data': []
})

await auth_module.async_setup_user(
'test-user', {'pin': '123456'})
assert len(auth_module._users) == 1
assert len(auth_module._data) == 1

result = await auth_module.async_validation(
'test-user', {'pin': '123456'})
Expand All @@ -45,12 +45,22 @@ async def test_depose_user(hass):
"""Test despose user."""
auth_module = await auth_mfa_module_from_config(hass, {
'type': 'insecure_example',
'users': [{'user_id': 'test-user', 'pin': '123456'}]
'data': [{'user_id': 'test-user', 'pin': '123456'}]
})
assert len(auth_module._users) == 1
assert len(auth_module._data) == 1

await auth_module.async_depose_user('test-user')
assert len(auth_module._users) == 0
assert len(auth_module._data) == 0


async def test_is_user_setup(hass):
"""Test is user setup."""
auth_module = await auth_mfa_module_from_config(hass, {
'type': 'insecure_example',
'data': [{'user_id': 'test-user', 'pin': '123456'}]
})
assert await auth_module.async_is_user_setup('test-user') is True
assert await auth_module.async_is_user_setup('invalid-user') is False


async def test_login(hass):
Expand All @@ -60,14 +70,13 @@ async def test_login(hass):
'users': [{'username': 'test-user', 'password': 'test-pass'}],
}], [{
'type': 'insecure_example',
'users': [{'user_id': 'mock-user', 'pin': '123456'}]
'data': [{'user_id': 'mock-user', 'pin': '123456'}]
}])
user = MockUser(
id='mock-user',
is_owner=False,
is_active=False,
name='Paulus',
mfa_modules=['insecure_example']
).add_to_auth_manager(hass.auth)
await hass.auth.async_link_user(user, Credentials(
id='mock-id',
Expand Down
Loading

0 comments on commit 658195c

Please sign in to comment.