forked from home-assistant/core
Added command_line auth provider that validates credentials by calling a command (#19985)
* Added external auth provider that calls a configurable program Closes #19975 * Raise proper InvalidAuth exception on OSError during program execution * Changed name of external auth provider to command_line * Renamed program config option to command in command_line auth provider * Made meta variable parsing in command_line auth provider optional * Added tests for command_line auth provider * Fixed indentation * Suppressed wrong pylint warning * Fixed linting * Added test for command line auth provider login flow * Log error when user fails authentication * Use %r formatter instead of explicit repr() * Mix all used names of typing module into module namespace I consider this nasty and bad coding style, but was requested by @awarecan for consistency with the remaining codebase. * Small code style change * Strip usernames with command_line auth provider
This commit is contained in:
committed by
Paulus Schoutsen
parent
fb1da53568
commit
06f3e8137a
@@ -0,0 +1,148 @@
|
||||
"""Tests for the command_line auth provider."""
|
||||
|
||||
from unittest.mock import Mock
|
||||
import os
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.auth import auth_store, models as auth_models, AuthManager
|
||||
from homeassistant.auth.providers import command_line
|
||||
from homeassistant.const import CONF_TYPE
|
||||
|
||||
from tests.common import mock_coro
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def store(hass):
|
||||
"""Mock store."""
|
||||
return auth_store.AuthStore(hass)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def provider(hass, store):
|
||||
"""Mock provider."""
|
||||
return command_line.CommandLineAuthProvider(hass, store, {
|
||||
CONF_TYPE: "command_line",
|
||||
command_line.CONF_COMMAND: os.path.join(
|
||||
os.path.dirname(__file__), "test_command_line_cmd.sh"
|
||||
),
|
||||
command_line.CONF_ARGS: [],
|
||||
command_line.CONF_META: False,
|
||||
})
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def manager(hass, store, provider):
|
||||
"""Mock manager."""
|
||||
return AuthManager(hass, store, {
|
||||
(provider.type, provider.id): provider
|
||||
}, {})
|
||||
|
||||
|
||||
async def test_create_new_credential(manager, provider):
|
||||
"""Test that we create a new credential."""
|
||||
credentials = await provider.async_get_or_create_credentials({
|
||||
"username": "good-user",
|
||||
"password": "good-pass",
|
||||
})
|
||||
assert credentials.is_new is True
|
||||
|
||||
user = await manager.async_get_or_create_user(credentials)
|
||||
assert user.is_active
|
||||
|
||||
|
||||
async def test_match_existing_credentials(store, provider):
|
||||
"""See if we match existing users."""
|
||||
existing = auth_models.Credentials(
|
||||
id=uuid.uuid4(),
|
||||
auth_provider_type="command_line",
|
||||
auth_provider_id=None,
|
||||
data={
|
||||
"username": "good-user"
|
||||
},
|
||||
is_new=False,
|
||||
)
|
||||
provider.async_credentials = Mock(return_value=mock_coro([existing]))
|
||||
credentials = await provider.async_get_or_create_credentials({
|
||||
"username": "good-user",
|
||||
"password": "irrelevant",
|
||||
})
|
||||
assert credentials is existing
|
||||
|
||||
|
||||
async def test_invalid_username(provider):
|
||||
"""Test we raise if incorrect user specified."""
|
||||
with pytest.raises(command_line.InvalidAuthError):
|
||||
await provider.async_validate_login("bad-user", "good-pass")
|
||||
|
||||
|
||||
async def test_invalid_password(provider):
|
||||
"""Test we raise if incorrect password specified."""
|
||||
with pytest.raises(command_line.InvalidAuthError):
|
||||
await provider.async_validate_login("good-user", "bad-pass")
|
||||
|
||||
|
||||
async def test_good_auth(provider):
|
||||
"""Test nothing is raised with good credentials."""
|
||||
await provider.async_validate_login("good-user", "good-pass")
|
||||
|
||||
|
||||
async def test_good_auth_with_meta(manager, provider):
|
||||
"""Test metadata is added upon successful authentication."""
|
||||
provider.config[command_line.CONF_ARGS] = ["--with-meta"]
|
||||
provider.config[command_line.CONF_META] = True
|
||||
|
||||
await provider.async_validate_login("good-user", "good-pass")
|
||||
|
||||
credentials = await provider.async_get_or_create_credentials({
|
||||
"username": "good-user",
|
||||
"password": "good-pass",
|
||||
})
|
||||
assert credentials.is_new is True
|
||||
|
||||
user = await manager.async_get_or_create_user(credentials)
|
||||
assert user.name == "Bob"
|
||||
assert user.is_active
|
||||
|
||||
|
||||
async def test_utf_8_username_password(provider):
|
||||
"""Test that we create a new credential."""
|
||||
credentials = await provider.async_get_or_create_credentials({
|
||||
"username": "ßßß",
|
||||
"password": "äöü",
|
||||
})
|
||||
assert credentials.is_new is True
|
||||
|
||||
|
||||
async def test_login_flow_validates(provider):
|
||||
"""Test login flow."""
|
||||
flow = await provider.async_login_flow({})
|
||||
result = await flow.async_step_init()
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
|
||||
result = await flow.async_step_init({
|
||||
"username": "bad-user",
|
||||
"password": "bad-pass",
|
||||
})
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result['errors']["base"] == "invalid_auth"
|
||||
|
||||
result = await flow.async_step_init({
|
||||
"username": "good-user",
|
||||
"password": "good-pass",
|
||||
})
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["data"]["username"] == "good-user"
|
||||
|
||||
|
||||
async def test_strip_username(provider):
|
||||
"""Test authentication works with username with whitespace around."""
|
||||
flow = await provider.async_login_flow({})
|
||||
result = await flow.async_step_init({
|
||||
"username": "\t\ngood-user ",
|
||||
"password": "good-pass",
|
||||
})
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["data"]["username"] == "good-user"
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "$username" = "good-user" ] && [ "$password" = "good-pass" ]; then
|
||||
echo "Auth should succeed." >&2
|
||||
if [ "$1" = "--with-meta" ]; then
|
||||
echo "name=Bob"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Auth should fail." >&2
|
||||
exit 1
|
||||
Reference in New Issue
Block a user