-
-
Notifications
You must be signed in to change notification settings - Fork 31.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add multi-factor authentication modules #15489
Conversation
tests/scripts/test_auth.py
Outdated
from homeassistant.scripts import auth as script_auth | ||
from homeassistant.auth.providers import homeassistant as hass_auth | ||
|
||
from tests.common import register_auth_provider | ||
from tests.common import register_auth_provider, MockUser |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'tests.common.MockUser' imported but unused
tests/scripts/test_auth.py
Outdated
@@ -3,10 +3,11 @@ | |||
|
|||
import pytest | |||
|
|||
from homeassistant.auth.models import Credentials |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'homeassistant.auth.models.Credentials' imported but unused
For architecture, if we need to resolve the flow result to credentials, we should just do that. We can't be making things up. Maybe we need a config flow that wraps the provider config flow instead of using inheritance. That way we can forward calls and then intercept the call to async_create_entry. But that too is a complicated solution. We should focus on the rest of the auth system before picking this up again. |
Sure, I am going to finish change user.mfa_modules design tomorrow, then I will put this PR on the shelf for a while.
|
I was thinking about that comment, and with credentials we do the exact opposite. We store the data that connects a user to an auth provider in the user. We should probably do that for MFA too. That way we can ask an MFA module: given this config and this value, is this ok? The only downside is if an MFA module wants to update the data 🤔 |
That is current design: in mfa data store: {
"data": {
"users": [
{
"ota_secret": "some_secret",
"user_id": "some_id"
}
]
},
"key": "auth_module.totp",
"version": 1
} In auth "users": [
{
"id": "some_id",
"is_active": true,
"is_owner": true,
"mfa_modules": ["totp"],
"name": null,
"system_generated": false
}
] |
I can just delete |
Yeah, single source of truth 👍 |
Rebase against dev |
homeassistant/auth/__init__.py
Outdated
"""Result of a credential login flow.""" | ||
self, flow: LoginFlow, result: Dict[str, Any]) \ | ||
-> Dict[str, Any]: | ||
"""Return a user as result of login flow.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to add a lot more documentation to this method . What is happening, when and why.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I am slightly confused why we are even making changes for this method?
All auth provider login flows will create async_start_mfa(flow_result)
. The MFA flow will show more forms if necessary and eventually will pass the original flow result on if MFA has passed. That means that this method will not see any new data?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed design, now the auth provider (not the base class) will not know about mfa, they just call async_finish
after validate credential. The _async_finish_login_flow
will do the heave lift, if need mfa then change the result type to form by calling async_step_select_mfa_module
"""Initialize an auth module.""" | ||
self.hass = hass | ||
self.config = config | ||
_LOGGER.debug('auth mfa module %s loaded.', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Constructors should never have side effects. Add this to auth_mfa_module_from_config
@@ -58,6 +61,11 @@ def name(self) -> str: | |||
"""Return the name of the auth provider.""" | |||
return self.config.get(CONF_NAME, self.DEFAULT_TITLE) | |||
|
|||
@property | |||
def support_mfa(self) -> bool: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When would this not be the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For trusted networks auth provider
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah interesting.
"""Handle the first step of login flow. | ||
|
||
Return self.async_show_form(step_id='init') if user_input == None. | ||
Return await self.async_start_mfa(flow_result) if login init step pass. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is async_start_mfa
defined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expect async_start_mfa
to not do anything if the context is type=link_user
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha, that is from old design, missed to update document. It should change to
Return await self.async_finish(flow_result) if login init step pass
Rebase done, clean it up as much as possible, test should be able to pass, typing still need fix. And this PR is still 900+ lines need review 😄 |
from homeassistant.util import dt as dt_util | ||
from homeassistant.util.decorator import Registry | ||
|
||
from ..auth_store import AuthStore | ||
from ..models import Credentials, UserMeta | ||
from ..models import Credentials, User, UserMeta |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'..models.User' imported but unused
import importlib | ||
import logging | ||
import types | ||
from typing import Any, Dict, List, Optional | ||
from typing import Any, Dict, List, Optional, Callable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'typing.Callable' imported but unused
@@ -1,4 +1,5 @@ | |||
"""Auth providers for Home Assistant.""" | |||
from collections import OrderedDict |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'collections.OrderedDict' imported but unused
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉
* Get user after login flow finished * Add multi factor authentication support * Typings
Description:
Add multi-factor authentication support for new auth system.
Auth module
Multi-factor auth modules has been added to auth/modules folder.
~~totp is Time-based One Time Password module which compatible with Google Authenticator and Authy (not test yet). ~~ will be included in separate PR
insecure_example
is a Example designed for demo and unit testing purpose, should not be used in any production system.Multi-facot auth module can be used mixed-match with auth providers. After normal auth provider validate, if there are auth modules enabled for the specific user, the login flow will direct to auth module input form. Auth module use separated storage, can be hardening it in future.
Related issue (if applicable): fixes #
Pull request in developers.home-assistant.io with documentation (if applicable): home-assistant/developers.home-assistant#52
Example entry for
configuration.yaml
(if applicable):Checklist:
tox
. Your PR cannot be merged unless tests passIf user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
REQUIREMENTS
variable (example).requirements_all.txt
by runningscript/gen_requirements_all.py
.If the code does not interact with devices: