Exemplo n.º 1
0
async def get_listen(request: core_api.APIRequest, session_id: str,
                     timeout: Optional[int],
                     access_level: int) -> List[GenericJSONDict]:

    if session_id is None:
        raise core_api.APIError(400, 'missing field: session_id')

    if not re.match('[a-zA-Z0-9]{1,32}', session_id):
        raise core_api.APIError(400, 'invalid field: session_id')

    if timeout is not None:
        try:
            timeout = int(timeout)

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

        if timeout < 1 or timeout > 3600:
            raise core_api.APIError(400, 'invalid field: timeout')

    else:
        timeout = 60

    session = core_sessions.get(session_id)
    events = await session.reset_and_wait(timeout, access_level)

    return [await e.to_json() for e in events]
Exemplo n.º 2
0
async def put_device(request: core_api.APIRequest, params: Attributes) -> None:
    core_api_schema.validate(params, core_device_attrs.get_schema(loose=True))

    # Password fields must explicitly be ignored, so we pop them from supplied data
    for f in ('admin', 'normal', 'viewonly'):
        params.pop(f'{f}_password', None)

    # Ignore the date attribute
    params.pop('date', None)

    # Reset device attributes
    await core_device.reset(preserve_attrs=[
        'admin_password_hash', 'normal_password_hash', 'viewonly_password_hash'
    ])
    await core_device.load()

    try:
        core_device_attrs.set_attrs(params, ignore_extra=True)

    except core_device_attrs.DeviceAttributeError as e:
        raise core_api.APIError(400, e.error, attribute=e.attribute)

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

    await core_device.save()
    await core_device_events.trigger_update()
Exemplo n.º 3
0
async def patch_device(request: core_api.APIRequest,
                       params: Attributes) -> None:
    def unexpected_field_code(field: str) -> str:
        if field in core_device_attrs.get_attrdefs():
            return 'attribute-not-modifiable'

        else:
            return 'no-such-attribute'

    core_api_schema.validate(params,
                             core_device_attrs.get_schema(),
                             unexpected_field_code=unexpected_field_code,
                             unexpected_field_name='attribute')

    try:
        reboot_required = core_device_attrs.set_attrs(params)

    except core_device_attrs.DeviceAttributeError as e:
        raise core_api.APIError(400, e.error, attribute=e.attribute)

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

    await core_device.save()
    await core_device_events.trigger_update()

    if reboot_required:
        main.loop.call_later(2, system.reboot)
Exemplo n.º 4
0
async def wrap_error_with_index(index: int, func: Callable, *args, **kwargs) -> Any:
    try:
        result = func(*args, **kwargs)
        if inspect.isawaitable(result):
            result = await result

    except core_api.APIError as e:
        raise core_api.APIError(
            status=e.status,
            code=e.code,
            index=index,
            **e.params
        )

    except asyncio.TimeoutError:
        raise core_api.APIError(
            status=504,
            code='device-timeout',
            index=index
        )

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

    return result
Exemplo n.º 5
0
async def get_listen(request: core_api.APIRequest) -> GenericJSONList:

    session_id = request.headers.get('Session-Id')
    if not session_id:
        raise core_api.APIError(400, 'missing-header', header='Session-Id')

    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

        if timeout < 1 or timeout > 3600:
            raise core_api.APIError(400, 'invalid-field', field='timeout')

    else:
        timeout = 60  # default

    session = core_sessions.get(session_id)
    events = await session.reset_and_wait(timeout, request.access_level)

    return [await e.to_json() for e in events]
Exemplo n.º 6
0
async def delete_port(request: core_api.APIRequest, port_id: str) -> None:
    port = core_ports.get(port_id)
    if not port:
        raise core_api.APIError(404, 'no-such-port')

    if not isinstance(port, core_vports.VirtualPort):
        raise core_api.APIError(400, 'port-not-removable')

    await port.remove()
    await core_vports.remove(port_id)
