Beispiel #1
0
async def test_async_user_not_allowed_do_auth(hass, app):
    """Test for not allowing auth."""
    user = await hass.auth.async_create_user("Hello")
    user.is_active = False

    # User not active
    assert async_user_not_allowed_do_auth(hass, user) == "User is not active"

    user.is_active = True
    user.local_only = True

    # No current request
    assert (async_user_not_allowed_do_auth(
        hass, user) == "No request available to validate local access")

    trusted_request = Mock(remote="192.168.1.123")
    untrusted_request = Mock(remote=UNTRUSTED_ADDRESSES[0])

    # Is Remote IP and local only (cloud not loaded)
    assert async_user_not_allowed_do_auth(hass, user, trusted_request) is None
    assert (async_user_not_allowed_do_auth(
        hass, user, untrusted_request) == "User cannot authenticate remotely")

    # Mimic cloud loaded and validate local IP again
    hass.config.components.add("cloud")
    assert async_user_not_allowed_do_auth(hass, user, trusted_request) is None
    assert (async_user_not_allowed_do_auth(
        hass, user, untrusted_request) == "User cannot authenticate remotely")

    # Is Cloud request and local only, even a local IP will fail
    with patch("hass_nabucasa.remote.is_cloud_request",
               Mock(get=Mock(return_value=True))):
        assert (async_user_not_allowed_do_auth(
            hass, user, trusted_request) == "User is local only")
Beispiel #2
0
class LoginFlowBaseView(HomeAssistantView):
    """Base class for the login views."""

    requires_auth = False

    def __init__(
        self,
        flow_mgr: AuthManagerFlowManager,
        store_result: StoreResultType,
    ) -> None:
        """Initialize the flow manager index view."""
        self._flow_mgr = flow_mgr
        self._store_result = store_result

    async def _async_flow_result_to_response(
        self,
        request: web.Request,
        client_id: str,
        result: data_entry_flow.FlowResult,
    ) -> web.Response:
        """Convert the flow result to a response."""
        if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
            # @log_invalid_auth does not work here since it returns HTTP 200.
            # We need to manually log failed login attempts.
            if (result["type"] == data_entry_flow.FlowResultType.FORM
                    and (errors := result.get("errors"))
                    and errors.get("base") in (
                        "invalid_auth",
                        "invalid_code",
                    )):
                await process_wrong_login(request)
            return self.json(_prepare_result_json(result))

        hass: HomeAssistant = request.app["hass"]

        if not await indieauth.verify_redirect_uri(
                hass, client_id, result["context"]["redirect_uri"]):
            return self.json_message("Invalid redirect URI",
                                     HTTPStatus.FORBIDDEN)

        result.pop("data")
        result.pop("context")

        result_obj: Credentials = result.pop("result")

        # Result can be None if credential was never linked to a user before.
        user = await hass.auth.async_get_user_by_credentials(result_obj)

        if user is not None and (user_access_error :=
                                 async_user_not_allowed_do_auth(hass, user)):
            return self.json_message(f"Login blocked: {user_access_error}",
                                     HTTPStatus.FORBIDDEN)
Beispiel #3
0
            )

        credential = self._retrieve_auth(client_id, code)

        if credential is None or not isinstance(credential, Credentials):
            return self.json(
                {
                    "error": "invalid_request",
                    "error_description": "Invalid code"
                },
                status_code=HTTPStatus.BAD_REQUEST,
            )

        user = await hass.auth.async_get_or_create_user(credential)

        if user_access_error := async_user_not_allowed_do_auth(hass, user):
            return self.json(
                {
                    "error": "access_denied",
                    "error_description": user_access_error,
                },
                status_code=HTTPStatus.FORBIDDEN,
            )

        refresh_token = await hass.auth.async_create_refresh_token(
            user, client_id, credential=credential)
        try:
            access_token = hass.auth.async_create_access_token(
                refresh_token, remote_addr)
        except InvalidAuthError as exc:
            return self.json(
Beispiel #4
0
class TokenView(HomeAssistantView):
    """View to issue tokens."""

    url = "/auth/token"
    name = "api:auth:token"
    requires_auth = False
    cors_allowed = True

    def __init__(self, retrieve_auth: RetrieveResultType) -> None:
        """Initialize the token view."""
        self._retrieve_auth = retrieve_auth

    @log_invalid_auth
    async def post(self, request: web.Request) -> web.Response:
        """Grant a token."""
        hass: HomeAssistant = request.app["hass"]
        data = cast(MultiDictProxy[str], await request.post())

        grant_type = data.get("grant_type")

        # IndieAuth 6.3.5
        # The revocation endpoint is the same as the token endpoint.
        # The revocation request includes an additional parameter,
        # action=revoke.
        if data.get("action") == "revoke":
            # action=revoke is deprecated. Use /auth/revoke instead.
            # Keep here for backwards compat
            return await RevokeTokenView.post(self, request
                                              )  # type: ignore[arg-type]

        if grant_type == "authorization_code":
            return await self._async_handle_auth_code(hass, data,
                                                      request.remote)

        if grant_type == "refresh_token":
            return await self._async_handle_refresh_token(
                hass, data, request.remote)

        return self.json({"error": "unsupported_grant_type"},
                         status_code=HTTPStatus.BAD_REQUEST)

    async def _async_handle_auth_code(
        self,
        hass: HomeAssistant,
        data: MultiDictProxy[str],
        remote_addr: str | None,
    ) -> web.Response:
        """Handle authorization code request."""
        client_id = data.get("client_id")
        if client_id is None or not indieauth.verify_client_id(client_id):
            return self.json(
                {
                    "error": "invalid_request",
                    "error_description": "Invalid client id"
                },
                status_code=HTTPStatus.BAD_REQUEST,
            )

        if (code := data.get("code")) is None:
            return self.json(
                {
                    "error": "invalid_request",
                    "error_description": "Invalid code"
                },
                status_code=HTTPStatus.BAD_REQUEST,
            )

        credential = self._retrieve_auth(client_id, code)

        if credential is None or not isinstance(credential, Credentials):
            return self.json(
                {
                    "error": "invalid_request",
                    "error_description": "Invalid code"
                },
                status_code=HTTPStatus.BAD_REQUEST,
            )

        user = await hass.auth.async_get_or_create_user(credential)

        if user_access_error := async_user_not_allowed_do_auth(hass, user):
            return self.json(
                {
                    "error": "access_denied",
                    "error_description": user_access_error,
                },
                status_code=HTTPStatus.FORBIDDEN,
            )