Django tests - simulate that user is logged in - python

In my tests I have a following bit of code:
def setUp(self):
self.client.defaults['HTTP_AUTHORIZATION'] = 'Basic ' + base64.b64encode(
'{username}:{password}'.format(**self.login_data)
)
def test_list_view(self):
response = self.client.get(reverse('data_list'))
self.assertEqual(response.status_code, 200)
My problem is that this check has to go through basic http authentication which uses ldap and it's pretty slow.
Is there a way I can simulate that user is logged in?

You should create user because tests create test database (not your) everytime.
User.objects.create_user(username=<client_username>, password=<client_password>)
Now create Client and login
self.c = django.test.client.Client()
self.c.login(username=<client_username>, password=<client_password>)

You can override request headers for every client request like this example:
def test_report_wrong_password(self):
headers = dict()
headers['HTTP_AUTHORIZATION'] = 'Basic ' + base64.b64encode('user_name:password')
response = self.client.post(
'/report/',
content_type='application/json',
data=json.dumps(JSON_DATA),
**headers)
self.assertEqual(response.status_code, 401)

Related

Authenticating FastAPI session via requests

I am following the fastapi docs to implement an user authentication system. Here is a minimal example of app.py:
# import lines and utilities omitted
#app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
#app.get("/user/me", response_model=UserRead)
def read_user(*, session=Depends(get_session), current_user=Depends(get_current_user)):
user = session.get(User, current_user.username)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
When the user calls /users/me, it returns the current user. This works completely well as described in the tutorial in the SwaggerUI. But, I am not able to perform the same authorization and operation via python requests. Here is a following python code which I used:
import requests
backend_url = "http://localhost:8000/"
login_data = {'username':'user1', 'password':'secret1'}
s = requests.Session()
s.post(backend_url + "token", login_data) # response 200
s.get(backend_url + "user/me") # response 401
I am looking for some ways by which I can reuse the access_token returned by fastapi.
I found the answer in docs of fastapi itself:
import requests
backend_url = "http://localhost:8000/"
login_data = {'username':'user1', 'password':'secret1'}
session = requests.Session()
response = session.post(backend_url + "token", login_data)
response = json.loads(response.content.decode('utf-8'))
session.headers.update({"Authorization": 'Bearer ' + response['access_token']})
session.get(backend_url + "user/me")
You are not using the session variable s = requests.Session() to send HTTP requests.
So the post and get methods are getting sent independently from each other.
Try using
s = requests.Session()
s.post(backend_url + "token", login_data) # use the session post method
s.get(backend_url + "user/me") # use the session get method

How to test authenticated POST request with Pytest in Django

I want to test an authenticated post request on an API using Pytest. This is what I am doing so far:
def test_auth_user_can_create(self, client):
url = api_reverse('crud-simulation_api')
data = {
"project": "testproject",
....
}
response = client.post(url, json=data)
assert response.status_code == 200
This doesn't work because it gives me back a 401 (Unauthorized) instead of a 200. That makes sense since the fixture is a client and not an admin client.
Yet if I pass in admin_client instead of client it gives me a Bad Request. The data that I send should be fine though.
I also tried to pass in the headers like so (since I use JWT authorization):
token = "bigassstringwhichismytoken"
headers = {
"Authorization": "JWT " + token
}
Finally I tried to log in before which gives me a 403 (Forbidden):
def test_auth_user_can_create_simulation_api(self, client, django_user_model):
username = "Jack"
password = "password"
django_user_model.objects.create_user(username=username, password=password)
client.login(username=username, password=password)
url = api_reverse('crud-simulation_api')
data = {
"project": "testproject",
...
}
response = client.post(url, json=data)
assert response.status_code == 200
If someone could point me into the right direction that would be fantastic! Thanks a lot in advance
To provide headers for client.{request} pass them individually as keyword agruments:
client.post(url, data, HTTP_FIRST_HEADER='...', HTTP_SECOND_HEADER='...')
Although you're unlikely to collide with any reserved parameter names in post call chain, better collect all headers you need in a dictionary:
headers = {
'HTTP_FIRST_HEADER': '...',
'HTTP_SECOND_HEADER': '...',
}
And pass them to request as arbitrary number of keyword arguments:
client.post(url, data, **headers)
In this case ** arguments are treated as extra information and are automatically added as headers.
You can hit the login url with username and password and get the token.
creade a header dictionary like headers = {'Authorization': 'JWT <token>'}
and use the header when using post.
client.post(url, json=data, headers=headers)
I would suggest installing the pytest-django package. Based on its docs, the easiest answer would be just using the admin_client fixture. As admin_client has the type of django.test.Client, it can be used for both get and post requests.
def test_sth_with_auth(admin_client):
response = admin_client.get('/private')
assert response.status_code == 200
Also if you want to use a specific user, you can try sth like this:
#pytest.fixture
def my_user(django_user_model):
return django_user_model.objects.create_user(username=username, password=password)
#pytest.fixture
def logged_in_client(client, my_user):
return client.force_login(my_user)
def test_sth_with_auth(logged_in_client):
response = logged_in_client.get('/private')
assert response.status_code == 200
this part of the doc can be helpful to write your desired logged_in_client().

OAuth and redirect_uri in offline Python script

I'm currently trying to write a Python script that will use Deviantart's API to automatically shuffle my favourites. To do that I need to first log in in my script. Deviantart uses OAuth2 authentication, which requires a redirect_uri, which as I understand it is supposed to be the server where my application is running.
However I'm running the script locally on my computer (not on a server) and just sending http requests via Python's Requests library. How do I then authenticate, when the OAuth procedure sends the code required for the authentication token as a parameter of a GET call to the redirect_uri, which points to nowhere in particular for me? Is there no way to authenticate without running a server?
EDIT
My problem is still that I'm running a simple offline script, and I'm not sure how to authenticate from it.
This is my authentication code so far:
import binascii, os, requests
def auth():
request = 'https://www.deviantart.com/oauth2/authorize'
state = binascii.hexlify(os.urandom(160))
params = {
'response_type': 'token',
'client_id': 1,
'redirect_uri': 'https://localhost:8080',
'state': state,
'scope': 'user'
}
r = requests.get(request, params)
print(r)
The printed response is simply a 200 HTTP code, rather than an access token (obviously, since the username and password haven't been entered anywhere). The request is sent to DA's authorisation page, but since the page itself doesn't actually open in my script, I can't enter my username and password to log in anywhere. And I can't directly send the username and password in the GET request to authenticate that way either (again obviously, since it would be a terrible idea to send the password like that).
Preferably I'd like a way to simply have the user (me) prompted for the username and password in the console that the script is running in and then have the script continue executing after the user has successfully logged in.
Alternatively, if the above is not possible, the script should open the authorisation webpage in a browser, and then continue execution once the user logs in.
How would I go about realising either of these two solutions in Python?
If your application is offline, you cannot use the Authorization Code nor the Implicit grant type: both flows require a redirection URI.
As your python script cannot be reached from the Internet and because Deviantart does not allow the use of another grant type (except Client Credentials, but not relevant in you case), then you won't be able to issue any access token.
Your application must be accessible from the Internet.
You are supposed to get authorization token using received code. This token will be used to access DeviantArt afterwards.
Refer to https://www.deviantart.com/developers/authentication (section "Using The Authorization Code Grant").
Per request, I'm updating this question with the code I ended up using for my script's authentication in the hope that it helps somebody.
import webbrowser
import requests
import urllib.parse
import binascii
import os
import time
from http.server import HTTPServer, BaseHTTPRequestHandler
AUTH = 'https://www.deviantart.com/oauth2/authorize'
TOKEN = 'https://www.deviantart.com/oauth2/token'
code = ''
state = binascii.hexlify(os.urandom(20)).decode('utf-8')
class Communicator:
def __init__(self):
self.client_id = '<insert-actual-id>' # You get these two from the DA developer API page
self.client_secret = '<insert-actual-secret>' # but it's safer if you store them in a separate file
self.server, self.port = 'localhost', 8080
self._redirect_uri = f'http://{self.server}:{self.port}'
self._last_request_time = 0
def auth(self, *args):
scope = ' '.join(args)
params = {
'response_type': 'code',
'client_id': self.client_id,
'redirect_uri': self._redirect_uri,
'scope': scope,
'state': state
}
request = requests.Request('GET', AUTH, params).prepare()
request.prepare_url(AUTH, params)
webbrowser.open(request.url)
server = HTTPServer((self.server, self.port), RequestHandler)
server.handle_request()
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': self._redirect_uri
}
self._get_token(params)
def _get_token(self, params):
r = requests.get(TOKEN, params).json()
self.token = r['access_token']
self.refresh_token = r['refresh_token']
def _refresh_token(self):
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': 'refresh_token',
'refresh_token': self.refresh_token
}
self._get_token(params)
def _request(self, func, url, params, sleep=5, cooldown=600):
t = time.time()
if t - self._last_request_time < sleep:
time.sleep(sleep - t + self._last_request_time)
self._last_request_time = t
max_sleep = 16 * sleep
params['access_token'] = self.token
while True:
try:
r = func(url, params).json()
if 'error_code' in r and r['error_code'] == 429:
sleep *= 2
time.sleep(sleep)
if sleep > max_sleep:
raise ConnectionError("Request timed out - server is busy.")
elif 'error' in r and r['error'] == 'user_api_threshold':
raise ConnectionError("Too many requests")
elif 'error' in r and r['error'] == 'invalid_token':
print("Refreshing token.")
self._refresh_token()
params['access_token'] = self.token
else:
return r
except ConnectionError:
print(f"Request limit reached - waiting {cooldown // 60} minutes before retrying...")
time.sleep(cooldown)
def get(self, url, params):
return self._request(requests.get, url, params)
def post(self, url, params):
return self._request(requests.post, url, params)
class RequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
global code
self.close_connection = True
query = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
if not query['state'] or query['state'][0] != state:
raise RuntimeError("state argument missing or invalid")
code = query['code']
BROWSE = 'browse'
BROWSE_MORE_LIKE_THIS = 'browse.mlt'
COLLECTION = 'collection'
COMMENT = 'comment.post'
FEED = 'feed'
GALLERY = 'gallery'
MESSAGE = 'message'
NOTE = 'note'
PUBLISH = 'publish'
STASH = 'stash'
USER = 'user'
USER_MANAGE = 'user.manage'
if __name__ == '__main__':
com = Communicator()
com.auth(BROWSE, COLLECTION) # request specific permissions
... # do stuff with com.get() and com.post() requests

