Beispiel #1
0
async def test_not_raise_exception_when_service_not_exist(hass):
    """Test login flow will not raise exception when notify service error."""
    hass.auth = await auth_manager_from_config(
        hass, [{
            'type': 'insecure_example',
            'users': [{
                'username': '******',
                'password': '******'
            }],
        }], [{
            'type': 'notify',
        }])
    user = MockUser(
        id='mock-user',
        is_owner=False,
        is_active=False,
        name='Paulus',
    ).add_to_auth_manager(hass.auth)
    await hass.auth.async_link_user(
        user,
        auth_models.Credentials(
            id='mock-id',
            auth_provider_type='insecure_example',
            auth_provider_id=None,
            data={'username': '******'},
            is_new=False,
        ))

    await hass.auth.async_enable_user_mfa(user, 'notify', {
        'notify_service': 'invalid-notify',
    })

    provider = hass.auth.auth_providers[0]

    result = await hass.auth.login_flow.async_init(
        (provider.type, provider.id))
    assert result['type'] == data_entry_flow.RESULT_TYPE_FORM

    with patch('pyotp.HOTP.at', return_value=MOCK_CODE):
        result = await hass.auth.login_flow.async_configure(
            result['flow_id'], {
                'username': '******',
                'password': '******',
            })
        assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
        assert result['reason'] == 'unknown_error'

    # wait service call finished
    await hass.async_block_till_done()
Beispiel #2
0
async def test_async_remove_user(opp):
    """Test removing a user."""
    events = []

    @callback
    def user_removed(event):
        events.append(event)

    opp.bus.async_listen("user_removed", user_removed)

    manager = await auth.auth_manager_from_config(
        opp,
        [{
            "type":
            "insecure_example",
            "users": [{
                "username": "******",
                "password": "******",
                "name": "Test Name",
            }],
        }],
        [],
    )
    opp.auth = manager
    ensure_auth_manager_loaded(manager)

    # Add fake user with credentials for example auth provider.
    user = MockUser(id="mock-user",
                    is_owner=False,
                    is_active=False,
                    name="Paulus").add_to_auth_manager(manager)
    user.credentials.append(
        auth_models.Credentials(
            id="mock-id",
            auth_provider_type="insecure_example",
            auth_provider_id=None,
            data={"username": "******"},
            is_new=False,
        ))
    assert len(user.credentials) == 1

    await opp.auth.async_remove_user(user)

    assert len(await manager.async_get_users()) == 0
    assert len(user.credentials) == 0

    await opp.async_block_till_done()
    assert len(events) == 1
    assert events[0].data["user_id"] == user.id
Beispiel #3
0
async def test_one_long_lived_access_token_per_refresh_token(mock_hass):
    """Test one refresh_token can only have one long-lived access token."""
    manager = await auth.auth_manager_from_config(mock_hass, [], [])
    user = MockUser().add_to_auth_manager(manager)
    refresh_token = await manager.async_create_refresh_token(
        user,
        client_name="GPS Logger",
        token_type=auth_models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN,
        access_token_expiration=timedelta(days=3000),
    )
    assert refresh_token.token_type == auth_models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN
    access_token = manager.async_create_access_token(refresh_token)
    jwt_key = refresh_token.jwt_key

    rt = await manager.async_validate_access_token(access_token)
    assert rt.id == refresh_token.id

    with pytest.raises(ValueError):
        await manager.async_create_refresh_token(
            user,
            client_name="GPS Logger",
            token_type=auth_models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN,
            access_token_expiration=timedelta(days=3000),
        )

    await manager.async_remove_refresh_token(refresh_token)
    assert refresh_token.id not in user.refresh_tokens
    rt = await manager.async_validate_access_token(access_token)
    assert rt is None, "Previous issued access token has been invoked"

    refresh_token_2 = await manager.async_create_refresh_token(
        user,
        client_name="GPS Logger",
        token_type=auth_models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN,
        access_token_expiration=timedelta(days=3000),
    )
    assert refresh_token_2.id != refresh_token.id
    assert refresh_token_2.token_type == auth_models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN
    access_token_2 = manager.async_create_access_token(refresh_token_2)
    jwt_key_2 = refresh_token_2.jwt_key

    assert access_token != access_token_2
    assert jwt_key != jwt_key_2

    rt = await manager.async_validate_access_token(access_token_2)
    jwt_payload = jwt.decode(access_token_2, rt.jwt_key, algorithm=["HS256"])
    assert jwt_payload["iss"] == refresh_token_2.id
    assert (jwt_payload["exp"] -
            jwt_payload["iat"] == timedelta(days=3000).total_seconds())
