Skip to content
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 Time-based Onetime Password Multi-factor Authentication Module #16129

Merged
merged 9 commits into from
Aug 26, 2018

Conversation

awarecan
Copy link
Contributor

@awarecan awarecan commented Aug 22, 2018

Description:

totp is Time-based One Time Password module which compatible with Google Authenticator and Authy.

See #15489 learn more about multi-factor authentication module

Edit: a totp MFA auth module named "Authenticator app" will be auto loaded if no auth_mfa_modules config section defined in configuration.yaml

Related issue (if applicable): fixes #

Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#6089

Example entry for configuration.yaml (if applicable):

# configuration.yaml
homeassistant:
  # Auth Provider
  auth_providers:
    - type: homeassistant
  auth_mfa_modules:
    - type: totp

Checklist:

  • The code change is tested and works locally.
  • Local tests pass with tox. Your PR cannot be merged unless tests pass

If user exposed functionality or configuration variables are added/changed:

If the code does not interact with devices:

  • Tests have been added to verify that the new code works.


provider = hass.auth.auth_providers[0]

result = await hass.auth.login_flow.async_init((provider.type, provider.id))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line too long (80 > 79 characters)

"""Test the Time-based One Time Password (MFA) auth module."""
from unittest.mock import patch

import pytest

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'pytest' imported but unused

@@ -0,0 +1,142 @@
"""Time-based One Time Password auth module."""
import logging
from typing import Any, Dict, List, Optional

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'typing.List' imported but unused
'typing.Optional' imported but unused

user[STORAGE_OTA_SECRET] = ota_secret
return ota_secret

self._users.append({ # type: ignore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should users be a list of dicts? It looks like we're assuming that user ids are unique so then a dict (of dicts) makes more sense to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copied code from auth provider, should change to dict


def _validate_2fa(self, user_id: str, code: str) -> bool:
"""Validate two factor authentication code."""
if user_id is None or code is None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When can this happen? Code input is validated by the input schema.

if ota_secret is None:
# even we cannot find user, we still do verify
# to make timing the same as if user was found.
pyotp.TOTP(pyotp.random_base32()).verify(code)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the time for generating the ota_secret via random_base32 insignificant compared to the rest of the validation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to a fixed dummy code

"""List enabled mfa modules for user."""
module_ids = []
modules = {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OrderedDict

Copy link
Member

@balloob balloob left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see anything related to setting up TOTP for a user in this PR besides a call to setup. Where is the schema?

See my PR on how this should look: https://github.com/home-assistant/home-assistant/pull/16141/files#r212261624

@awarecan
Copy link
Contributor Author

PR order problem, the setup flow part need wait #16141 merged first.

@awarecan
Copy link
Contributor Author

Rebase on #16141, add setup flow, add QR code generation

Need first merge #16141, then merge home-assistant/frontend#1590, then merge this PR, finally need add/fix translate to display QR code

user: User) -> None:
"""Initialize the setup flow."""
super().__init__(auth_module, user.id)
self._auth_module = auth_module # type: TotpAuthModule
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already set in the parent class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For correct the stupid tying

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it looks like we don't need the parent class. Do we really think the default init step will not be overridden in other mfa flows?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the inheritance is not needed as we're overriding everything.


if user_input:
verified = await self.hass.async_add_executor_job( # type: ignore
pyotp.TOTP(self._ota_secret).verify, user_input['code'])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't self._auth_module.async_validation be used here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is validate against a temp secret has not been saved yet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

import pyqrcode

qr_code = pyqrcode.create(data)
return str(qr_code.png_as_base64_str(scale=4))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the svg method and we don't need to install pypng as dependency.

return str(qr_code.png_as_base64_str(scale=4))


@bind_hass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to add @bind_hass for internal methods.

image = _generate_qr_code(url)
return ota_secret, url, image

return await hass.async_add_executor_job(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the whole method is about running a sync method, just make the method sync on the global scope and call with async_add_executor_job directly.

"step": {
"init": {
"title": "Scan this QR code with your app",
"description": "Scan the QR code with your authentication app, such as **Google Authenticator** or **Authy**. \nOr enter {code} instead. \n\n[QR Code]{url}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Markdown image syntax is ![alt text](url). I don't see that in the description?

@awarecan
Copy link
Contributor Author

The only thing left is translation string. I am not familiar with the process, @balloob can you take care of it?

@awarecan awarecan added this to the 0.77 milestone Aug 24, 2018
@awarecan
Copy link
Contributor Author

awarecan commented Aug 25, 2018

Finally get this work. There is a change in front end need to be done. We are using data:image to embed image in markdown, however it will be filter out by filterXSS. Filter out data:image is sort of core function provided by js-xss lib, there is no easy way to workaround it.

So, I have to disable filterXSS to make my QR code displayed. I think the properly way to handle it (see front end PR: home-assistant/frontend#1599), maybe we should make filterXSS as an option in ha-markdown component, and disable it in config flow, and this mfa setup flow, since we can tightly control the markdown string be parsed in those flow, we don't have XSS concern for it. But it may help on other use cases.

My quick fix in front end to get following screenshot.

diff --git a/src/components/ha-markdown.js b/src/components/ha-markdown.js
index 355507ec..e13f1b4b 100644
--- a/src/components/ha-markdown.js
+++ b/src/components/ha-markdown.js
@@ -46,11 +46,11 @@ class HaMarkdown extends EventsMixin(PolymerElement) {
       this._renderScheduled = false;
 
       if (this._scriptLoaded === 1) {
-        this.innerHTML = this.filterXSS(this.marked(this.content, {
+        this.innerHTML = this.marked(this.content, {
           gfm: true,
           tables: true,
           breaks: true
-        }));
+        });
         this._resize();
 
         const walker = document.createTreeWalker(this, 1 /* SHOW_ELEMENT */, null, false);

image

@@ -0,0 +1,213 @@
"""Time-based One Time Password auth module."""
import logging
from base64 import b64encode

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'base64.b64encode' imported but unused

@ghost ghost assigned balloob Aug 26, 2018
@balloob balloob merged commit 47755fb into home-assistant:dev Aug 26, 2018
@ghost ghost removed the in progress label Aug 26, 2018
balloob pushed a commit that referenced this pull request Aug 26, 2018
…16129)

* Add Time-based Onetime Password Multi-factor Auth

Add TOTP setup flow, generate QR code

* Resolve rebase issue

* Use svg instead png for QR code

* Lint and typing

* Fix translation

* Load totp auth module by default

* use <svg> tag instead markdown image

* Update strings

* Cleanup
@balloob balloob mentioned this pull request Aug 29, 2018
@home-assistant home-assistant locked and limited conversation to collaborators Dec 10, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants