示例#1
0
def guess_target_format(request):
    # type: (Request) -> Tuple[Str, bool]
    """
    Guess the best applicable response ``Content-Type`` header according to request ``Accept`` header and ``format``
    query, or defaulting to :py:data:`CONTENT_TYPE_JSON`.

    :returns: tuple of matched MIME-type and where it was found (``True``: header, ``False``: query)
    """
    content_type = FORMAT_TYPE_MAPPING.get(request.params.get("format"))
    is_header = False
    if not content_type:
        is_header = True
        content_type = get_header("accept",
                                  request.headers,
                                  default=CONTENT_TYPE_JSON,
                                  split=";,")
        if content_type != CONTENT_TYPE_JSON:
            # because most browsers enforce some 'visual' list of accept header, revert to JSON if detected
            # explicit request set by other client (e.g.: using 'requests') will have full control over desired content
            user_agent = get_header("user-agent", request.headers)
            if user_agent and any(
                    browser in user_agent
                    for browser in ["Mozilla", "Chrome", "Safari"]):
                content_type = CONTENT_TYPE_JSON
    if not content_type or content_type == CONTENT_TYPE_ANY:
        is_header = True
        content_type = CONTENT_TYPE_JSON
    return content_type, is_header
示例#2
0
def unauthorized_or_forbidden(request):
    # type: (Request) -> HTTPException
    """
    Overrides the default ``HTTPForbidden`` [403] by appropriate ``HTTPUnauthorized`` [401] when applicable.

    Unauthorized response is for restricted user access according to credentials and/or authorization headers.
    Forbidden response is for operation refused by the underlying process operations.

    Without this fix, both situations return [403] regardless.

    .. seealso::
        http://www.restapitutorial.com/httpstatuscodes.html
    """
    authn_policy = request.registry.queryUtility(IAuthenticationPolicy)
    http_err = HTTPForbidden
    http_msg = s.HTTPForbiddenResponseSchema.description
    if authn_policy:
        principals = authn_policy.effective_principals(request)
        if Authenticated not in principals:
            http_err = HTTPUnauthorized
            http_msg = s.UnauthorizedResponseSchema.description
    content = get_request_info(request, default_message=http_msg)

    return raise_http(nothrow=True,
                      httpError=http_err,
                      detail=content[u"detail"],
                      content=content,
                      contentType=get_header("Accept",
                                             request.headers,
                                             default=CONTENT_TYPE_JSON,
                                             split=";,"))
示例#3
0
def not_found_or_method_not_allowed(request):
    # type: (Request) -> HTTPException
    """
    Overrides the default ``HTTPNotFound`` [404] by appropriate ``HTTPMethodNotAllowed`` [405] when applicable.

    Not found response can correspond to underlying process operation not finding a required item, or a completely
    unknown route (path did not match any existing API definition).
    Method not allowed is more specific to the case where the path matches an existing API route, but the specific
    request method (GET, POST, etc.) is not allowed on this path.

    Without this fix, both situations return [404] regardless.
    """
    # noinspection PyProtectedMember
    if isinstance(request.exception, PredicateMismatch
                  ) and request.method not in request.exception._safe_methods:
        http_err = HTTPMethodNotAllowed
        http_msg = ""  # auto-generated by HTTPMethodNotAllowed
    else:
        http_err = HTTPNotFound
        http_msg = s.NotFoundResponseSchema.description
    content = get_request_info(request, default_message=http_msg)
    return raise_http(nothrow=True,
                      httpError=http_err,
                      detail=content[u"detail"],
                      content=content,
                      contentType=get_header("Accept",
                                             request.headers,
                                             default=CONTENT_TYPE_JSON,
                                             split=";,"))