Beispiel #4
0
async def test_not_raise_exception_when_service_not_exist(opp):
    """Test login flow will not raise exception when notify service error."""
    opp.auth = await auth_manager_from_config(
        opp,
        [{
            "type": "insecure_example",
            "users": [{
                "username": "******",
                "password": "******"
            }],
        }],
        [{
            "type": "notify"
        }],
    )
    user = MockUser(id="mock-user",
                    is_owner=False,
                    is_active=False,
                    name="Paulus").add_to_auth_manager(opp.auth)
    await opp.auth.async_link_user(
        user,
        auth_models.Credentials(
            id="mock-id",
            auth_provider_type="insecure_example",
            auth_provider_id=None,
            data={"username": "******"},
            is_new=False,
        ),
    )

    await opp.auth.async_enable_user_mfa(user, "notify",
                                         {"notify_service": "invalid-notify"})

    provider = opp.auth.auth_providers[0]

    result = await opp.auth.login_flow.async_init((provider.type, provider.id))
    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM

    with patch("pyotp.HOTP.at", return_value=MOCK_CODE):
        result = await opp.auth.login_flow.async_configure(
            result["flow_id"], {
                "username": "******",
                "password": "******"
            })
        assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
        assert result["reason"] == "unknown_error"

    # wait service call finished
    await opp.async_block_till_done()
Beispiel #5
0
async def test_remove_refresh_token(mock_hass):
    """Test that we can remove a refresh token."""
    manager = await auth.auth_manager_from_config(mock_hass, [], [])
    user = MockUser().add_to_auth_manager(manager)
    refresh_token = await manager.async_create_refresh_token(user, CLIENT_ID)
    access_token = manager.async_create_access_token(refresh_token)

    await manager.async_remove_refresh_token(refresh_token)

    assert (
        await manager.async_get_refresh_token(refresh_token.id) is None
    )
    assert (
        await manager.async_validate_access_token(access_token) is None
    )
Beispiel #6
0
async def test_refresh_token_type(hass):
    """Test create a refresh token with token type."""
    manager = await auth.auth_manager_from_config(hass, [], [])
    user = MockUser().add_to_auth_manager(manager)

    with pytest.raises(ValueError):
        await manager.async_create_refresh_token(
            user, CLIENT_ID, token_type=auth_models.TOKEN_TYPE_SYSTEM)

    token = await manager.async_create_refresh_token(
        user, CLIENT_ID,
        token_type=auth_models.TOKEN_TYPE_NORMAL)
    assert token is not None
    assert token.client_id == CLIENT_ID
    assert token.token_type == auth_models.TOKEN_TYPE_NORMAL
Beispiel #7
0
async def test_register_revoke_token_callback(mock_hass):
    """Test that a registered revoke token callback is called."""
    manager = await auth.auth_manager_from_config(mock_hass, [], [])
    user = MockUser().add_to_auth_manager(manager)
    refresh_token = await manager.async_create_refresh_token(user, CLIENT_ID)

    called = False

    def cb():
        nonlocal called
        called = True

    manager.async_register_revoke_token_callback(refresh_token.id, cb)
    await manager.async_remove_refresh_token(refresh_token)
    assert called
Beispiel #8
0
async def test_having_owner_finishes_user_step(hass, hass_storage):
    """If owner user already exists, mark user step as complete."""
    MockUser(is_owner=True).add_to_hass(hass)

    with patch("homeassistant.components.onboarding.views.async_setup"
               ) as mock_setup, patch.object(onboarding, "STEPS",
                                             [onboarding.STEP_USER]):
        assert await async_setup_component(hass, "onboarding", {})

    assert len(mock_setup.mock_calls) == 0
    assert onboarding.DOMAIN not in hass.data
    assert onboarding.async_is_onboarded(hass)

    done = hass_storage[onboarding.STORAGE_KEY]["data"]["done"]
    assert onboarding.STEP_USER in done