Exemplo n.º 7
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
Exemplo n.º 8
0
async def patch_port(request: core_api.APIRequest, port_id: str,
                     params: Attributes) -> None:
    port = core_ports.get(port_id)
    if port is None:
        raise core_api.APIError(404, 'no-such-port')

    await set_port_attrs(port, params, ignore_extra_attrs=False)
Exemplo n.º 9
0
    async def _handle_api_call_exception(self, func: Callable, kwargs: dict, error: Exception) -> None:
        kwargs = dict(kwargs)
        params = kwargs.pop('params', None)
        args = json_utils.dumps(kwargs)
        body = params and json_utils.dumps(params) or '{}'

        if isinstance(error, core_responses.HTTPError):
            error = core_api.APIError(error.code, error.msg)

        if isinstance(error, core_api.APIError):
            logger.error('api call %s failed: %s (args=%s, body=%s)', func.__name__, error, args, body)

            self.set_status(error.status)
            if not self._finished:  # Avoid finishing an already finished request
                await self.finish_json(dict({'error': error.message}, **error.params))

        elif isinstance(error, StreamClosedError) and func.__name__ == 'get_listen':
            logger.debug('api call get_listen could not complete: stream closed')

        else:
            logger.error('api call %s failed: %s (args=%s, body=%s)', func.__name__, error, args, body, exc_info=True)

            self.set_status(500)
            if not self._finished:  # Avoid finishing an already finished request
                await self.finish_json({'error': str(error)})
Exemplo n.º 10
0
def adapt_api_error(error: Exception) -> Exception:
    if isinstance(
            error,
        (core_responses.HostUnreachable, core_responses.NetworkUnreachable,
         core_responses.UnresolvableHostname)):
        return core_api.APIError(502, 'unreachable')

    elif isinstance(error, core_responses.ConnectionRefused):
        return core_api.APIError(502, 'connection refused')

    elif isinstance(error, core_responses.InvalidJson):
        return core_api.APIError(502, 'invalid device')

    elif isinstance(error, core_responses.Timeout):
        return core_api.APIError(504, 'device timeout')

    elif isinstance(error, core_responses.HTTPError):
        return core_api.APIError.from_http_error(error)

    elif isinstance(error, DeviceOffline):
        return core_api.APIError(503, 'device offline')

    elif isinstance(error, InvalidDevice):
        return core_api.APIError(502, 'invalid device')

    elif isinstance(error, core_responses.AuthError):
        return core_api.APIError(
            400,
            'forbidden')  # Yes, 400, since it's a slave authorization issue

    elif isinstance(error, core_api.APIError):
        return error

    else:  # Leave error unchanged since it's probably an internal exception
        return error
Exemplo n.º 11
0
async def delete_port_history(request: core_api.APIRequest, port_id: str) -> None:
    port = core_ports.get(port_id)
    if port is None:
        raise core_api.APIError(404, 'no-such-port')

    query = request.query

    from_str = query.get('from')
    if from_str is None:
        raise core_api.APIError(400, 'missing-field', field='from')

    try:
        from_timestamp = int(from_str)

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

    if from_timestamp < 0:
        raise core_api.APIError(400, 'invalid-field', field='from')

    to_str = query.get('to')
    if to_str is None:
        raise core_api.APIError(400, 'missing-field', field='to')

    try:
        to_timestamp = int(to_str)

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

    if to_timestamp < 0:
        raise core_api.APIError(400, 'invalid-field', field='to')

    await core_history.remove_samples([port], from_timestamp, to_timestamp)
Exemplo n.º 12
0
async def get_discovered(request: core_api.APIRequest) -> GenericJSONList:
    timeout = request.query.get('timeout')
    if timeout is None:
        raise core_api.APIError(400, 'missing-field', field='timeout')

    try:
        timeout = int(timeout)

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

    discovered_devices = slaves_discover.get_discovered_devices()
    if discovered_devices is None:
        await slaves_discover.discover(timeout)

    return [
        d.to_json() for d in slaves_discover.get_discovered_devices().values()
    ]
Exemplo n.º 13
0
async def patch_port_value(request: core_api.APIRequest, port_id: str,
                           params: PortValue) -> None:
    port = core_ports.get(port_id)
    if port is None:
        raise core_api.APIError(404, 'no-such-port')

    try:
        core_api_schema.validate(params, await port.get_value_schema())

    except core_api.APIError:
        # Transform any validation error into an invalid-field APIError for value
        raise core_api.APIError(400, 'invalid-value') from None

    value = params

    # Step validation
    step = await port.get_attr('step')
    min_ = await port.get_attr('min')
    if None not in (step, min_) and step != 0 and (value - min_) % step:
        raise core_api.APIError(400, 'invalid-value')

    if not port.is_enabled():
        raise core_api.APIError(400, 'port-disabled')

    if not await port.is_writable():
        raise core_api.APIError(400, 'read-only-port')

    old_value = port.get_last_read_value()

    try:
        await port.write_transformed_value(value,
                                           reason=core_ports.CHANGE_REASON_API)

    except core_ports.PortTimeout as e:
        raise core_api.APIError(504, 'port-timeout') from e

    except core_ports.PortError as e:
        raise core_api.APIError(502, 'port-error', code=str(e)) from e

    except core_api.APIError:
        raise

    except Exception as e:
        # Transform any unhandled exception into APIError(500)
        raise core_api.APIError(500, 'unexpected-error', message=str(e)) from e

    await core_main.update()

    # If port value hasn't really changed, use 202 Accepted to inform consumer that new value hasn't been applied yet
    current_value = port.get_last_read_value()
    if (old_value == current_value) and (old_value != value):
        port.debug("API supplied value hasn't been applied right away")
        raise core_api.APIAccepted()
Exemplo n.º 14
0
async def wrap_error_with_port_id(port_id: str, func: Callable, *args,
                                  **kwargs) -> Any:
    try:
        result = func(*args, **kwargs)
        if inspect.isawaitable(result):
            result = await result

    except core_api.APIError as e:
        raise core_api.APIError(status=e.status,
                                code=e.code,
                                id=port_id,
                                **e.params)

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

    return result
Exemplo n.º 15
0
async def patch_webhooks(request: core_api.APIRequest,
                         params: GenericJSONDict) -> None:
    core_api_schema.validate(params, core_api_schema.PATCH_WEBHOOKS)

    try:
        core_webhooks.setup(**params)

    except core_webhooks.InvalidParamError as e:
        raise core_api.APIError(400, str(e)) from e

    core_webhooks.save()
Exemplo n.º 16
0
    def get_request_json(self) -> Any:
        if self._json is self._UNDEFINED:
            try:
                self._json = json_utils.loads(self.request.body)

            except ValueError as e:
                logger.error('could not decode json from request body: %s', e)

                raise core_api.APIError(400, 'malformed-body') from e

        return self._json
Exemplo n.º 17
0
async def patch_reverse(request: core_api.APIRequest,
                        params: GenericJSONDict) -> None:
    core_api_schema.validate(params, core_api_schema.PATCH_REVERSE)

    try:
        core_reverse.setup(**params)

    except core_reverse.InvalidParamError as e:
        raise core_api.APIError(400, str(e)) from e

    core_reverse.save()
Exemplo n.º 18
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()
Exemplo n.º 19
0
async def put_webhooks(request: core_api.APIRequest,
                       params: GenericJSONDict) -> None:
    core_api_schema.validate(params, core_api_schema.PATCH_WEBHOOKS)

    # Also ensure that needed fields are not empty when webhooks are enabled
    if params['enabled']:
        if not params['host']:
            raise core_api.APIError(400, 'invalid-field', field='host')
        if not params['path']:
            raise core_api.APIError(400, 'invalid-field', field='path')

    if 'password' not in params and 'password_hash' not in params:
        raise core_api.APIError(400, 'missing-field', field='password')

    try:
        core_webhooks.setup(**params)

    except core_webhooks.InvalidParamError as e:
        raise core_api.APIError(400, 'invalid-field', field=e.param) from e

    await core_webhooks.save()
Exemplo n.º 20
0
async def patch_firmware(request: core_api.APIRequest,
                         params: GenericJSONDict) -> None:
    core_api_schema.validate(params, core_api_schema.PATCH_FIRMWARE)

    status = await fwupdate.get_status()
    if status not in (fwupdate.STATUS_IDLE, fwupdate.STATUS_ERROR):
        raise core_api.APIError(503, 'busy')

    if params.get('url'):
        await fwupdate.update_to_url(params['url'])

    else:  # Assuming params['version']
        await fwupdate.update_to_version(params['version'])
Exemplo n.º 21
0
async def add_virtual_port(attrs: GenericJSONDict) -> core_ports.BasePort:
    id_ = attrs['id']
    type_ = attrs['type']
    min_ = attrs.get('min')
    max_ = attrs.get('max')
    integer = attrs.get('integer')
    step = attrs.get('step')
    choices = attrs.get('choices')

    core_api.logger.debug('adding port "%s"', id_)

    if core_ports.get(id_):
        raise core_api.APIError(400, 'duplicate-port')

    if len(core_vports.all_port_args()) >= settings.core.virtual_ports:
        raise core_api.APIError(400, 'too-many-ports')

    await core_vports.add(id_, type_, min_, max_, integer, step, choices)
    port = await core_ports.load_one(
        'qtoggleserver.core.vports.VirtualPort',
        {
            'id_': id_,
            'type_': type_,
            'min_': min_,
            'max_': max_,
            'integer': integer,
            'step': step,
            'choices': choices
        },
        trigger_add=
        False  # Will trigger add event manually, later, after we've enabled the port
    )

    # A virtual port is enabled by default
    await port.enable()
    await port.save()
    await port.trigger_add()

    return port
Exemplo n.º 22
0
async def get_port_value(request: core_api.APIRequest, port_id: str) -> NullablePortValue:
    port = core_ports.get(port_id)
    if port is None:
        raise core_api.APIError(404, 'no-such-port')

    if not port.is_enabled():
        return

    # TODO
    # Given the fact that get_last_read_value() simply returns last cached read value, and the fact that API specs
    # indicate that 502/504 be returned by GET /port/[id]/value in case of errors, we should remember the last error
    # generated by read_value() and return it here, if any.

    return port.get_last_read_value()
Exemplo n.º 23
0
async def patch_port_sequence(request: core_api.APIRequest, port_id: str,
                              params: GenericJSONDict) -> None:
    port = core_ports.get(port_id)
    if port is None:
        raise core_api.APIError(404, 'no-such-port')

    core_api_schema.validate(params, core_api_schema.PATCH_PORT_SEQUENCE)

    values = params['values']
    delays = params['delays']
    repeat = params['repeat']

    if len(values) != len(delays):
        raise core_api.APIError(400, 'invalid-field', field='delays')

    value_schema = await port.get_value_schema()
    step = await port.get_attr('step')
    min_ = await port.get_attr('min')
    for value in values:
        # Translate any APIError generated when validating value schema into an invalid-field APIError on value
        try:
            core_api_schema.validate(value, value_schema)

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

        # Step validation
        if None not in (step, min_) and step != 0 and (value - min_) % step:
            raise core_api.APIError(400, 'invalid-field', field='values')

    if not port.is_enabled():
        raise core_api.APIError(400, 'port-disabled')

    if not await port.is_writable():
        raise core_api.APIError(400, 'read-only-port')

    if await port.get_attr('expression'):
        raise core_api.APIError(400, 'port-with-expression')

    try:
        await port.set_sequence(values, delays, repeat)

    except Exception as e:
        # Transform any unhandled exception into APIError(500)
        raise core_api.APIError(500, 'unexpected-error', message=str(e)) from e
Exemplo n.º 24
0
async def post_ports(request: core_api.APIRequest, params: GenericJSONDict) -> Attributes:
    core_api_schema.validate(params, core_api_schema.POST_PORTS)

    port_id = params['id']
    port_type = params['type']
    _min = params.get('min')
    _max = params.get('max')
    integer = params.get('integer')
    step = params.get('step')
    choices = params.get('choices')

    if core_ports.get(port_id):
        raise core_api.APIError(400, 'duplicate port')

    if len(core_vports.all_settings()) >= settings.core.virtual_ports:
        raise core_api.APIError(400, 'too many ports')

    core_vports.add(port_id, port_type, _min, _max, integer, step, choices)
    port = await core_ports.load_one(
        'qtoggleserver.core.vports.VirtualPort',
        {
            'port_id': port_id,
            '_type': port_type,
            '_min': _min,
            '_max': _max,
            'integer': integer,
            'step': step,
            'choices': choices
        }
    )

    # A virtual port is enabled by default
    await port.enable()
    await port.save()

    return await port.to_json()
Exemplo n.º 25
0
async def patch_port(request: core_api.APIRequest, port_id: str, params: Attributes) -> None:
    port = core_ports.get(port_id)
    if port is None:
        raise core_api.APIError(404, 'no such port')

    def unexpected_field_msg(field: str) -> str:
        if field in port.get_non_modifiable_attrs():
            return 'attribute not modifiable: {field}'

        else:
            return 'no such attribute: {field}'

    core_api_schema.validate(params, await port.get_schema(), unexpected_field_msg=unexpected_field_msg)

    # Step validation
    for name, value in params.items():
        attrdef = port.ATTRDEFS[name]
        step = attrdef.get('step')
        _min = attrdef.get('min')
        if None not in (step, _min) and step != 0 and (value - _min) % step:
            raise core_api.APIError(400, f'invalid field: {name}')

    errors_by_name = {}

    async def set_attr(attr_name: str, attr_value: Attribute) -> None:
        core_api.logger.debug('setting attribute %s = %s on %s', attr_name, json_utils.dumps(attr_value), port)

        try:
            await port.set_attr(attr_name, attr_value)

        except Exception as e:
            errors_by_name[attr_name] = e

    if params:
        await asyncio.wait([set_attr(name, value) for name, value in params.items()])

    if errors_by_name:
        name, error = next(iter(errors_by_name.items()))

        if isinstance(error, core_api.APIError):
            raise error

        elif isinstance(error, core_ports.InvalidAttributeValue):
            raise core_api.APIError(400, f'invalid field: {name}')

        elif isinstance(error, core_ports.PortTimeout):
            raise core_api.APIError(504, 'port timeout')

        elif isinstance(error, core_ports.PortError):
            raise core_api.APIError(502, f'port error: {error}')

        else:
            # Transform any unhandled exception into APIError(500)
            raise core_api.APIError(500, str(error))

    await port.save()
Exemplo n.º 26
0
async def patch_discovered_device(request: core_api.APIRequest, name: str,
                                  params: GenericJSONDict) -> GenericJSONDict:
    core_api_schema.validate(params, api_schema.PATCH_DISCOVERED_DEVICE)

    discovered_devices = slaves_discover.get_discovered_devices() or {}
    discovered_device = discovered_devices.get(name)
    if not discovered_device:
        raise core_api.APIError(404, 'no-such-device')

    attrs = params['attrs']
    try:
        discovered_device = await slaves_discover.configure(
            discovered_device, attrs)

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

    return discovered_device.to_json()
Exemplo n.º 27
0
    def prepare(self) -> None:
        # Disable cache
        self.set_header('Cache-Control', 'no-cache, no-store, must-revalidate, max-age=0')

        if not self.AUTH_ENABLED:
            return

        # Parse auth header
        auth = self.request.headers.get('Authorization')
        if auth:
            try:
                usr = core_api_auth.parse_auth_header(
                    auth,
                    core_api_auth.ORIGIN_CONSUMER,
                    core_api_auth.consumer_password_hash_func
                )

            except core_api_auth.AuthError as e:
                logger.warning(str(e))
                return

        else:
            if core_device_attrs.admin_password_hash == core_device_attrs.EMPTY_PASSWORD_HASH:
                logger.debug('authenticating request as admin due to empty admin password')
                usr = '******'

            else:
                logger.warning('missing authorization header')
                return

        self.access_level = core_api.ACCESS_LEVEL_MAPPING[usr]
        self.username = usr

        logger.debug(
            'granted access level %s (username=%s)',
            core_api.ACCESS_LEVEL_MAPPING[self.access_level],
            self.username
        )

        # Validate session id
        session_id = self.request.headers.get('Session-Id')
        if session_id:
            if not SESSION_ID_RE.match(session_id):
                raise core_api.APIError(400, 'invalid-header', header='Session-Id')
Exemplo n.º 28
0
async def patch_port_value(request: core_api.APIRequest, port_id: str, params: PortValue) -> None:
    port = core_ports.get(port_id)
    if port is None:
        raise core_api.APIError(404, 'no such port')

    core_api_schema.validate(params, await port.get_value_schema(), invalid_request_msg='invalid value')

    value = params

    # Step validation
    step = await port.get_attr('step')
    _min = await port.get_attr('min')
    if None not in (step, _min) and step != 0 and (value - _min) % step:
        raise core_api.APIError(400, 'invalid field: value')

    if not port.is_enabled():
        raise core_api.APIError(400, 'port disabled')

    if not await port.is_writable():
        raise core_api.APIError(400, 'read-only port')

    old_value = port.get_value()

    try:
        await port.write_transformed_value(value, reason=core_ports.CHANGE_REASON_API)

    except core_ports.PortTimeout as e:
        raise core_api.APIError(504, 'port timeout') from e

    except core_ports.PortError as e:
        raise core_api.APIError(502, f'port error: {e}') from e

    except core_api.APIError:
        raise

    except Exception as e:
        # Transform any unhandled exception into APIError(500)
        raise core_api.APIError(500, str(e)) from e

    await main.update()

    # If port value hasn't really changed, trigger a value-change to inform consumer that new value has been ignored
    current_value = port.get_value()
    if old_value == current_value:
        port.debug('API supplied value was ignored')
        port.trigger_value_change()
Exemplo n.º 29
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)
Exemplo n.º 30
0
async def post_port_sequence(request: core_api.APIRequest, port_id: str, params: GenericJSONDict) -> None:
    port = core_ports.get(port_id)
    if port is None:
        raise core_api.APIError(404, 'no such port')

    core_api_schema.validate(params, core_api_schema.POST_PORT_SEQUENCE)

    values = params['values']
    delays = params['delays']
    repeat = params['repeat']

    if len(values) != len(delays):
        raise core_api.APIError(400, 'invalid field: delays')

    value_schema = await port.get_value_schema()
    step = await port.get_attr('step')
    _min = await port.get_attr('min')
    for value in values:
        core_api_schema.validate(value, value_schema, invalid_request_msg='invalid field: values')

        # Step validation
        if None not in (step, _min) and step != 0 and (value - _min) % step:
            raise core_api.APIError(400, 'invalid field: values')

    if not port.is_enabled():
        raise core_api.APIError(400, 'port disabled')

    if not await port.is_writable():
        raise core_api.APIError(400, 'read-only port')

    if await port.get_attr('expression'):
        raise core_api.APIError(400, 'port with expression')

    try:
        await port.set_sequence(values, delays, repeat)

    except Exception as e:
        # Transform any unhandled exception into APIError(500)
        raise core_api.APIError(500, str(e)) from e