示例#4
0
def ows_parser_factory(request):
    # type: (Request) -> OWSParser
    """
    Retrieve the appropriate :class:`OWSParser` parser using the ``Content-Type`` header.

    If the ``Content-Type`` header is missing or ``text/plain``, and the request has a body,
    try to parse the body as JSON and set the content-type to ``application/json`` if successful.

    Handle XML-like ``Content-Type`` headers such as ``application/x-www-form-urlencoded`` whenever applicable.

    Otherwise, use the basic :class:`OWSGetParser` or :class:`OWSPostParser` according to the presence of a body.
    These provide minimal parsing to handle most typical `OGC Web Services` (:term:`OWS`) request parameters.
    """
    content_type = get_header("Content-Type",
                              request.headers,
                              default=None,
                              split=";,")

    if content_type is None or content_type == CONTENT_TYPE_PLAIN:
        if is_json_body(request.body):
            # default to json when can be parsed as json
            request.headers[
                "Content-Type"] = request.content_type = content_type = CONTENT_TYPE_JSON

    if content_type in (CONTENT_TYPE_JSON, CONTENT_TYPE_FORM):
        return MultiFormatParser(request)

    if request.body:
        return OWSPostParser(request)

    return OWSGetParser(request)
示例#5
0
def request_api(
        request,  # type: Request
        path,  # type: Str
        method="GET",  # type: Str
        data=None,  # type: Optional[JSON]
        headers=None,  # type: Optional[HeadersType]
        cookies=None,  # type: Optional[CookiesType]
):  # type: (...) -> Response
    """
    Use a pyramid sub-request to request Magpie API routes via the UI. This avoids max retries and closed connections
    when using 1 worker (eg: during tests).

    Some information is retrieved from ``request`` to pass down to the sub-request (eg: cookies).
    If they are passed as argument, corresponding values will override the ones found in ``request``.

    All sub-requests to the API are assumed to be of ``magpie.common.CONTENT_TYPE_JSON`` unless explicitly overridden
    with ``headers``.
    """
    method = method.upper()
    extra_kwargs = {"method": method}

    if headers:
        headers = dict(headers)
    else:
        headers = {
            "Accept": CONTENT_TYPE_JSON,
            "Content-Type": CONTENT_TYPE_JSON
        }
    # although no body is required per-say for HEAD/GET requests, add it if missing
    # this avoid downstream errors when 'request.POST' is accessed
    # we use a plain empty byte str because empty dict `{}` or `None` cause errors on each case
    # of local/remote testing with corresponding `webtest.TestApp`/`requests.Request`
    if not data:
        data = u""
    if isinstance(data, dict) and get_header(
            "Content-Type", headers, split=[",", ";"]) == CONTENT_TYPE_JSON:
        data = json.dumps(data)

    if isinstance(cookies, dict):
        cookies = list(cookies.items())
    if cookies and isinstance(headers, dict):
        headers = list(cookies.items())
        for c, v in cookies:
            headers.append(("Set-Cookie", "{}={}".format(c, v)))
    if not cookies:
        cookies = request.cookies
    # cookies must be added to kw only if populated, iterable error otherwise
    if cookies:
        extra_kwargs["cookies"] = cookies

    subreq = Request.blank(path,
                           base_url=request.application_url,
                           headers=headers,
                           POST=data,
                           **extra_kwargs)
    return request.invoke_subrequest(subreq, use_tweens=True)
示例#6
0
def check_ui_response_basic_info(response,
                                 expected_code=200,
                                 expected_type=CONTENT_TYPE_HTML):
    msg = None \
        if get_header("Content-Type", response.headers) != CONTENT_TYPE_JSON \
        else "Response body: {}".format(get_json_body(response))
    check_val_equal(response.status_code, expected_code, msg=msg)
    check_val_is_in("Content-Type", dict(response.headers))
    check_val_is_in(expected_type, get_response_content_types_list(response))
    check_val_is_in("Magpie Administration", response.text,
                    msg=null)  # don't output big html if failing
示例#7
0
 def test_get_header_split(self):
     headers = {
         "Content-Type": "{}; charset=UTF-8".format(CONTENT_TYPE_JSON)
     }
     for name in [
             "content_type", "content-type", "Content_Type", "Content-Type",
             "CONTENT_TYPE", "CONTENT-TYPE"
     ]:
         for split in [";,", ",;", ";", (",", ";"), [";", ","]]:
             utils.check_val_equal(get_header(name, headers, split=split),
                                   CONTENT_TYPE_JSON)
示例#8
0
def internal_server_error(request):
    # type: (Request) -> HTTPException
    """
    Overrides default HTTP.
    """
    content = get_request_info(
        request,
        exception_details=True,
        default_message=s.InternalServerErrorResponseSchema.description)
    return raise_http(nothrow=True,
                      httpError=HTTPInternalServerError,
                      detail=content[u"detail"],
                      content=content,
                      contentType=get_header("Accept",
                                             request.headers,
                                             default=CONTENT_TYPE_JSON,
                                             split=";,"))
示例#9
0
 def validate_accept_header(request):
     # ignore types defined under UI or static routes to allow rendering
     path = request.path if not request.path.startswith(
         "/magpie") else request.path.replace("/magpie", "", 1)
     if not any(path.startswith(p) for p in ("/ui", "/static")):
         any_supported_header = SUPPORTED_CONTENT_TYPES + [CONTENT_TYPE_ANY]
         accept = get_header("accept",
                             request.headers,
                             default=CONTENT_TYPE_JSON,
                             split=";,")
         verify_param(accept,
                      isIn=True,
                      paramCompare=any_supported_header,
                      paramName="Accept Header",
                      httpError=HTTPNotAcceptable,
                      msgOnFail=s.NotAcceptableResponseSchema.description)
     return handler(request)
示例#10
0
def ows_parser_factory(request):
    # type: (Request) -> OWSParser
    """
    Retrieve the appropriate ``OWSRequest`` parser using the ``Content-Type`` header.

    Default to JSON if no ``Content-Type`` is specified or if it is 'text/plain' but can be parsed as JSON. Otherwise,
    use the GET/POST WPS parsers.
    """
    content_type = get_header("Content-Type",
                              request.headers,
                              default=None,
                              split=";,")
    if content_type is None or content_type == CONTENT_TYPE_PLAIN:
        try:
            # noinspection PyUnresolvedReferences
            if request.body:
                # raises if parsing fails
                # noinspection PyUnresolvedReferences
                json.loads(request.body)
            content_type = CONTENT_TYPE_JSON
        except ValueError:
            pass

    if content_type == CONTENT_TYPE_JSON:
        return JSONParser(request)
    elif content_type == CONTENT_TYPE_FORM:
        return FormParser(request)
    else:
        if request.method == "GET":
            return WPSGet(request)
        elif request.method == "POST":
            return WPSPost(request)

    # method not supported, raise using the specified content type header
    raise_http(httpError=HTTPMethodNotAllowed,
               contentType=content_type,
               detail="Method not implemented by Magpie OWSParser.")
示例#11
0
def test_request(test_item,
                 method,
                 path,
                 timeout=5,
                 allow_redirects=True,
                 **kwargs):
    """
    Calls the request using either a :class:`webtest.TestApp` instance or :class:`requests.Request` from a string URL.

    :param test_item: one of `Base_Magpie_TestCase`, `webtest.TestApp` or remote server URL to call with `requests`
    :param method: request method (GET, POST, PUT, DELETE)
    :param path: test path starting at base path
    :param timeout: `timeout` to pass down to `request`
    :param allow_redirects: `allow_redirects` to pass down to `request`
    :return: response of the request
    """
    method = method.upper()
    status = kwargs.pop("status", None)

    # obtain json body from any json/data/body/params kw and empty {} if not specified
    # reapply with the expected webtest/requests method kw afterward
    json_body = None
    for kw in ["json", "data", "body", "params"]:
        json_body = kwargs.get(kw, json_body)
        if kw in kwargs:
            kwargs.pop(kw)
    json_body = json_body or {}

    app_or_url = get_app_or_url(test_item)
    if isinstance(app_or_url, TestApp):
        # remove any 'cookies' keyword handled by the 'TestApp' instance
        if "cookies" in kwargs:
            cookies = kwargs.pop("cookies")
            if cookies and not app_or_url.cookies:
                app_or_url.cookies.update(cookies)

        # obtain Content-Type header if specified to ensure it is properly applied
        kwargs["content_type"] = get_header("Content-Type",
                                            kwargs.get("headers"))

        # convert JSON body as required
        kwargs["params"] = json_body
        if json_body is not None:
            kwargs.update(
                {"params": json.dumps(json_body, cls=json.JSONEncoder)})
        if status and status >= 300:
            kwargs.update({"expect_errors": True})
        # noinspection PyProtectedMember
        resp = app_or_url._gen_request(method, path, **kwargs)
        # automatically follow the redirect if any and evaluate its response
        max_redirect = kwargs.get("max_redirects", 5)
        while 300 <= resp.status_code < 400 and max_redirect > 0:
            resp = resp.follow()
            max_redirect -= 1
        assert max_redirect >= 0, "Maximum follow redirects reached."
        # test status accordingly if specified
        assert resp.status_code == status or status is None, "Response not matching the expected status code."
        return resp
    else:
        # remove keywords specific to TestApp
        kwargs.pop("expect_errors", None)

        kwargs["json"] = json_body
        url = "{url}{path}".format(url=app_or_url, path=path)
        return requests.request(method,
                                url,
                                timeout=timeout,
                                allow_redirects=allow_redirects,
                                **kwargs)