Beispiel #9
0
async def test_event_user_updated_fires(hass):
    """Test the user updated event fires."""
    manager = await auth.auth_manager_from_config(hass, [], [])
    user = MockUser().add_to_auth_manager(manager)
    await manager.async_create_refresh_token(user, CLIENT_ID)

    assert len(list(user.refresh_tokens.values())) == 1

    events = async_capture_events(hass, EVENT_USER_UPDATED)

    await manager.async_update_user(user, name="new name")
    assert user.name == "new name"

    await hass.async_block_till_done()
    assert len(events) == 1
Beispiel #10
0
async def test_create_access_token(mock_hass):
    """Test normal refresh_token's jwt_key keep same after used."""
    manager = await auth.auth_manager_from_config(mock_hass, [], [])
    user = MockUser().add_to_auth_manager(manager)
    refresh_token = await manager.async_create_refresh_token(user, CLIENT_ID)
    assert refresh_token.token_type == auth_models.TOKEN_TYPE_NORMAL
    jwt_key = refresh_token.jwt_key
    access_token = manager.async_create_access_token(refresh_token)
    assert access_token is not None
    assert refresh_token.jwt_key == jwt_key
    jwt_payload = jwt.decode(access_token, jwt_key, algorithm=["HS256"])
    assert jwt_payload["iss"] == refresh_token.id
    assert (
        jwt_payload["exp"] - jwt_payload["iat"] == timedelta(minutes=30).total_seconds()
    )
Beispiel #11
0
async def test_refresh_token_requires_client_for_user(hass):
    """Test create refresh token for a user with client_id."""
    manager = await auth.auth_manager_from_config(hass, [], [])
    user = MockUser().add_to_auth_manager(manager)
    assert user.system_generated is False

    with pytest.raises(ValueError):
        await manager.async_create_refresh_token(user)

    token = await manager.async_create_refresh_token(user, CLIENT_ID)
    assert token is not None
    assert token.client_id == CLIENT_ID
    assert token.token_type == auth_models.TOKEN_TYPE_NORMAL
    # default access token expiration
    assert token.access_token_expiration == auth_const.ACCESS_TOKEN_EXPIRATION
Beispiel #12
0
async def test_create_long_lived_access_token(mock_hass):
    """Test refresh_token's jwt_key changed for long-lived access token."""
    manager = await auth.auth_manager_from_config(mock_hass, [], [])
    user = MockUser().add_to_auth_manager(manager)
    refresh_token = await manager.async_create_refresh_token(
        user, client_name='GPS Logger',
        token_type=auth_models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN,
        access_token_expiration=timedelta(days=300))
    assert refresh_token.token_type == \
        auth_models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN
    access_token = manager.async_create_access_token(refresh_token)
    jwt_payload = jwt.decode(
        access_token, refresh_token.jwt_key, algorithm=['HS256'])
    assert jwt_payload['iss'] == refresh_token.id
    assert jwt_payload['exp'] - jwt_payload['iat'] == \
        timedelta(days=300).total_seconds()
Beispiel #13
0
async def test_delete(hass, hass_ws_client, hass_access_token):
    """Test delete command works."""
    client = await hass_ws_client(hass, hass_access_token)
    test_user = MockUser(id='efg', ).add_to_hass(hass)

    assert len(await hass.auth.async_get_users()) == 2

    await client.send_json({
        'id': 5,
        'type': auth_config.WS_TYPE_DELETE,
        'user_id': test_user.id,
    })

    result = await client.receive_json()
    assert result['success'], result
    assert len(await hass.auth.async_get_users()) == 1
