For some of my Django views I've created a decorator that performs Basic HTTP access authentication. However, while writing test cases in Django, it took me a while to work out how to authenticate to the view. Here's how I did it. I hope somebody finds this useful.
Here's how I did it:
from django.test import Client
import base64
auth_headers = {
'HTTP_AUTHORIZATION': 'Basic ' + base64.b64encode('username:password'),
}
c = Client()
response = c.get('/my-protected-url/', **auth_headers)
Note: You will also need to create a user.
In your Django TestCase you can update the client defaults to contain your HTTP basic auth credentials.
import base64
from django.test import TestCase
class TestMyStuff(TestCase):
def setUp(self):
credentials = base64.b64encode('username:password')
self.client.defaults['HTTP_AUTHORIZATION'] = 'Basic ' + credentials
For python3, you can base64-encode your username:password string:
base64.b64encode(b'username:password')
This returns bytes, so you need to transfer it into an ASCII string with .decode('ascii'):
Complete example:
import base64
from django.test import TestCase
class TestClass(TestCase):
def test_authorized(self):
headers = {
'HTTP_AUTHORIZATION': 'Basic ' +
base64.b64encode(b'username:password').decode("ascii")
}
response = self.client.get('/', **headers)
self.assertEqual(response.status_code, 200)
Assuming I have a login form, I use the following technique to login through the test framework:
client = Client()
client.post('/login/', {'username': 'john.smith', 'password': 'secret'})
I then carry the client around in my other tests since it's already authenticated. What is your question to this post?
(python3) I'm using this in a test:
credentials_string = '%s:%s' % ('invalid', 'invalid')
credentials = base64.b64encode(credentials_string.encode())
self.client.defaults['HTTP_AUTHORIZATION'] = 'Basic ' + credentials.decode()
and the following in a view:
import base64
[...]
type, auth = request.META['HTTP_AUTHORIZATION'].split(' ', 1)
auth = base64.b64decode(auth.strip()).decode()
Another way to do it is to bypass the Django Client() and use Requests instead.
class MyTest(TestCase):
def setUp(self):
AUTH = requests.auth.HTTPBasicAuth("username", "password")
def some_test(self):
resp = requests.get(BASE_URL + 'endpoint/', auth=AUTH)
self.assertEqual(resp.status_code, 200)
Related
For some of my Django views I've created a decorator that performs Basic HTTP access authentication. However, while writing test cases in Django, it took me a while to work out how to authenticate to the view. Here's how I did it. I hope somebody finds this useful.
Here's how I did it:
from django.test import Client
import base64
auth_headers = {
'HTTP_AUTHORIZATION': 'Basic ' + base64.b64encode('username:password'),
}
c = Client()
response = c.get('/my-protected-url/', **auth_headers)
Note: You will also need to create a user.
In your Django TestCase you can update the client defaults to contain your HTTP basic auth credentials.
import base64
from django.test import TestCase
class TestMyStuff(TestCase):
def setUp(self):
credentials = base64.b64encode('username:password')
self.client.defaults['HTTP_AUTHORIZATION'] = 'Basic ' + credentials
For python3, you can base64-encode your username:password string:
base64.b64encode(b'username:password')
This returns bytes, so you need to transfer it into an ASCII string with .decode('ascii'):
Complete example:
import base64
from django.test import TestCase
class TestClass(TestCase):
def test_authorized(self):
headers = {
'HTTP_AUTHORIZATION': 'Basic ' +
base64.b64encode(b'username:password').decode("ascii")
}
response = self.client.get('/', **headers)
self.assertEqual(response.status_code, 200)
Assuming I have a login form, I use the following technique to login through the test framework:
client = Client()
client.post('/login/', {'username': 'john.smith', 'password': 'secret'})
I then carry the client around in my other tests since it's already authenticated. What is your question to this post?
(python3) I'm using this in a test:
credentials_string = '%s:%s' % ('invalid', 'invalid')
credentials = base64.b64encode(credentials_string.encode())
self.client.defaults['HTTP_AUTHORIZATION'] = 'Basic ' + credentials.decode()
and the following in a view:
import base64
[...]
type, auth = request.META['HTTP_AUTHORIZATION'].split(' ', 1)
auth = base64.b64decode(auth.strip()).decode()
Another way to do it is to bypass the Django Client() and use Requests instead.
class MyTest(TestCase):
def setUp(self):
AUTH = requests.auth.HTTPBasicAuth("username", "password")
def some_test(self):
resp = requests.get(BASE_URL + 'endpoint/', auth=AUTH)
self.assertEqual(resp.status_code, 200)
I hope that somebody can help me.
I have to write a unit test with unittest of Python in a flask api. I have a login route that works perfectly fine when accessing it through the app with a React frontend but whenever I tried to post from the test, the request.authorization is None... It drives me crazy
I looked all over the internet and tried a lot of different approach but whatever I do, request.authorization is always None when doing a test
Testing :
import unittest
import base64
from backend.peace_api import app
class TestLogin(unittest.TestCase):
# Assert login() with correct authentication
def test_login(self):
with app.app_context():
tester = app.test_client(self)
auth = 'seo#hotmail.com:password'
authheader = base64.b64encode(bytes(auth, 'UTF-8'))
headers = {"HTTP_AUTHORIZATION": "Bearer " + str(authheader), "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"}
response = tester.post('/api/login/', headers=dict(headers))
print(response.json)
self.assertEqual(response.status_code, 200)
if __name__ == '__main__':
unittest.main()
Route :
import jwt
import datetime
from flask import Blueprint, request, jsonify
from backend.peace_api import database, secret_key
from backend.peace_api.flat.models.flat import Flat
login_blueprint = Blueprint("login", __name__)
#login_blueprint.route("/", methods=["POST"])
def login():
auth = request.authorization # This here is always None
print("Hello World")
print(request)
print(request.authorization)
if auth is None:
return jsonify({"success": False}, 401)
email = auth.username
password = auth.password
if email is None or email is None or password is None:
return jsonify({"success": False}, 500)
mongo_flat = database.flats.find_one({"email": email})
if mongo_flat is not None:
flat = Flat(
mongo_flat["_id"],
mongo_flat["name"],
mongo_flat["email"],
mongo_flat["password"],
mongo_flat["tasks"],
mongo_flat["members"],
)
if password == flat.password and email == flat.email:
token = jwt.encode(
{
"id": str(flat.id),
"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30),
},
secret_key,
)
return jsonify({"token": token.decode("UTF-8")})
else:
return jsonify({"success": False}, 401)
else:
return jsonify({"success": False}, 401)
Printed message :
Testing started at 19:15 ...
Launching unittests with arguments python -m unittest test_login.TestLogin in [...]\tests
Hello World
<Request 'http://localhost/api/login/' [POST]>
None
Ran 1 test in 0.017s
OK
[{'success': False}, 401]
I have honestly no clue what I should do... Thanks for the help
So there are a few issues with your setup which are resulting in the header not being sent or being sent but being malformed.
The name of the header is "Authorization", not "HTTP_AUTHORIZATION".
The credentials value for the Authorization header needs to be base64 encoded per the spec.
The default authorization middleware for Werkzeug only supports Basic auth, so your Bearer token will not work unless you're using an extension that adds Bearer support to Werkzeug (without knowing more about your setup it's hard to know what's going on there).
Here's a very simplified Flask App that demonstrates a working test client with a functioning Authorization header:
import flask
import base64
app = flask.Flask("app")
#app.route("/")
def test():
print(flask.request.authorization)
return "Worked"
with app.test_client() as c:
c.get("/", headers={"Authorization": "Basic {}".format(base64.b64encode(b"useo#hotmail.com:pass").decode("utf8"))})
Which prints:
{'password': 'pass', 'username': 'seo#hotmail.com'}
<Response streamed [200 OK]>
A similar question was asked here:
Flask werkzeug request.authorization is none but Authorization headers present
The task is to make a HTTP post request to url sending a json string data over, url is protected by HTTP Basic Authentication, I need to provide an Authorization: field in header, and emailAdd is the userid of Basic Authentication, password is generated by TOTP, where digits is 10-digit, time step is 30s, T0 is 0, hash function uses sha512, and shared secret is emailAdd + "ABCD".
My code is like:
import requests, base64, json
from passlib.totp import TOTP
from requests.auth import HTTPBasicAuth
totp = TOTP(key = base64.b32encode(shared_secret.encode()), digits = 10, alg = "sha512")
password = totp.generate().token
r = requests.Session()
#auth = base64.b64encode((''.join(userid) + ':' + ''.join(password)).encode())
r.headers.update({"Content-Type": "application/json"}) #this is task required
#r.headers.update({"Authorization": "Basic " + auth.decode()})
res = r.post(url, json = data, auth = (userid, password))
print(res.status_code)
print(res.content)
But I failed authentication, I think the password should be correct, and there is something wrong with post request, can anyone please tell me what the problem is? Also, I'm in a different time zone from the server, does it make any difference on TOTP generated password? And I'm running python on windows, if that matters.
Thanks
I'm setting up an API. Everything is working. I'm creating a token via OAuth2 python lib. I'm using TastyPie for my API.
The problem I'm facing.. is that there is no "create" token method in the AccessToken or Client models.
I can create an accessToken via the Django admin, and I can create one by doing a curl to:
myhost.com/oauth2/access_token (with all the info, secret key, client id, user & pass)
my goal is to upon successful registration of a user with my API, the oAuth client is automatically created (working) but I also want to generate the AccessToken. I cannot cURL my own server as its giving me a redirect/connection refused error so I want to do it programmatically in Python. Anyway to do this? Here's a snippet:
try:
user = User.objects.create_user(username, password)
user.save()
if user:
oauth_client = Client(user=user, name="api account", client_type=1, url="http://example.com")
oauth_client.save()
oauth_client_id = oauth_client.pk
oauth_client_secret = oauth_client.client_secret
if oauth_client:
print user
print oauth_client_id
print AccessToken.objects.all()
print '........'
token = AccessToken(user=user, client=oauth_client_id, scope=6)
token.save()
the last two lines above, while giving NO errors.. will NOT save a new AccessToken.
I'm using https://github.com/caffeinehit/django-oauth2-provider. I managed to create access token and refresh token by using models. I might be bypassing grant flow.
I haven't used this code in production but in development server i can perform API calls using the access token generated this way. I think it should be well tested before going to production.
#settings.py
OAUTH2_PROVIDER = {
# this is the list of available scopes
'SCOPES': {'read': 'Read scope'},
'ACCESS_TOKEN_EXPIRE_SECONDS': 36000,
}
#views.py
expire_seconds = oauth2_settings.user_settings['ACCESS_TOKEN_EXPIRE_SECONDS']
scopes = oauth2_settings.user_settings['SCOPES']
application = Application.objects.get(name="ApplicationName")
expires = datetime.now() + timedelta(seconds=expire_seconds)
access_token = AccessToken.objects.create(
user=user,
application=application,
token=random_token_generator(request),
expires=expires,
scope=scopes)
refresh_token = RefreshToken.objects.create(
user=user,
token=random_token_generator(request),
access_token=access_token,
application=application)
token = {
'access_token': access_token.token,
'token_type': 'Bearer',
'expires_in': expire_seconds,
'refresh_token': refresh_token.token,
'scope': scopes}
return Response(token, status=200)
This is how I was able to make it work:
from oauth2_provider.views import TokenView
import json
class SuperUserLogin(views.APIView):
permission_classes = (permissions.AllowAny, )
def post(self, request, **kwargs):
url, headers, body, status_code = TokenView().create_token_response(request)
return Response(json.loads(body), status=status_code)
This is how my request object looks like.
{
"username" : email,
"password" : password,
"client_id" : client_id,
"client_secret" : client_secret,
"grant_type" : password
}
This generates the desired access_token. I've verified the token creation on my database.
Based on what I see here https://github.com/caffeinehit/django-oauth2-provider/blob/master/provider/oauth2/views.py#L93 token creation is done this way
access_token = AccessToken.objects.create(
user=user,
client=client,
scope=scope
)
RefreshToken.objects.create(
user=user,
access_token=access_token,
client=client
)
I assume second token isn't so interesting for you so it's almost your code but with managers create() method. The only difference it makes is that manager calls save() with force_insert=True.
So try
token.save(force_insert = True)
I was able to get this to work in Django 1.6 using the following:
token = AccessToken.objects.create(user=user,
client=Client.objects.get(name=clientName),
scope=3)
Try below,
In [1]: import base64
In [2]: from django.conf import settings
In [3]: from django.http import HttpRequest
In [4]: from oauth2_provider.views import TokenView
In [5]: request = HttpRequest()
In [6]: key = base64.b64encode('{}:{}'.format(<CLIENT_ID>, <SECRET_KEY>))
In [7]: request.META = {'HTTP_AUTHORIZATION': 'Basic {}'.format(key)}
In [8]: request.POST = {'grant_type': 'password', 'username': '<USERNAME>', 'password': '<PASSWORD>'}
In [9]: tv = TokenView()
In [10]: url, headers, body, status = tv.create_token_response(request)
In [11]: body
Out [11]: '{"access_token": "IsttAWdao3JF6o3Fk9ktf2gRrUhuOZ", "token_type": "Bearer", "expires_in": 36000, "refresh_token": "y2KQyyliOuRIXf3q9PWzEUeBnx43nm", "scope": "read write"}'
Try this one, I just tested it moments earlier
>>> from oauth2_provider.models import Application
>>> app = Application.objects.create(name="Sample ORM", client_type="public", authorization_grant_type="password", user_id=1)
<Application: Sample ORM>
>>> import requests
>>> from requests.auth import HTTPBasicAuth
>>>
>>>
>>> data = "grant_type=password&username=admin&password=d3#narmada13"
>>> headers = {"content-type": "application/x-www-form-urlencoded"}
>>> r = requests.post(token_url, data=data, auth=(app.client_id, app.client_secret), headers=headers)
>>> print r.content
{"access_token": "5kEaw4O7SX6jO9nT0NdzLBpnq0CweE", "token_type": "Bearer", "expires_in": 7776000, "refresh_token": "ZQjxcuTSTmTaLSyfGNGqNvF3M6KzwZ", "scope": "read write"}
>>> import json
>>> json.loads(r.content)['access_token']
u'5kEaw4O7SX6jO9nT0NdzLBpnq0CweE'
>>>
I can set an http proxy using request.meta['proxy'], but how do I authenticate the proxy?
This does not work to specify user and pass:
request.meta['proxy'] = 'http://user:pass#123.456.2323:2222'
From looking around, I may have to send request.headers['Proxy-Authorization'], but what format do I send it in?
username and password are base64 encoded in the form "username:password"
import base64
# Set the location of the proxy
proxy_string = choice(self._get_proxies_from_file('proxies.txt')) # user:pass#ip:port
proxy_items = proxy_string.split('#')
request.meta['proxy'] = "http://%s" % proxy_items[1]
# setup basic authentication for the proxy
user_pass=base64.encodestring(proxy_items[0])
request.headers['Proxy-Authorization'] = 'Basic ' + user_pass
The w3lib module has a very convenient function for this usecase.
from w3lib.http import basic_auth_header
request.meta["proxy"] = "http://192.168.1.1:8050"
request.headers["Proxy-Authorization"] = basic_auth_header(proxy_user, proxy_pass)
This is also mentioned in a blog article of Zyte (the maintainers of scrapy)