Python requests parameters not going through

I'm trying to make a post request to Quizlet following their OAuth flow from these instructions https://quizlet.com/api/2.0/docs/authorization-code-flow. I'm running into a problem where on Step 2, I have to make a post request with a token I generated from their server, but I'm not having success passing in the token to the url. I know it was generated correctly, but I'm having trouble passing it in and not getting a 400 response.
More directly, my question is, is there another way of including the grant_type and code parameters that I'm trying to pass in through the url in the post request such as passing them in through the header of the post request? I've looked at the documentation for requests but I've had no luck.
#app.route('/')
#app.route('/index')
def index():
code = request.args.get('code')
state = request.args.get('state')
print("code is " + code)
r = requests.post("https://api.quizlet.com/oauth/token?grant_type=authorization_code&code=" + code)
return render_template('index.html')
You must specify the required headers Authorization, Content-Type.
import requests
from requests.auth import _basic_auth_str
client_id = 'YOUR CLIENT ID'
secret = 'YOUR CLIENT SECRET'
code = 'CODE FROM STEP 1'
headers = {
'Authorization': _basic_auth_str(client_id, secret),
'Content-Type': 'application/x-www-form-urlencoded'
}
r = requests.post('https://api.quizlet.com/oauth/token?grant_type=authorization_code&code={0}'.format(
code), headers=headers)
print r.status_code
print r.content

python django 1.4 how to set two WWW-Authenticate http headers

Im developing small intranet web service. I want authenticate users over kerberos in MS AD or with basic auth. For that reason i need to set two 'WWW-Authenticate' http headers in response 401. How can i do it with Django ?
Should be something like this:
Client: GET www/index.html
Server: HTTP/1.1 401 Unauthorized
WWW-Authenticate: Negotiate
WWW-Authenticate: Basic realm="corp site"
This code overwrite header
def auth(request):
response = None
auth = request.META.get('HTTP_AUTHORIZATION')
if not auth:
response = HttpResponse(status = 401)
response['WWW-Authenticate'] = 'Negotiate'
response['WWW-Authenticate'] = 'Basic realm=" trolls place basic auth"'
elif auth.startswith('Negotiate YII'):
...
return response
I guess a middleware would be best for this task, but in case you have something else in mind, here is the middleware code adjusted to work with your view(which you can very easily still turn into a middleware if you decide to do so):
from django.conf import settings
from django.http import HttpResponse
def basic_challenge(realm=None):
if realm is None:
realm = getattr(settings,
'WWW_AUTHENTICATION_REALM',
'Restricted Access')
response = HttpResponse('Authorization Required',
mimetype="text/plain")
response['WWW-Authenticate'] = 'Basic realm="%s"' % (realm)
response.status_code = 401
return response
def basic_authenticate(authentication):
(authmeth, auth) = authentication.split(' ', 1)
if 'basic' != authmeth.lower():
return None
auth = auth.strip().decode('base64')
username, password = auth.split(':', 1)
AUTHENTICATION_USERNAME = getattr(settings,
'BASIC_WWW_AUTHENTICATION_USERNAME')
AUTHENTICATION_PASSWORD = getattr(settings,
'BASIC_WWW_AUTHENTICATION_PASSWORD')
return (username == AUTHENTICATION_USERNAME and
password == AUTHENTICATION_PASSWORD)
def auth_view(request):
auth = request.META.get('HTTP_AUTHORIZATION', '')
if auth.startswith('Negotiate YII'):
pass
elif auth:
if basic_authenticate(auth):
#successfully authenticated
pass
else:
return basic_challenge()
# nothing matched, still return basic challange
return basic_challenge()

Categories

Resources