Example #1
0
async def slave_device_forward(
    request: core_api.APIRequest,
    name: str,
    path: str,
    params: Optional[GenericJSONDict] = None,
    internal_use: bool = False
) -> Any:

    slave = slaves_devices.get(name)

    if not slave:
        raise core_api.APIError(404, 'no-such-device')

    if not internal_use:
        if not path.startswith('/'):
            path = '/' + path

        if path.startswith('/listen'):
            raise core_api.APIError(404, 'no-such-function')

    intercepted, response = await slave.intercept_request(request.method, path, params, request)
    if intercepted:
        return response

    override_disabled = request.query.get('override_disabled')
    if not slave.is_enabled() and (override_disabled != 'true'):
        raise core_api.APIError(404, 'device-disabled')

    override_offline = request.query.get('override_offline')
    if (not slave.is_online() or not slave.is_ready()) and (override_offline != 'true'):
        raise core_api.APIError(503, 'device-offline')

    timeout = request.query.get('timeout')
    if timeout is not None:
        try:
            timeout = int(timeout)

        except ValueError:
            raise core_api.APIError(400, 'invalid-field', field='timeout') from None

    else:
        # Use default slave timeout unless API call requires longer timeout
        timeout = settings.slaves.timeout
        for m, path_re in _LONG_TIMEOUT_API_CALLS:
            if request.method == m and path_re.fullmatch(path):
                timeout = settings.slaves.long_timeout
                break

    try:
        response = await slave.api_call(request.method, path, params, timeout=timeout, retry_counter=None)

    except Exception as e:
        raise slaves_exceptions.adapt_api_error(e) from e

    return response
Example #2
0
async def post_slave_device_events(request: core_api.APIRequest, name: str, params: GenericJSONDict) -> None:
    slave = slaves_devices.get(name)
    if not slave:
        raise core_api.APIError(404, 'no-such-device')

    # Slave events endpoint has special privilege requirements: its token signature must be validated using slave admin
    # password

    auth = request.headers.get('Authorization')
    if not auth:
        slave.warning('missing authorization header')
        raise core_api.APIError(401, 'authentication-required')

    try:
        core_api_auth.parse_auth_header(
            auth,
            core_api_auth.ORIGIN_DEVICE,
            lambda u: slave.get_admin_password_hash(),
            require_usr=False
        )

    except core_api_auth.AuthError as e:
        slave.warning(str(e))
        raise core_api.APIError(401, 'authentication-required') from e

    core_api_schema.validate(params, api_schema.POST_SLAVE_DEVICE_EVENTS)

    if slave.get_poll_interval() > 0:
        raise core_api.APIError(400, 'polling-enabled')

    if slave.is_listen_enabled():
        raise core_api.APIError(400, 'listening-enabled')

    # At this point we can be sure the slave is permanently offline

    try:
        await slave.handle_event(params)

    except Exception as e:
        raise core_api.APIError(500, 'unexpected-error', message=str(e)) from e

    slave.update_last_sync()
    await slave.save()

    # As soon as we receive event requests (normally generated by a webhook), we can consider the device is temporarily
    # reachable, so we apply provisioning values and update data locally
    slave.schedule_provisioning_and_update(1)
Example #3
0
async def patch_slave_device(request: core_api.APIRequest, name: str,
                             params: GenericJSONDict) -> None:
    core_api_schema.validate(params, api_schema.PATCH_SLAVE_DEVICE)

    slave = slaves_devices.get(name)
    if not slave:
        raise core_api.APIError(404, 'no such device')

    if params.get('enabled') is True and not slave.is_enabled():
        await slave.enable()

    elif params.get('enabled') is False and slave.is_enabled():
        await slave.disable()

    if params.get('poll_interval') and params.get('listen_enabled'):
        raise core_api.APIError(400, 'listening and polling')

    if params.get('poll_interval') is not None:
        slave.set_poll_interval(params['poll_interval'])

    if params.get('listen_enabled') is not None:
        if params['listen_enabled']:
            # We need to know if device supports listening; we therefore call GET /device before enabling it

            if slave.is_enabled():
                try:
                    attrs = await slave.api_call('GET', '/device')

                except Exception as e:
                    raise exceptions.adapt_api_error(e) from e

                if 'listen' not in attrs['flags']:
                    raise core_api.APIError(400, 'no listen support')

            slave.enable_listen()

        else:
            slave.disable_listen()

    slave.save()
    slave.trigger_update()
Example #4
0
async def delete_slave_device(request: core_api.APIRequest, name: str) -> None:
    slave = slaves_devices.get(name)
    if not slave:
        raise core_api.APIError(404, 'no-such-device')

    await slaves_devices.remove(slave)