示例#12
0
def request_api(
        request,  # type: Request
        path,  # type: Str
        method="GET",  # type: Str
        data=None,  # type: Optional[Union[JSON, Str]]
        headers=None,  # type: Optional[HeadersType]
        cookies=None,  # type: Optional[CookiesType]
):  # type: (...) -> AnyResponseType
    """
    Use a pyramid sub-request to request Magpie API routes via the UI. This avoids max retries and closed connections
    when using 1 worker (eg: during tests).

    Some information is retrieved from :paramref:`request` to pass down to the sub-request (eg: cookies).
    If they are passed as argument, corresponding values will override the ones found in :paramref:`request`.

    All sub-requests to the API are assumed to be :py:data:`magpie.common.CONTENT_TYPE_JSON` unless explicitly
    overridden with :paramref:`headers`. Headers are also looked for for additional ``Set-Cookie`` header in case they
    need to be passed down to :paramref:`cookies`.

    :param request: incoming Magpie UI request that requires sub-request to Magpie API, to retrieve required details.
    :param path: local Magpie API path (relative to root without URL).
    :param method: HTTP method to send the API sub-request.
    :param data: JSON dictionary or literal string content of the request body.
    :param headers: override headers to employ for the API sub-request. Defaults to JSON Accept & Content-Type headers.
    :param cookies: override cookies to employ for the API sub-request. Defaults to current logged user.
    """
    method = method.upper()
    extra_kwargs = {"method": method}

    if headers:
        headers = dict(headers)
    else:
        headers = {
            "Accept": CONTENT_TYPE_JSON,
            "Content-Type": CONTENT_TYPE_JSON
        }
    # although no body is required per-say for HEAD/GET requests, add it if missing
    # this avoid downstream errors when 'request.POST' is accessed
    # we use a plain empty byte str because empty dict `{}` or `None` cause errors on each case
    # of local/remote testing with corresponding `webtest.TestApp`/`requests.Request`
    if not data:
        data = ""
    if isinstance(data, dict) and get_header(
            "Content-Type", headers, split=[",", ";"]) == CONTENT_TYPE_JSON:
        data = json.dumps(data)

    if isinstance(cookies, dict):
        cookies = list(cookies.items())
    if cookies and isinstance(headers, dict):
        headers = list(headers.items())
        for cookie_name, cookie_value in cookies:
            headers.append(
                ("Set-Cookie", "{}={}".format(cookie_name, cookie_value)))
    if not cookies:
        cookies = request.cookies
    # cookies must be added to kw only if populated, iterable error otherwise
    if cookies:
        extra_kwargs["cookies"] = cookies

    subreq = Request.blank(path,
                           base_url=request.application_url,
                           headers=headers,
                           POST=data,
                           **extra_kwargs)
    return request.invoke_subrequest(subreq, use_tweens=True)