jamielennox / requests-mock

Mocked responses for the requests library
https://requests-mock.readthedocs.io
Apache License 2.0
441 stars 71 forks source link

Easy access to request cookies and authentication in callbacks #49

Open snorfalorpagus opened 6 years ago

snorfalorpagus commented 6 years ago

Is there an easy way to access cookies and authentication in a mock request callback?

I want to do something like this:

def callback(request, context):
    if request.auth and request.auth.password == "secret":
        context.status_code = 200
        return "ok"
    else:
        context.status_code = 403  # forbidden
        return "forbidden"

I wrote a couple of basic helper functions for this:

def parse_basic_auth(request):
    if "Authorization" in request.headers:
        header = request.headers["Authorization"]
        if header.startswith("Basic"):
            data = header.split(" ")[1]
            username, password = base64.b64decode(data).decode("utf-8").split(":")
            return Auth(username, password)
    return None

def parse_cookie(request, cookie_name):
    if "Cookie" in request.headers:
        cookies = request.headers["Cookie"].split("; ")
        for cookie in cookies:
            name, value = cookie.split("=", 1)
            if name == cookie_name:
                return value
    return None

Does this functionality exist already somewhere? If not, is there interest in including it (with some tests) in requests-mock?

jamielennox commented 6 years ago

So I've had an attempt in the past at doing cookies but it leads down a deeper rabbit hole.

The problem with cookies is that requests-mock doesn't imply and order on the way responses are returned. Figuring out that cookies should be maintained across requests was more difficult than i would have expected and so it just never got completed. See #17 for that.

Regarding simply exposing auth and cookies on the requests - sure, i'd be happy to include that. In general I try to follow the naming scheme that requests follows and a requests.Request has an auth and cookie parameter that we could replicate. Adding it to the _RequestObjectProxy would make it available in the matcher and in the history.

What do you think we return there?

For auth having simply basic auth excludes tokens and other options, but is it better to be explicit like a basic_auth property or maintain the requests pattern? For cookies do we go the effort of a full CookieJar?

Also, if we add them there do we need them as a regular matcher, eg:

m.get(url, auth=("user", "password"), text="response", status_code=200)

We can go into details, but yes, i'd be happy to have those accessors.

rfportilla commented 4 years ago

Hi, Jamie. This seems like an interesting challenge. It seems like we are still interested in this on some level even though this conversation has gone stale. I am curious as to what the expectation here is. There should be a line drawn between creating a mock and creating a fully interactive web service. I realize there is a lot of gray area between these two. With that said, I think the focus should be the following (not carrying a live cookie jar throughout an long use case):

  1. Given a set of headers, a cookie jar is generated and pre-defined return is provided.
  2. Given a cookie jar (and possibly other criteria), a pre-defined return is provided.
  3. Provide helper properties like auth and cookie.

Am I understanding the scope? If the interaction needs to be more complex, then returns should be defined in between steps. I think the biggest part is to add cookie matching as part of the criteria.

jamielennox commented 4 years ago

Hey - so i'll warn up front this is more difficult than I expected. There are a number of cookie handling libraries, but they're not designed to make it easy to extract cookies in a nice way. The other reason this kind of lost momentum is:

  1. I think most people are using requests on APIs, and there's not many cookies in use.
  2. A cookie is just a header right? If you return a response with the appropriate Set-Cookie headers it pretty much works today as requests is responsible for that level of parsing.

So on the fully interactive web service - I completely agree, and one of the things that requests-mock has never guaranteed is ordering. You can hit any mock in any order and so long as it matches you get a response. There was some problems here with cookies that was a problem that I can't quite remember without diving in but are around the long-lived case you mention.

From a UX perspective, i generally try and match parameters to what requests itself provides, which in this case would be: https://requests.readthedocs.io/en/master/user/quickstart/#cookies, though we need to distinguish here on are you trying to match this cookie in a request or are you trying to return this cookie in a response. There's precedence for this in headers though where headers={} are headers returned on a response, and request_headers={} are headers that must be matched to trigger the response.

So from your list:

  1. I think this should be handled already? That's mostly handled by the requests layer above us.
  2. Probably given a dictionary, but yes, the standard matcher is triggered.
  3. Yea, it'd be good maybe on request_history and some of those to be able to interrogate the cookies that were sent.