I use APIClient() for my tests.
I use Token auth, so I need to use THIS
If we dive into the source code we'll see next:
# rest_framework/test.py
class APIClient(APIRequestFactory, DjangoClient):
def __init__(self, enforce_csrf_checks=False, **defaults):
super().__init__(**defaults)
self.handler = ForceAuthClientHandler(enforce_csrf_checks)
self._credentials = {}
def credentials(self, **kwargs):
"""
Sets headers that will be used on every outgoing request.
"""
self._credentials = kwarg
Also I use APIClient() as a pytest fixture in my code:
#pytest.fixture(scope="function")
def _api():
"""API factory for anonymous and auth requests"""
def __api(token=None, field=None):
api_client = APIClient()
headers = {}
if token:
headers["HTTP_AUTHORIZATION"] = f"Token {token}"
if field:
headers["X-CUSTOM-HEADER"] = field
api_client.credentials(**headers)
return api_client
return __api
But if we create TestMiddleware to look for headers, we see next:
lass TestMiddleware:
def __init__(self, get_response):
self._get_response = get_response
def __call__(self, request):
header = request.headers.get("X-CUSTOM-HEADER") # None
header = request.META.get("X-CUSTOM-HEADER") # Works fine!
...
# Response processing
response = self._get_response(request)
return response
The question is: Have we any way to have access to the X-CUSTOM-HEADER with APIClient() ?
Also if I use Postman it obviously works fine with request.headers.get()
The kwargs passed to the credentials() method ends up feeding directly into the constructor for a WSGIRequest; this means the kwargs it accepts aren't HTTP headers, but WSGI environment variables. And HTTP headers passed as WSGI env vars are always prefixed with HTTP_ — e.g. the Authorization header is configured with HTTP_AUTHORIZATION. Also, underscores are used in place of dashes.
To have your X-Custom-Header header come out the other side in request.headers (not request.META, which is a copy of the WSGI env vars), pass HTTP_X_CUSTOM_HEADER instead of X-CUSTOM-HEADER.
Related
I am trying to implement a type of custom authentication by using aiohttp something like the example in this link but I also need request body. Here is an example for requests:
class CustomAuth(AuthBase):
def __init__(self, secretkey):
self.secretkey = secretkey
def get_hash(self, request):
if request.body:
data = request.body.decode('utf-8')
else:
data = "{}"
signature = hmac.new(
str.encode(self.secretkey),
msg=str.encode(data),
digestmod=hashlib.sha256
).hexdigest().upper()
return signature
def __call__(self, request):
request.headers["CUSTOM-AUTH"] = self.get_hash(request)
return request
I've looked into tracing and BasicAuth but they are useless in my situation. On on_request_start request body is not ready, on on_request_chunk_sent headers have already been sent. A solution like BasicAuth don't have access the request data at all.
Do you have any idea?
Thanks in advance.
I have two endpoints defined in my flask service python file.
The first endpoint returns a list of parent and child nodes from a mmap json file which it parses.
The second endpoint returns a specific child field from a mmap json file which it parses.
Each of these endpoints can only be used when the token has been validated. Thus I have the following implementations.
from flask import request
import requests
def check_token(self):
# Method to verify the token from the another python Service
token_header = request.headers['authorization']
# Remove the 'Basic" part from the token
auth_token = token_header.split(maxsplit=1)[1]
self.__payload = {'token' : auth_token}
# Append the token to the header by using the payload
response = requests.get(self.__url, params=self.__payload, verify=False)
return response
# 1. This endpoint returns a list of parent and child nodes from a mmap json file which it parses.
class SystemList(Resource):
def get(self, systemname):
token = check_token()
if token.ok:
# Open the mmap file, parse it, and jsonify the result and return it
# Invalid token present
else:
return make_response(
jsonify("Invalid access as token invalid.")
)
# 2. The endpoint returns a specific child field from a mmap json file which it parses.
class SystemChildList(Resource):
def get(self, systemname, id):
token = check_token()
if token.ok:
# Open the mmap file, parse it, and jsonify the result and return it
# Invalid token present
else:
return make_response(
jsonify("Invalid access as token invalid.")
)
The issue I have is that I want to use a decorator to handle the validation of the token.
I want to be able to add it before the get() method something like the following.
#validatetoken
def get(self, designation):
# I am not sure what goes here and what do I need to put here?
# I want to be able to have one decorator for both of the SystemList and SystemChildList
# resource shown above.
I am not sure on what goes in the decorator. I am really new to these concepts. Any help is appreciated.
You can use method_decorators parameter to achieve this
try,
from flask import request
from functools import wraps
import requests
def check_token(f):
#wraps(f)
def decorated(*args, **kwargs):
token_header = request.headers['authorization']
# Remove the 'Basic" part from the token
auth_token = token_header.split(maxsplit=1)[1]
__url = "url_for_token_validation"
__payload = {'token' : auth_token}
# Append the token to the header by using the payload
response = requests.get(__url, params=__payload, verify=False)
if response.status_code != requests.codes.ok:
return make_response(
jsonify("Invalid access as token invalid.")
)
return f(*args, **kwargs)
return decorated
# 1. This endpoint returns a list of parent and child nodes from a mmap json file which it parses.
class SystemList(Resource):
#check_token
def get(self, systemname):
# Open the mmap file, parse it, and jsonify the result and return it
# 2. The endpoint returns a specific child field from a mmap json file which it parses.
class SystemChildList(Resource):
#check_token
def get(self, systemname, id):
# Open the mmap file, parse it, and jsonify the result and return it
I'm trying to write a nice auth helper for kraken. I want it to be as automatic as possible, so it needs to:
add a nonce (time.time()*1000) to the POST body
calculate a signature over the POST body
put the signature into the headers
I wrote the obvious code based on this answer:
class KrakenAuth(AuthBase):
"""a requests-module-compatible auth module for kraken.com"""
def __init__(self, key, secret):
self.api_key = key
self.secret_key = secret
def __call__(self, request):
#print("Auth got a %r" % type(request))
nonce = int(1000*time.time())
request.data = getattr(request, 'data', {})
request.data['nonce'] = nonce
request.prepare()
message = request.path_url + hashlib.sha256(str(nonce) + request.body).digest()
hmac_key = base64.b64decode(self.secret_key)
signature = hmac.new(hmac_key, message, hashlib.sha512).digest()
signature = base64.b64encode(signature)
request.headers.update({
'API-Key': self.api_key,
'API-Sign': signature
})
return request
and them I'm calling it (from a wrapper method on another object) like:
def _request(self, method, url, **kwargs):
if not self._auth:
self._auth = KrakenAuth(key, secret)
if 'auth' not in kwargs:
kwargs['auth'] = self._auth
return self._session.request(method, URL + url, **kwargs)
...but it doesn't work. The commented-out print() statement shows that it's getting a PreparedRequest object not a Request object, and thus the call to request.prepare() is a call to PreparedRequest.prepare does nothing useful because there's no request.data because it's already been converted into a body attribute.
You can't access the data attribute of the request, because the authentication object is applied to a requests.PreparedRequest() instance, which has no .data attribute.
The normal flow for a Session.request() call (used by all the request.<method> and session.<method> calls), is as follows:
A Request() instance is created with all the same arguments as the original call
The request is passed to Session.prepare_request(), which merges in session-stored base values with the arguments of the original call first, then
A PreparedRequest() instance is created
The PreparedRequest.prepare() method is called on that prepared request instance, passing in the merged data from the Request instance and the session.
The prepare() method calls the various self.prepare_* methods, including PreparedRequest.prepare_auth().
PreparedRequest.prepare_auth() calls auth(self) to give the authentication object a chance to attach information to the request.
Unfortunately for you, at no point during this flow will the original data mapping be available to anyone else but PreparedRequest.prepare() and PreparedRequest.prepare_body(), and in those methods the mapping is a local variable. You can't access it from the authentication object.
Your options are then:
To decode the body again, and call prepare_body() with the updated mapping.
To not use an authentication object, but use the other path from my answer; to explicitly create a prepared request and manipulate data first.
To play merry hell with the Python stack and extract locals from the prepare() method that is two frames up. I really can't recommend this path.
To keep the authentication method encapsulated nicely, I'd go with decoding / re-encoding; the latter is simple enough by reusing PreparedRequest.prepare_body():
import base64
import hashlib
import hmac
import time
try:
# Python 3
from urllib.parse import parse_qs
except ImportError:
# Python 2
from urlparse import parse_qs
from requests import AuthBase
URL_ENCODED = 'application/x-www-form-urlencoded'
class KrakenAuth(AuthBase):
"""a requests-module-compatible auth module for kraken.com"""
def __init__(self, key, secret):
self.api_key = key
self.secret_key = secret
def __call__(self, request):
ctheader = request.headers.get('Content-Type')
assert (
request.method == 'POST' and (
ctheader == URL_ENCODED or
requests.headers.get('Content-Length') == '0'
)
), "Must be a POST request using form data, or empty"
# insert the nonce in the encoded body
data = parse_qs(request.body)
data['nonce'] = nonce
request.prepare_body(data, None, None)
body = request.body
if not isinstance(body, bytes): # Python 3
body = body.encode('latin1') # standard encoding for HTTP
message = request.path_url + hashlib.sha256(b'%s%s' % (nonce, body)).digest()
hmac_key = base64.b64decode(self.secret_key)
signature = hmac.new(hmac_key, message, hashlib.sha512).digest()
signature = base64.b64encode(signature)
request.headers.update({
'API-Key': self.api_key,
'API-Sign': signature
})
return request
I'm trying to refactor some code in which many HTTP requests are made using the requests module. Many of these requests have (partially) the same headers, so I would like to 'pre-fill' these using Session objects.
However, I'm having difficulty making multiple inheritance work in this context. Here is what I've tried:
import requests, time
requestbin_URL = 'http://requestb.in/1nsaz9y1' # For testing only; remains usable for 48 hours
auth_token = 'asdlfjkwoieur182932385' # Fake authorization token
class AuthorizedSession(requests.Session):
def __init__(self, auth_token):
super(AuthorizedSession, self).__init__()
self.auth_token = auth_token
self.headers.update({'Authorization': 'token=' + self.auth_token})
class JSONSession(requests.Session):
def __init__(self):
super(JSONSession, self).__init__()
self.headers.update({'content-type': 'application/json'})
class AuthorizedJSONSession(AuthorizedSession, JSONSession):
def __init__(self, auth_token):
AuthorizedSession.__init__(self, auth_token=auth_token)
JSONSession.__init__(self)
""" These two commented-out requests work as expected """
# with JSONSession() as s:
# response = s.post(requestbin_URL, data={"ts" : time.time()})
# with AuthorizedSession(auth_token=auth_token) as s:
# response = s.post(requestbin_URL, data={"key1" : "value1"})
""" This one doesn't """
with AuthorizedJSONSession(auth_token=auth_token) as s:
response = s.post(requestbin_URL, data={"tag" : "some_tag_name"})
If I inspect the result of the last request at http://requestb.in/1nsaz9y1?inspect, I see the following:
It seems like the Content-Type field is correctly set to application/json; however, I don't see an Authorization header with the fake authentication token. How can I combine the AuthorizedSession and JSONSession classes to see both?
I've found that the request works if I define AuthorizedJSONSession more simply as follows:
class AuthorizedJSONSession(AuthorizedSession, JSONSession):
def __init__(self, auth_token):
super(AuthorizedJSONSession, self).__init__(auth_token=auth_token)
The resulting request now has updated both the Authorization and Content-Type headers:
I've understood that when a class inherits from multiple classes which in turn inherit from the same base class, then Python is 'smart enough' to simply use super to initialize.
Having a code inspired from http://code.djangoproject.com/wiki/XML-RPC :
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
from django.http import HttpResponse
dispatcher = SimpleXMLRPCDispatcher(allow_none=False, encoding=None) # Python 2.5
def rpc_handler(request):
"""
the actual handler:
if you setup your urls.py properly, all calls to the xml-rpc service
should be routed through here.
If post data is defined, it assumes it's XML-RPC and tries to process as such
Empty post assumes you're viewing from a browser and tells you about the service.
"""
if len(request.POST):
response = HttpResponse(mimetype="application/xml")
response.write(dispatcher._marshaled_dispatch(request.raw_post_data))
else:
pass # Not interesting
response['Content-length'] = str(len(response.content))
return response
def post_log(message = "", tags = []):
""" Code called via RPC. Want to know here the remote IP (or hostname). """
pass
dispatcher.register_function(post_log, 'post_log')
How could get the IP address of the client within the "post_log" definition?
I have seen IP address of client in Python SimpleXMLRPCServer? but can't apply it to my case.
Thanks.
Ok I could do it ... with some nifty tips ...
First, I created my own copy of SimpleXMLRPCDispatcher which inherit everything from it and overides 2 methods :
class MySimpleXMLRPCDispatcher (SimpleXMLRPCDispatcher) :
def _marshaled_dispatch(self, data, dispatch_method = None, request = None):
# copy and paste from /usr/lib/python2.6/SimpleXMLRPCServer.py except
response = self._dispatch(method, params)
# which becomes
response = self._dispatch(method, params, request)
def _dispatch(self, method, params, request = None):
# copy and paste from /usr/lib/python2.6/SimpleXMLRPCServer.py except
return func(*params)
# which becomes
return func(request, *params)
Then in my code, all to do is :
# ...
if len(request.POST):
response = HttpResponse(mimetype="application/xml")
response.write(dispatcher._marshaled_dispatch(request.raw_post_data, request = request))
# ...
def post_log(request, message = "", tags = []):
""" Code called via RPC. Want to know here the remote IP (or hostname). """
ip = request.META["REMOTE_ADDR"]
hostname = socket.gethostbyaddr(ip)[0]
That's it.
I know it's not very clean... Any suggestion for cleaner solution is welcome!