HTTP GET request with django-pytest client [duplicate] - python

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

How to decline a pull request on Bitbucket from Python?

How do you use Bitbucket's 2.0 API to decline a pull request via Python?
According to their documentaion, it should be something like:
import requests
kwargs = {
'username': MY_BITBUCKET_ACCOUNT,
'repo_slug': MY_BITBUCKET_REPO,
'pull_request_id': pull_request_id
}
url = 'https://api.bitbucket.org/2.0/repositories/{username}/{repo_slug}/pullrequests/{pull_request_id}/decline'.format(**kwargs)
headers = {'Content-Type': 'application/json'}
response = requests.post(url, auth=(USERNAME, PASSWORD), headers=headers)
However, this fails with response.text simply saying "Bad Request".
This similar code works for me with their other API endpoints, so I'm not sure why the decline method is failing.
What am I doing wrong?
You have to authenticate with Oath. I wrote a wrapper for making these requests. Here is a simple example that works. The only thing I couldn't figure out was how to add a reason it was declined. I ended up making a request before I declined the PR that added a comment on why it was declined.
import os
from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session
class Bitbucket(object):
def __init__(self, client_id, client_secret, workplace, repo_slug):
self.workplace = workplace # username or company username
self.repo_slug = repo_slug
self.token_url = 'https://bitbucket.org/site/oauth2/access_token'
self.api_url = 'https://api.bitbucket.org/2.0/'
self.max_pages = 10
self.client = BackendApplicationClient(client_id=client_id)
self.oauth = OAuth2Session(client=self.client)
self.oauth.fetch_token(
token_url=self.token_url,
client_id=client_id,
client_secret=client_secret
)
def get_api_url(self, endpoint):
return f'{self.api_url}repositories/{self.workplace}/{self.repo_slug}/{endpoint}'
bitbucket = Bitbucket(os.environ['BITBUCKET_KEY'], os.environ['BITBUCKET_SECRET'], workplace='foo', repo_slug='bar')
pr_id = 1234
resp = bitbucket.oauth.post(f"{bitbucket.get_api_url('pullrequests')}/{pr_id}/decline")
if resp.status_code == 200:
print('Declined')
else:
print('Someting went wrong.')

flask: When unittesting, request.authorization is always None

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

How to generate an AccessToken programmatically in Django?

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'
>>>

Authenticate Scrapy HTTP Proxy

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)

Using Basic HTTP access authentication in Django testing framework

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)

Categories