WIP: Add multi-factor authentication plug-able modules#15335
WIP: Add multi-factor authentication plug-able modules#15335awarecan wants to merge 101 commits intohome-assistant:devfrom
Conversation
|
For other auth provider, such as To manually generate import pyotp
print(pyotp.random_base32()) |
homeassistant/auth.py
Outdated
| """Return the name of the auth provider.""" | ||
| return self.config.get(CONF_NAME, self.DEFAULT_TITLE) | ||
|
|
||
| async def load_modules(self, moudle_configs): |
| 'users': [], | ||
| } | ||
|
|
||
| if 'users' not in data: |
There was a problem hiding this comment.
Data file was corrupted? Cannot recall why I added this statement, can be removed if you feel redundant.
There was a problem hiding this comment.
It's redundant. If storage doesn't exist, we get None back and set the default above. If we save, the default was the foundation.
|
It's usually wise to discuss these things before creating big PRs like this. That way there is no work wasted if we have different views on the architecture. |
| await self.hass.async_add_executor_job( | ||
| data.validate_login, username, password) | ||
| finally: | ||
| await data.async_save() |
There was a problem hiding this comment.
legacy code, should be removed. It was used in v1 implementation when auth module save data in auth provider storage.
|
I thought we had the discussion around plugable auth module in #15225 |
|
That's true, but that's only 1 part of this. Starting code review now |
homeassistant/auth.py
Outdated
| """Initialize the login flow.""" | ||
| self._auth_provider = auth_provider | ||
| # self._auth_modules is mutable, we need a copy | ||
| self._auth_modules = auth_provider.modules.copy() |
There was a problem hiding this comment.
auth modules should not change anymore after startup ?
homeassistant/auth.py
Outdated
| """Raised when we encounter invalid authentication.""" | ||
|
|
||
|
|
||
| class SessionStore: |
There was a problem hiding this comment.
I don't see any need for the session store. This should just be stored on the instance of the login flow handler. It will then be removed with that.
There was a problem hiding this comment.
Yes, it was inherited from previous version of implement. I agreed that is over-design, we should not have session at all, flow_id can be used as a session.
| @@ -0,0 +1,66 @@ | |||
| """Example auth module.""" | |||
There was a problem hiding this comment.
This should not live under auth provider as it has nothing to do with it?
I suggest we restructure our auth into
homeassistant/auth/__init__.pyhomeassistant/auth/providers/homeassistant.pyhomeassistant/auth/modules/totp.py
I also not sure if I like modules, it's too generic. What about two_factor ?
| }, extra=vol.PREVENT_EXTRA) | ||
|
|
||
| STORAGE_VERSION = 1 | ||
| STORAGE_KEY = 'auth_module.insecure_example' |
There was a problem hiding this comment.
I think that we should store these on the user objects in a data object that is stored/restored when user is saved/restored.
homeassistant/auth.py
Outdated
| return {} | ||
|
|
||
|
|
||
| class LoginFlow(data_entry_flow.FlowHandler): |
There was a problem hiding this comment.
I don't like this architecture.
I think that we should implement it as a FlowHandler that is not extended by the login flows, but instead wraps one.
It should take the login flow it needs to wrap. Once that flow returns a create entry, it should hold that value, look up the user, see if any two factor has been registered, instantiate those and let them handle the next step.
You can use __getattr__ to intercept the methods on the LoginFlow and decide where to redirect the call.
There was a problem hiding this comment.
Hmm , maybe I do like this architecture. I just don't like how it iterates through the modules and mutates them.
homeassistant/auth.py
Outdated
| if not errors and result: | ||
| return await self.async_finish(result) | ||
| else: | ||
| session_id = await auth_module.\ |
There was a problem hiding this comment.
We shouldn't create sessions, just instantiate and store on this instance.
homeassistant/auth.py
Outdated
|
|
||
| async def async_finish(self, username): | ||
| """Handle the pass of login flow.""" | ||
| if self._auth_modules: |
There was a problem hiding this comment.
Use a guard clause
if not X:
return Y
…
homeassistant/auth.py
Outdated
| async def async_finish(self, username): | ||
| """Handle the pass of login flow.""" | ||
| if self._auth_modules: | ||
| _, auth_module = self._auth_modules.popitem(False) |
There was a problem hiding this comment.
This is weird, instead, once finish called, we should check if user has any 2fa configured (stored on user object?) and then create a local list of 2fa to ask which we can mutate.
There was a problem hiding this comment.
A user can have multiple 2FA active, it only needs to pass 1 to log in.
There was a problem hiding this comment.
I see your point, so the flow should like
admin create user -> user login -> user enable/setup one of many 2fa from a list
user select auth_provider -> auth_provider validate -> user select which 2fa he/she want to use -> ask 2fa challenge -> 2fa module validate -> user logged in
There was a problem hiding this comment.
Yep, and we can do it all within a login flow without needed a frontend change
homeassistant/auth.py
Outdated
| if self._auth_modules: | ||
| _, auth_module = self._auth_modules.popitem(False) | ||
|
|
||
| step_id = 'auth_module_' + auth_module.type |
There was a problem hiding this comment.
string concatenation should be done using .format
homeassistant/auth.py
Outdated
| await self.async_save() | ||
| return data | ||
|
|
||
| async def async_cleanup_session(self): |
There was a problem hiding this comment.
Session expiration is up to the LoginFlow to enforce.
homeassistant/auth.py
Outdated
| vol.Optional(CONF_NAME): str, | ||
| # Specify ID if you have two auth providers for same type. | ||
| vol.Optional(CONF_ID): str, | ||
| vol.Optional(CONF_MODULES): |
There was a problem hiding this comment.
Modules should not be nested under an auth provider. Instead, users should be able to enable 2fa for their account.
There was a problem hiding this comment.
So the configuration should like
# configuration.yml
homeassistant:
auth_providers:
- type: a_provder
- type: b_provider
auth_2fa_modules:
- type: totp
- type: hotp_smsuser should be able to enable one of many module in his setting page
user should be able to select use which module after auth_provider validate
That is the sequence used in my Cisco VPN client configured with okta integration.
|
Hello @awarecan, When attempting to inspect the commits of your pull request for CLA signature status among all authors we encountered commit(s) which were not linked to a GitHub account, thus not allowing us to determine their status(es). The commits that are missing a linked GitHub account are the following:
Unfortunately, we are unable to accept this pull request until this situation is corrected. Here are your options:
We apologize for this inconvenience, especially since it usually bites new contributors to Home Assistant. We hope you understand the need for us to protect ourselves and the great community we all have built legally. The best thing to come out of this is that you only need to fix this once and it benefits the entire Home Assistant and GitHub community. Thanks, I look forward to checking this PR again soon! ❤️ |
* support for tuya platform * support tuya platform * lint fix * change dependency * add tuya platform support * remove tuya platform except switch. fix code as required * fix the code as review required * fix as required * fix a mistake
* Update __init__.py * Update requirements_all.txt
There are some devices that speak HomeKit that we shouldn't expose. Some bridges (such as the Hue) provide reduced functionality over HomeKit and have a functional native API, so should be ignored. We also shouldn't offer to configure the built-in Home Assistant HomeKit bridge.
* Add HomematicIP security zone * Update access point tests * Fix state if not armed and coments * Add comment for the empty state_attributes * Fix comment * Fix spelling
* Add Python 3.8-dev tox tests. * Allow failures on 3.8-dev * Allow failures on 3.8-dev take2 * Only run on pushes to dev
## Description: Make typing checks more strict: add `--strict-optional` flag that forbids implicit None return type. This flag will become default in the next version of mypy (0.600) Add `homeassistant/util/` to checked dirs. ## Checklist: - [x] The code change is tested and works locally. - [x] Local tests pass with `tox`. **Your PR cannot be merged unless tests pass**
* Upgrade mypy to 0.600 * Upgrade mypy to 0.610 * Typing improvements * remove unneeded or * remove merge artifact * Update loader.py
* User management * Lint * Fix dict * Reuse data instance * OrderedDict all the way
Only include base class and insecure_example
|
Rebase seems mess up, close this PR for now |
Description:
Add multi-factor authentication support for new auth system.
Auth module
Multi-factor auth modules has been added to auth/modules folder.
totpis Time-based One Time Password module which compatible with Google Authenticator and Authy (not test yet).insecure_exampleis 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:
REQUIREMENTSvariable (example).requirements_all.txtby runningscript/gen_requirements_all.py.If the code does not interact with devices: