def validate_active_flag(request_body: bytes) -> None:
    """
    Validate the active flag data given to the endpoint.

    Args:
        request_body: The body of the request.

    Raises:
        Fail: There is active flag data given to the endpoint which is not
            either a Boolean or NULL.
    """

    if not request_body:
        return

    request_text = request_body.decode()
    if 'active_flag' not in json.loads(request_text):
        return

    active_flag = json.loads(request_text).get('active_flag')

    if active_flag is None or isinstance(active_flag, bool):
        return

    raise Fail(status_code=HTTPStatus.BAD_REQUEST)
Example #2
0
def validate_width(request_body: bytes) -> None:
    """
    Validate the width argument given to a VWS endpoint.

    Args:
        request_body: The body of the request.

    Raises:
        Fail: Width is given and is not a positive number.
    """

    if not request_body:
        return

    request_text = request_body.decode()
    if 'width' not in json.loads(request_text):
        return

    width = json.loads(request_text).get('width')

    width_is_number = isinstance(width, numbers.Number)
    width_positive = width_is_number and width > 0

    if not width_positive:
        raise Fail(status_code=HTTPStatus.BAD_REQUEST)
Example #3
0
def validate_metadata_encoding(request_body: bytes) -> None:
    """
    Validate that the given application metadata can be base64 decoded.

    Args:
        request_body: The body of the request.

    Raises:
        Fail: Application metadata is given and it cannot be base64
            decoded.
    """
    if not request_body:
        return

    request_text = request_body.decode()
    request_json = json.loads(request_text)
    if 'application_metadata' not in request_json:
        return

    application_metadata = request_json.get('application_metadata')

    if application_metadata is None:
        return

    try:
        decode_base64(encoded_data=application_metadata)
    except binascii.Error as exc:
        raise Fail(status_code=HTTPStatus.UNPROCESSABLE_ENTITY) from exc
Example #4
0
def validate_json(
    request_body: bytes,
    request_method: str,
) -> None:
    """
    Validate that there is either no JSON given or the JSON given is valid.

    Args:
        request_body: The body of the request.
        request_method: The HTTP method of the request.

    Raises:
        UnnecessaryRequestBody: A request body was given for an endpoint which
            does not require one.
        Fail: The request body includes invalid JSON.
    """

    if not request_body:
        return

    if request_method not in (POST, PUT):
        raise UnnecessaryRequestBody

    try:
        json.loads(request_body.decode())
    except JSONDecodeError as exc:
        raise Fail(status_code=HTTPStatus.BAD_REQUEST) from exc
Example #5
0
def validate_date_header_given(request_headers: Dict[str, str]) -> None:
    """
    Validate the date header is given to a VWS endpoint.

    Args:
        request_headers: The headers sent with the request.

    Raises:
        Fail: The date is not given.
    """

    if 'Date' in request_headers:
        return

    raise Fail(status_code=HTTPStatus.BAD_REQUEST)
def validate_auth_header_has_signature(
    request_headers: Dict[str, str], ) -> None:
    """
    Validate the authorization header includes a signature.

    Args:
        request_headers: The headers sent with the request.

    Raises:
        Fail: The "Authorization" header does not include a signature.
    """
    header = request_headers['Authorization']
    if header.count(':') == 1 and header.split(':')[1]:
        return

    raise Fail(status_code=HTTPStatus.BAD_REQUEST)
Example #7
0
def validate_date_format(request_headers: Dict[str, str]) -> None:
    """
    Validate the format of the date header given to a VWS endpoint.

    Args:
        request_headers: The headers sent with the request.

    Raises:
        Fail: The date is in the wrong format.
    """

    date_header = request_headers['Date']
    date_format = '%a, %d %b %Y %H:%M:%S GMT'
    try:
        datetime.datetime.strptime(date_header, date_format)
    except ValueError as exc:
        raise Fail(status_code=HTTPStatus.BAD_REQUEST) from exc
def validate_access_key_exists(
    request_headers: Dict[str, str],
    databases: Set[VuforiaDatabase],
) -> None:
    """
    Validate the authorization header includes an access key for a database.

    Args:
        request_headers: The headers sent with the request.
        databases: All Vuforia databases.

    Raises:
        Fail: The access key does not match a given database.
    """
    header = request_headers['Authorization']
    first_part, _ = header.split(':')
    _, access_key = first_part.split(' ')
    for database in databases:
        if access_key == database.server_access_key:
            return

    raise Fail(status_code=HTTPStatus.BAD_REQUEST)
Example #9
0
def validate_name_length(request_body: bytes) -> None:
    """
    Validate the length of the name argument given to a VWS endpoint.

    Args:
        request_body: The body of the request.

    Raises:
        Fail: A name is given and it is not a between 1 and 64 characters in
            length.
    """
    if not request_body:
        return

    request_text = request_body.decode()
    if 'name' not in json.loads(request_text):
        return

    name = json.loads(request_text)['name']

    if name and len(name) < 65:
        return

    raise Fail(status_code=HTTPStatus.BAD_REQUEST)
Example #10
0
def validate_name_type(request_body: bytes) -> None:
    """
    Validate the type of the name argument given to a VWS endpoint.

    Args:
        request_body: The body of the request.

    Raises:
        Fail: A name is given and it is not a string.
    """

    if not request_body:
        return

    request_text = request_body.decode()
    if 'name' not in json.loads(request_text):
        return

    name = json.loads(request_text)['name']

    if isinstance(name, str):
        return

    raise Fail(status_code=HTTPStatus.BAD_REQUEST)
Example #11
0
def validate_metadata_type(request_body: bytes) -> None:
    """
    Validate that the given application metadata is a string or NULL.

    Args:
        request_body: The body of the request.

    Raises:
        Fail: Application metadata is given and it is not a string or NULL.
    """
    if not request_body:
        return

    request_text = request_body.decode()
    request_json = json.loads(request_text)
    if 'application_metadata' not in request_json:
        return

    application_metadata = request_json.get('application_metadata')

    if application_metadata is None or isinstance(application_metadata, str):
        return

    raise Fail(status_code=HTTPStatus.BAD_REQUEST)
def validate_image_data_type(request_body: bytes) -> None:
    """
    Validate that the given image data is a string.

    Args:
        request_body: The body of the request.

    Raises:
        Fail: Image data is given and it is not a string.
    """

    if not request_body:
        return

    request_text = request_body.decode()
    if 'image' not in json.loads(request_text):
        return

    image = json.loads(request_text).get('image')

    if isinstance(image, str):
        return

    raise Fail(status_code=HTTPStatus.BAD_REQUEST)
def validate_image_encoding(request_body: bytes) -> None:
    """
    Validate that the given image data can be base64 decoded.

    Args:
        request_body: The body of the request.

    Raises:
        Fail: Image data is given and it cannot be base64 decoded.
    """

    if not request_body:
        return

    request_text = request_body.decode()
    if 'image' not in json.loads(request_text):
        return

    image = json.loads(request_text).get('image')

    try:
        decode_base64(encoded_data=image)
    except binascii.Error as exc:
        raise Fail(status_code=HTTPStatus.UNPROCESSABLE_ENTITY) from exc
Example #14
0
def update_target(target_id: str) -> Response:
    """
    Update a target.

    Fake implementation of
    https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Update-a-Target
    """
    # We do not use ``request.get_json(force=True)`` because this only works
    # when the content type is given as ``application/json``.
    request_json = json.loads(request.data)
    databases = get_all_databases()
    database = get_database_matching_server_keys(
        request_headers=dict(request.headers),
        request_body=request.data,
        request_method=request.method,
        request_path=request.path,
        databases=databases,
    )

    assert isinstance(database, VuforiaDatabase)
    [target] = [
        target for target in database.targets if target.target_id == target_id
    ]

    if target.status != TargetStatuses.SUCCESS.value:
        raise TargetStatusNotSuccess

    update_values = {}
    if 'width' in request_json:
        update_values['width'] = request_json['width']

    if 'active_flag' in request_json:
        active_flag = request_json['active_flag']
        if active_flag is None:
            raise Fail(status_code=HTTPStatus.BAD_REQUEST)
        update_values['active_flag'] = active_flag

    if 'application_metadata' in request_json:
        application_metadata = request_json['application_metadata']
        if application_metadata is None:
            raise Fail(status_code=HTTPStatus.BAD_REQUEST)
        update_values['application_metadata'] = application_metadata

    if 'name' in request_json:
        name = request_json['name']
        update_values['name'] = name

    if 'image' in request_json:
        image = request_json['image']
        update_values['image'] = image

    target_manager_base_url = os.environ['TARGET_MANAGER_BASE_URL']
    put_url = (
        f'{target_manager_base_url}/databases/{database.database_name}/'
        f'targets/{target_id}'
    )
    requests.put(url=put_url, json=update_values)

    date = email.utils.formatdate(None, localtime=False, usegmt=True)
    headers = {
        'Connection': 'keep-alive',
        'Content-Type': 'application/json',
        'Server': 'nginx',
        'Date': date,
    }
    body = {
        'result_code': ResultCodes.SUCCESS.value,
        'transaction_id': uuid.uuid4().hex,
    }
    return Response(
        status=HTTPStatus.OK,
        response=json_dump(body),
        headers=headers,
    )
Example #15
0
    def update_target(
        self,
        request: _RequestObjectProxy,
        context: _Context,
    ) -> str:
        """
        Update a target.

        Fake implementation of
        https://library.vuforia.com/articles/Solution/How-To-Use-the-Vuforia-Web-Services-API.html#How-To-Update-a-Target
        """
        try:
            run_services_validators(
                request_headers=request.headers,
                request_body=request.body,
                request_method=request.method,
                request_path=request.path,
                databases=self._target_manager.databases,
            )
        except ValidatorException as exc:
            context.headers = exc.headers
            context.status_code = exc.status_code
            return exc.response_text

        database = get_database_matching_server_keys(
            request_headers=request.headers,
            request_body=request.body,
            request_method=request.method,
            request_path=request.path,
            databases=self._target_manager.databases,
        )

        assert isinstance(database, VuforiaDatabase)

        target_id = request.path.split('/')[-1]
        target = database.get_target(target_id=target_id)
        body: Dict[str, str] = {}

        date = email.utils.formatdate(None, localtime=False, usegmt=True)

        if target.status != TargetStatuses.SUCCESS.value:
            exception = TargetStatusNotSuccess()
            context.headers = exception.headers
            context.status_code = exception.status_code
            return exception.response_text

        width = request.json().get('width', target.width)
        name = request.json().get('name', target.name)
        active_flag = request.json().get('active_flag', target.active_flag)
        application_metadata = request.json().get(
            'application_metadata',
            target.application_metadata,
        )

        image_value = target.image_value
        if 'image' in request.json():
            image_value = base64.b64decode(request.json()['image'])

        if 'active_flag' in request.json() and active_flag is None:
            fail_exception = Fail(status_code=HTTPStatus.BAD_REQUEST)
            context.headers = fail_exception.headers
            context.status_code = fail_exception.status_code
            return fail_exception.response_text

        if (
            'application_metadata' in request.json()
            and application_metadata is None
        ):
            fail_exception = Fail(status_code=HTTPStatus.BAD_REQUEST)
            context.headers = fail_exception.headers
            context.status_code = fail_exception.status_code
            return fail_exception.response_text

        # In the real implementation, the tracking rating can stay the same.
        # However, for demonstration purposes, the tracking rating changes but
        # when the target is updated.
        available_values = list(set(range(6)) - {target.tracking_rating})
        processed_tracking_rating = random.choice(available_values)

        gmt = ZoneInfo('GMT')
        last_modified_date = datetime.datetime.now(tz=gmt)

        new_target = dataclasses.replace(
            target,
            name=name,
            width=width,
            active_flag=active_flag,
            application_metadata=application_metadata,
            image_value=image_value,
            processed_tracking_rating=processed_tracking_rating,
            last_modified_date=last_modified_date,
        )

        database.targets.remove(target)
        database.targets.add(new_target)

        body = {
            'result_code': ResultCodes.SUCCESS.value,
            'transaction_id': uuid.uuid4().hex,
        }
        body_json = json_dump(body)
        context.headers = {
            'Connection': 'keep-alive',
            'Content-Type': 'application/json',
            'Server': 'nginx',
            'Date': date,
            'Content-Length': str(len(body_json)),
        }
        return body_json