Beispiel #14
0
async def test_rename_does_not_change_refresh_token(mock_hass):
    """Test that we can rename without changing refresh token."""
    manager = await auth.auth_manager_from_config(mock_hass, [], [])
    user = MockUser().add_to_auth_manager(manager)
    await manager.async_create_refresh_token(user, CLIENT_ID)

    assert len(list(user.refresh_tokens.values())) == 1
    token_before = list(user.refresh_tokens.values())[0]

    await manager.async_update_user(user, name="new name")
    assert user.name == "new name"

    assert len(list(user.refresh_tokens.values())) == 1
    token_after = list(user.refresh_tokens.values())[0]

    assert token_before == token_after
Beispiel #15
0
async def test_delete(hass, hass_ws_client, hass_access_token):
    """Test delete command works."""
    client = await hass_ws_client(hass, hass_access_token)
    test_user = MockUser(id="efg").add_to_hass(hass)

    assert len(await hass.auth.async_get_users()) == 2

    await client.send_json({
        "id": 5,
        "type": auth_config.WS_TYPE_DELETE,
        "user_id": test_user.id
    })

    result = await client.receive_json()
    assert result["success"], result
    assert len(await hass.auth.async_get_users()) == 1
Beispiel #16
0
async def test_async_remove_user(hass):
    """Test removing a user."""
    events = []

    @callback
    def user_removed(event):
        events.append(event)

    hass.bus.async_listen('user_removed', user_removed)

    manager = await auth.auth_manager_from_config(hass, [{
        'type':
        'insecure_example',
        'users': [{
            'username': '******',
            'password': '******',
            'name': 'Test Name'
        }]
    }], [])
    hass.auth = manager
    ensure_auth_manager_loaded(manager)

    # Add fake user with credentials for example auth provider.
    user = MockUser(
        id='mock-user',
        is_owner=False,
        is_active=False,
        name='Paulus',
    ).add_to_auth_manager(manager)
    user.credentials.append(
        auth_models.Credentials(
            id='mock-id',
            auth_provider_type='insecure_example',
            auth_provider_id=None,
            data={'username': '******'},
            is_new=False,
        ))
    assert len(user.credentials) == 1

    await hass.auth.async_remove_user(user)

    assert len(await manager.async_get_users()) == 0
    assert len(user.credentials) == 0

    await hass.async_block_till_done()
    assert len(events) == 1
    assert events[0].data['user_id'] == user.id
Beispiel #17
0
async def test_refresh_token_type_long_lived_access_token(hass):
    """Test create a refresh token has long-lived access token type."""
    manager = await auth.auth_manager_from_config(hass, [], [])
    user = MockUser().add_to_auth_manager(manager)

    with pytest.raises(ValueError):
        await manager.async_create_refresh_token(
            user, token_type=auth_models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN)

    token = await manager.async_create_refresh_token(
        user, client_name='GPS LOGGER', client_icon='mdi:home',
        token_type=auth_models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN)
    assert token is not None
    assert token.client_id is None
    assert token.client_name == 'GPS LOGGER'
    assert token.client_icon == 'mdi:home'
    assert token.token_type == auth_models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN
async def test_create_auth_system_generated_user(hass, hass_ws_client):
    """Test we can't add auth to system generated users."""
    system_user = MockUser(system_generated=True).add_to_hass(hass)
    client = await hass_ws_client(hass)

    await client.send_json({
        "id": 5,
        "type": "config/auth_provider/homeassistant/create",
        "user_id": system_user.id,
        "username": "******",
        "password": "******",
    })

    result = await client.receive_json()

    assert not result["success"], result
    assert result["error"]["code"] == "system_generated"
async def test_cannot_retrieve_expired_access_token(hass):
    """Test that we cannot retrieve expired access tokens."""
    manager = await auth.auth_manager_from_config(hass, [])
    user = MockUser().add_to_auth_manager(manager)
    refresh_token = await manager.async_create_refresh_token(user, CLIENT_ID)
    assert refresh_token.user.id is user.id
    assert refresh_token.client_id == CLIENT_ID

    access_token = manager.async_create_access_token(refresh_token)
    assert manager.async_get_access_token(access_token.token) is access_token

    with patch('homeassistant.auth.dt_util.utcnow',
               return_value=dt_util.utcnow() + auth.ACCESS_TOKEN_EXPIRATION):
        assert manager.async_get_access_token(access_token.token) is None

    # Even with unpatched time, it should have been removed from manager
    assert manager.async_get_access_token(access_token.token) is None
Beispiel #20
0
async def test_login_as_existing_user(mock_hass):
    """Test login as existing user."""
    manager = await auth.auth_manager_from_config(mock_hass, [{
        'type':
        'insecure_example',
        'users': [{
            'username': '******',
            'password': '******',
            'name': 'Test Name'
        }]
    }])
    ensure_auth_manager_loaded(manager)

    # Add fake user with credentials for example auth provider.
    user = MockUser(
        id='mock-user',
        is_owner=False,
        is_active=False,
        name='Paulus',
    ).add_to_auth_manager(manager)
    user.credentials.append(
        auth.Credentials(
            id='mock-id',
            auth_provider_type='insecure_example',
            auth_provider_id=None,
            data={'username': '******'},
            is_new=False,
        ))

    step = await manager.login_flow.async_init(('insecure_example', None))
    assert step['type'] == data_entry_flow.RESULT_TYPE_FORM

    step = await manager.login_flow.async_configure(step['flow_id'], {
        'username': '******',
        'password': '******',
    })
    assert step['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
    credentials = step['result']

    user = await manager.async_get_or_create_user(credentials)
    assert user is not None
    assert user.id == 'mock-user'
    assert user.is_owner is False
    assert user.is_active is False
    assert user.name == 'Paulus'
Beispiel #21
0
async def test_cannot_retrieve_expired_access_token(hass):
    """Test that we cannot retrieve expired access tokens."""
    manager = await auth.auth_manager_from_config(hass, [], [])
    user = MockUser().add_to_auth_manager(manager)
    refresh_token = await manager.async_create_refresh_token(user, CLIENT_ID)
    assert refresh_token.user.id is user.id
    assert refresh_token.client_id == CLIENT_ID

    access_token = manager.async_create_access_token(refresh_token)
    assert (await manager.async_validate_access_token(access_token) is
            refresh_token)

    with patch('homeassistant.util.dt.utcnow',
               return_value=dt_util.utcnow() -
               auth_const.ACCESS_TOKEN_EXPIRATION - timedelta(seconds=11)):
        access_token = manager.async_create_access_token(refresh_token)

    assert (await manager.async_validate_access_token(access_token) is None)
async def test_create_auth_system_generated_user(hass, hass_access_token,
                                                 hass_ws_client):
    """Test we can't add auth to system generated users."""
    system_user = MockUser(system_generated=True).add_to_hass(hass)
    client = await hass_ws_client(hass, hass_access_token)

    await client.send_json({
        'id': 5,
        'type': auth_ha.WS_TYPE_CREATE,
        'user_id': system_user.id,
        'username': '******',
        'password': '******',
    })

    result = await client.receive_json()

    assert not result['success'], result
    assert result['error']['code'] == 'system_generated'
Beispiel #23
0
async def test_deactivate_owner(opp, opp_ws_client):
    """Test that owner cannot be deactivated."""
    user = MockUser(id="abc", name="Test Owner", is_owner=True).add_to_opp(opp)

    assert user.is_active is True
    assert user.is_owner is True

    client = await opp_ws_client(opp)
    await client.send_json({
        "id": 5,
        "type": "config/auth/update",
        "user_id": user.id,
        "is_active": False
    })

    result = await client.receive_json()
    assert not result["success"], result
    assert result["error"]["code"] == "cannot_deactivate_owner"
async def test_create_auth_system_generated_user(opp, opp_access_token, opp_ws_client):
    """Test we can't add auth to system generated users."""
    system_user = MockUser(system_generated=True).add_to_opp(opp)
    client = await opp_ws_client(opp, opp_access_token)

    await client.send_json(
        {
            "id": 5,
            "type": auth_ha.WS_TYPE_CREATE,
            "user_id": system_user.id,
            "username": "******",
            "password": "******",
        }
    )

    result = await client.receive_json()

    assert not result["success"], result
    assert result["error"]["code"] == "system_generated"
Beispiel #25
0
def test_auth_code_store_expiration():
    """Test that the auth code store will not return expired tokens."""
    store, retrieve = auth._create_auth_code_store()
    client_id = 'bla'
    user = MockUser(id='mock_user')
    now = utcnow()

    with patch('homeassistant.util.dt.utcnow', return_value=now):
        code = store(client_id, user)

    with patch('homeassistant.util.dt.utcnow',
               return_value=now + timedelta(minutes=10)):
        assert retrieve(client_id, RESULT_TYPE_USER, code) is None

    with patch('homeassistant.util.dt.utcnow', return_value=now):
        code = store(client_id, user)

    with patch('homeassistant.util.dt.utcnow',
               return_value=now + timedelta(minutes=9, seconds=59)):
        assert retrieve(client_id, RESULT_TYPE_USER, code) == user
async def test_refresh_token_provider_validation(mock_hass):
    """Test that creating access token from refresh token checks with provider."""
    manager = await auth.auth_manager_from_config(
        mock_hass,
        [
            {
                "type": "insecure_example",
                "users": [{"username": "******", "password": "******"}],
            }
        ],
        [],
    )

    credential = auth_models.Credentials(
        id="mock-credential-id",
        auth_provider_type="insecure_example",
        auth_provider_id=None,
        data={"username": "******"},
        is_new=False,
    )

    user = MockUser().add_to_auth_manager(manager)
    user.credentials.append(credential)
    refresh_token = await manager.async_create_refresh_token(
        user, CLIENT_ID, credential=credential
    )
    ip = "127.0.0.1"

    assert manager.async_create_access_token(refresh_token, ip) is not None

    with patch(
        "homeassistant.auth.providers.insecure_example.ExampleAuthProvider.async_validate_refresh_token",
        side_effect=InvalidAuthError("Invalid access"),
    ) as call:
        with pytest.raises(InvalidAuthError):
            manager.async_create_access_token(refresh_token, ip)

    call.assert_called_with(refresh_token, ip)
Beispiel #27
0
async def test_exception_handling():
    """Test handling of exceptions."""
    send_messages = []
    user = MockUser()
    refresh_token = Mock()
    conn = websocket_api.ActiveConnection(logging.getLogger(__name__), None,
                                          send_messages.append, user,
                                          refresh_token)

    for (exc, code, err) in (
        (exceptions.Unauthorized(), websocket_api.ERR_UNAUTHORIZED,
         "Unauthorized"),
        (
            vol.Invalid("Invalid something"),
            websocket_api.ERR_INVALID_FORMAT,
            "Invalid something. Got {'id': 5}",
        ),
        (asyncio.TimeoutError(), websocket_api.ERR_TIMEOUT, "Timeout"),
        (
            exceptions.HomeAssistantError("Failed to do X"),
            websocket_api.ERR_UNKNOWN_ERROR,
            "Failed to do X",
        ),
        (ValueError("Really bad"), websocket_api.ERR_UNKNOWN_ERROR,
         "Unknown error"),
        (
            exceptions.HomeAssistantError(),
            websocket_api.ERR_UNKNOWN_ERROR,
            "Unknown error",
        ),
    ):
        send_messages.clear()
        conn.async_handle_exception({"id": 5}, exc)
        assert len(send_messages) == 1
        assert send_messages[0]["error"]["code"] == code
        assert send_messages[0]["error"]["message"] == err
Beispiel #28
0
async def test_login_flow_validates_mfa(hass):
    """Test login flow with mfa enabled."""
    hass.auth = await auth_manager_from_config(
        hass, [{
            'type': 'insecure_example',
            'users': [{
                'username': '******',
                'password': '******'
            }],
        }], [{
            'type': 'notify',
        }])
    user = MockUser(
        id='mock-user',
        is_owner=False,
        is_active=False,
        name='Paulus',
    ).add_to_auth_manager(hass.auth)
    await hass.auth.async_link_user(
        user,
        auth_models.Credentials(
            id='mock-id',
            auth_provider_type='insecure_example',
            auth_provider_id=None,
            data={'username': '******'},
            is_new=False,
        ))

    notify_calls = async_mock_service(hass, 'notify', 'test-notify',
                                      NOTIFY_SERVICE_SCHEMA)

    await hass.auth.async_enable_user_mfa(user, 'notify', {
        'notify_service': 'test-notify',
    })

    provider = hass.auth.auth_providers[0]

    result = await hass.auth.login_flow.async_init(
        (provider.type, provider.id))
    assert result['type'] == data_entry_flow.RESULT_TYPE_FORM

    result = await hass.auth.login_flow.async_configure(
        result['flow_id'], {
            'username': '******',
            'password': '******',
        })
    assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
    assert result['errors']['base'] == 'invalid_auth'

    result = await hass.auth.login_flow.async_configure(
        result['flow_id'], {
            'username': '******',
            'password': '******',
        })
    assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
    assert result['errors']['base'] == 'invalid_auth'

    with patch('pyotp.HOTP.at', return_value=MOCK_CODE):
        result = await hass.auth.login_flow.async_configure(
            result['flow_id'], {
                'username': '******',
                'password': '******',
            })
        assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
        assert result['step_id'] == 'mfa'
        assert result['data_schema'].schema.get('code') == str

    # wait service call finished
    await hass.async_block_till_done()

    assert len(notify_calls) == 1
    notify_call = notify_calls[0]
    assert notify_call.domain == 'notify'
    assert notify_call.service == 'test-notify'
    message = notify_call.data['message']
    message.hass = hass
    assert MOCK_CODE in message.async_render()

    with patch('pyotp.HOTP.verify', return_value=False):
        result = await hass.auth.login_flow.async_configure(
            result['flow_id'], {'code': 'invalid-code'})
        assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
        assert result['step_id'] == 'mfa'
        assert result['errors']['base'] == 'invalid_code'

    # wait service call finished
    await hass.async_block_till_done()

    # would not send new code, allow user retry
    assert len(notify_calls) == 1

    # retry twice
    with patch('pyotp.HOTP.verify', return_value=False), \
            patch('pyotp.HOTP.at', return_value=MOCK_CODE_2):
        result = await hass.auth.login_flow.async_configure(
            result['flow_id'], {'code': 'invalid-code'})
        assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
        assert result['step_id'] == 'mfa'
        assert result['errors']['base'] == 'invalid_code'

        # after the 3rd failure, flow abort
        result = await hass.auth.login_flow.async_configure(
            result['flow_id'], {'code': 'invalid-code'})
        assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
        assert result['reason'] == 'too_many_retry'

    # wait service call finished
    await hass.async_block_till_done()

    # restart login
    result = await hass.auth.login_flow.async_init(
        (provider.type, provider.id))
    assert result['type'] == data_entry_flow.RESULT_TYPE_FORM

    with patch('pyotp.HOTP.at', return_value=MOCK_CODE):
        result = await hass.auth.login_flow.async_configure(
            result['flow_id'], {
                'username': '******',
                'password': '******',
            })
        assert result['type'] == data_entry_flow.RESULT_TYPE_FORM
        assert result['step_id'] == 'mfa'
        assert result['data_schema'].schema.get('code') == str

    # wait service call finished
    await hass.async_block_till_done()

    assert len(notify_calls) == 2
    notify_call = notify_calls[1]
    assert notify_call.domain == 'notify'
    assert notify_call.service == 'test-notify'
    message = notify_call.data['message']
    message.hass = hass
    assert MOCK_CODE in message.async_render()

    with patch('pyotp.HOTP.verify', return_value=True):
        result = await hass.auth.login_flow.async_configure(
            result['flow_id'], {'code': MOCK_CODE})
        assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
        assert result['data'].id == 'mock-user'
Beispiel #29
0
async def test_login_flow_validates_mfa(opp):
    """Test login flow with mfa enabled."""
    opp.auth = await auth_manager_from_config(
        opp,
        [{
            "type": "insecure_example",
            "users": [{
                "username": "******",
                "password": "******"
            }],
        }],
        [{
            "type": "notify"
        }],
    )
    user = MockUser(id="mock-user",
                    is_owner=False,
                    is_active=False,
                    name="Paulus").add_to_auth_manager(opp.auth)
    await opp.auth.async_link_user(
        user,
        auth_models.Credentials(
            id="mock-id",
            auth_provider_type="insecure_example",
            auth_provider_id=None,
            data={"username": "******"},
            is_new=False,
        ),
    )

    notify_calls = async_mock_service(opp, "notify", "test-notify",
                                      NOTIFY_SERVICE_SCHEMA)

    await opp.auth.async_enable_user_mfa(user, "notify",
                                         {"notify_service": "test-notify"})

    provider = opp.auth.auth_providers[0]

    result = await opp.auth.login_flow.async_init((provider.type, provider.id))
    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM

    result = await opp.auth.login_flow.async_configure(result["flow_id"], {
        "username": "******",
        "password": "******"
    })
    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["errors"]["base"] == "invalid_auth"

    result = await opp.auth.login_flow.async_configure(
        result["flow_id"], {
            "username": "******",
            "password": "******"
        })
    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
    assert result["errors"]["base"] == "invalid_auth"

    with patch("pyotp.HOTP.at", return_value=MOCK_CODE):
        result = await opp.auth.login_flow.async_configure(
            result["flow_id"], {
                "username": "******",
                "password": "******"
            })
        assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
        assert result["step_id"] == "mfa"
        assert result["data_schema"].schema.get("code") == str

    # wait service call finished
    await opp.async_block_till_done()

    assert len(notify_calls) == 1
    notify_call = notify_calls[0]
    assert notify_call.domain == "notify"
    assert notify_call.service == "test-notify"
    message = notify_call.data["message"]
    message.opp = opp
    assert MOCK_CODE in message.async_render()

    with patch("pyotp.HOTP.verify", return_value=False):
        result = await opp.auth.login_flow.async_configure(
            result["flow_id"], {"code": "invalid-code"})
        assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
        assert result["step_id"] == "mfa"
        assert result["errors"]["base"] == "invalid_code"

    # wait service call finished
    await opp.async_block_till_done()

    # would not send new code, allow user retry
    assert len(notify_calls) == 1

    # retry twice
    with patch("pyotp.HOTP.verify",
               return_value=False), patch("pyotp.HOTP.at",
                                          return_value=MOCK_CODE_2):
        result = await opp.auth.login_flow.async_configure(
            result["flow_id"], {"code": "invalid-code"})
        assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
        assert result["step_id"] == "mfa"
        assert result["errors"]["base"] == "invalid_code"

        # after the 3rd failure, flow abort
        result = await opp.auth.login_flow.async_configure(
            result["flow_id"], {"code": "invalid-code"})
        assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
        assert result["reason"] == "too_many_retry"

    # wait service call finished
    await opp.async_block_till_done()

    # restart login
    result = await opp.auth.login_flow.async_init((provider.type, provider.id))
    assert result["type"] == data_entry_flow.RESULT_TYPE_FORM

    with patch("pyotp.HOTP.at", return_value=MOCK_CODE):
        result = await opp.auth.login_flow.async_configure(
            result["flow_id"], {
                "username": "******",
                "password": "******"
            })
        assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
        assert result["step_id"] == "mfa"
        assert result["data_schema"].schema.get("code") == str

    # wait service call finished
    await opp.async_block_till_done()

    assert len(notify_calls) == 2
    notify_call = notify_calls[1]
    assert notify_call.domain == "notify"
    assert notify_call.service == "test-notify"
    message = notify_call.data["message"]
    message.opp = opp
    assert MOCK_CODE in message.async_render()

    with patch("pyotp.HOTP.verify", return_value=True):
        result = await opp.auth.login_flow.async_configure(
            result["flow_id"], {"code": MOCK_CODE})
        assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
        assert result["data"].id == "mock-user"
Beispiel #30
0
def hass_read_only_user(hass, local_auth):
    """Return a Home Assistant read only user."""
    read_only_group = hass.loop.run_until_complete(
        hass.auth.async_get_group(GROUP_ID_READ_ONLY))
    return MockUser(groups=[read_only_group]).add_to_hass(hass)