How to send username:password to unittest's app.get() request? - python

This is part of my unit test in Flask-RESTful.
self.app = application.app.test_client()
rv = self.app.get('api/v1.0/{0}'.format(ios_sync_timestamp))
eq_(rv.status_code,200)
Within the command line I could use curl to send the username:password to the service:
curl -d username:password http://localhost:5000/api/v1.0/1234567
How do I achieve the same within my unit test's get() ?
Since my get/put/post require authentication otherwise the test would fail.

From RFC 1945, Hypertext Transfer Protocol -- HTTP/1.0
11.1 Basic Authentication Scheme
...
To receive authorization, the client sends the user-ID and password,
separated by a single colon (":") character, within a base64 [5]
encoded string in the credentials.string.
...
If the user agent wishes to send the user-ID "Aladdin" and password
open sesame", it would use the following header field:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
So if you really use http basic authentication you can solution like below, although your curl usage suggests some other authentication scheme.
from base64 import b64encode
headers = {
'Authorization': 'Basic ' + b64encode("{0}:{1}".format(username, password)).decode('utf-8')
}
rv = self.app.get('api/v1.0/{0}'.format(ios_sync_timestamp), headers=headers)

An alternative solution - All credit goes to Doug Black
def request(self, method, url, auth=None, **kwargs):
headers = kwargs.get('headers', {})
if auth:
headers['Authorization'] = 'Basic ' + base64.b64encode(auth[0] + ':' + auth[1])
kwargs['headers'] = headers
return self.app.open(url, method=method, **kwargs)
and then use this method in your tests:
resp = self.request('GET', 'api/v1.0/{0}'.format(ios_sync_timestamp), auth=(username, password))

For Python 3, try the following example:
from base64 import b64encode
headers = {
'Authorization': 'Basic %s' % b64encode(b"username:password").decode("ascii")
}
self.app.get("foo/", headers=headers)
If you'd like to use dynamic variables for username and password, then try something like:
'Basic %s' % b64encode(bytes(username + ':' + password, "utf-8")).decode("ascii")
See also: Python, HTTPS GET with basic authentication

Related

API misinterpreting a password with special characters

I use a python code to connect to an API to retrieve information in json format. A password is used for the authentication to the API. The authentication was working fine so far, but since I changed my password into one with special characters, things went wrong. i guess the password is not well handled either by the API or the code I use.
This is the piece of code that seems to cause the errors:
# Create connection and request header.
# This class does not perform any verification of the server`s certificate.
conn = HTTPSConnection(ADDRESS)
auth_header = 'Basic %s' % (':'.join([USER_NAME, PASSWORD]).encode('Base64').strip('\r\n'))
request_header = {'Authorization':auth_header.decode(),
'Content-Type': content_type}
The error I get after executing the code is the following:
response = self.perform_request(log, 'GET', 'network', params=query)
File "/usr/local/lib/python2.7/query_code.py", line 103, in perform_request
auth_header = 'Basic %s' % str(":".join([USER_NAME, PASSWORD]).encode('Base64').strip('\r\n'))
TypeError: sequence item 1: expected string or Unicode, NoneType found
I should have had a successful connection to the API with code 200 and results returned in json format.
I have difficulties spotting the origin of all this, and would like to have your opinion on what I think it is, and how to solve it.
Try this instead:
def basic_token(key, secret):
return base64.b64encode(bytes(f'{key}:{secret}', 'utf-8')).decode('utf-8')
auth_header = f'basic {basic_token(USERNAME, PASSWORD)}'
If you are using a python version < 3.6, you will need to use .format instead of f-strings:
def basic_token(key, secret):
return base64.b64encode(bytes('{}:{}'.format(key, secret), 'utf-8')).decode('utf-8')
auth_header = 'basic {}'.format(basic_token(USERNAME, PASSWORD))

Base64 Encoding issue - Python vs Powershell

I am trying to encode an auth token and pass it to a REST API, this works fine with powershell but applying the same method to python script throws 'unauthorized' exception.
I suspect there is a problem in encoded value. Not able to figure out the solution. Any ideas ?
The rest endpoint is IBM uDeploy.
Powershell
$tokenEncoded = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes( "PasswordIsAuthToken:{`"token`":`"$pass`"}" ))
$basicAuthValue = "Basic $tokenEncoded"
$headers = #{}
$headers.Add("Authorization", $basicAuthValue)
$response = Invoke-RestMethod -Method Put -Headers $headers -Uri $requestUri -Body $jsonRequest
Python
epass = base64.b64encode("PasswordIsAuthToken:{\"token\":\"$password\"}")
print 'base64 encoded: ' + epass
opener = urllib2.build_opener(urllib2.HTTPHandler)
req = urllib2.Request(reqUrl,json.dumps(json_data))
req.add_header('Authorization', 'Basic '+epass)
req.get_method = lambda: 'PUT'
resp = opener.open(req)
You are sending the literal string $password as the token, not the contents of a variable named password.
You only need to include the PasswordIsAuthToken and your token in a Basic Auth HTTP header (PasswordIsAuthToken forms the username, and token the password):
epass = base64.b64encode("PasswordIsAuthToken:" + password)
opener = urllib2.build_opener(urllib2.HTTPHandler)
req = urllib2.Request(reqUrl,json.dumps(json_data))
req.add_header('Authorization', 'Basic ' + epass)
If you reall need to wrap the token in a JSON-like structure then you'd need to use string formatting (which the Powershell code also does, but you omitted):
epass = base64.b64encode('PasswordIsAuthToken:{"token":"%s"}' % password)
or you could use the json module:
epass = base64.b64encode('PasswordIsAuthToken:' + json.dumps({'token': password}))
However, I believe the system should accept the token unwrapped.
I strongly recommend you use the requests library instead, which makes using a REST API vastly cleaner:
import requests
auth = ('PasswordIsAuthToken', password)
# or alternatively
# auth = ('PasswordIsAuthToken', '{"token":"%s"}' % password)
response = requests.put(json=json_data, auth=auth)
Note that there is no need to encode the JSON body yourself, nor do you have to encode the Basic Auth header.

Exception Value: HTTP Error 400: Bad Request after a Python/urllib2 request to PayPal Sandbox Site

I'm trying to integrate PayPal REST API in my website. As a first step, I'm trying to translate cURL commands into Python and I'm getting an Exception Value: HTTP Error 400.
The code I'm using (is based on https://stackoverflow.com/a/2003832/2675537):
def basic_authorization(user, password):
s = user + ":" + password
return "Basic " + s.encode("base64").rstrip()
req = urllib2.Request("https://api.sandbox.paypal.com/v1/oauth2/token",
headers = {
"Authorization": basic_authorization("EBWKjlELKMYqRNQ6sYvFo64FtaRLRR5BdHEESmha49TM", "EO422dn3gQLgDbuwqTjzrFgFtaRLRR5BdHEESmha49TM"),
"Accept": "application/json",
"Accept": "*/*",
"User-Agent": "my-python-app/1",
},
data = '{"message":{"body":' + 'grant_type=client_credentials' + '}}' )
f = urllib2.urlopen(req)
return HttpResponse(f)
which is the equivalent (I guess) to:
curl https://api.sandbox.paypal.com/v1/oauth2/token \
-H "Accept: application/json" \
-u "EBWKjlELKMYqRNQ6sYvFo64FtaRLRR5BdHEESmha49TM:EO422dn3gQLgDbuwqTjzrFgFtaRLRR5BdHEESmha49TM" \
-d "grant_type=client_credentials"
And the traceback is here: (Edit: Broken Link)
According to PayPal I should get a response like this:
{"scope":"https://api.paypal.com/v1/payments/.* https://api.paypal.com/v1/vault/credit-card https://api.paypal.com/v1/vault/credit-card/.* https://api.paypal.com/v1/developer/.*","access_token":"OABI8rm75u.5EIuK7.JrI2sLhnv3rhDgLElKAwTfyys","token_type":"Bearer","app_id":"APP-2EJ531395M785864S","expires_in":28800}
Is there an error in my code? Is there a better way to do it?
First of all, I would suggest you not to post your keys in clear text in your code examples.
The error your getting "HTTP Error 400: Bad Request", is due to a badly formed request.
From the docs the format for a request is:
urllib2.Request(url[, data][, headers][, origin_req_host][, unverifiable])
data may be a string specifying additional data to send to the server,
or None if no such data is needed.
headers should be a dictionary, and will be treated as if add_header()
was called with each key and value as arguments.
So your data field is passing a dict instead of string, and it would be a lot more readable
if you separated the fields outside of the Request class. When you have multiple header
fields to fill in, I find it better to use the add_header method as shown below.
import urllib, urllib2
def basic_authorization(user, password):
s = user + ":" + password
return "Basic " + s.encode("base64").rstrip()
url = "https://api.sandbox.paypal.com/v1/oauth2/token"
params = { "grant_type": client_credentials}
data = urllib.urlencode(params)
req = urllib2.Request(url, data)
req.add_header("Authorization",basic_authorization("XXX"))
req.add_header("Accept", "application/json")
req.add_header("User-Agent", "my-python-app/1")
response = urllib2.urlopen(req)

Python requests library how to pass Authorization header with single token

I have a request URI and a token. If I use:
curl -s "<MY_URI>" -H "Authorization: TOK:<MY_TOKEN>"
etc., I get a 200 and view the corresponding JSON data.
So, I installed requests and when I attempt to access this resource I get a 403 probably because I do not know the correct syntax to pass that token. Can anyone help me figure it out?
This is what I have:
import sys,socket
import requests
r = requests.get('<MY_URI>','<MY_TOKEN>')
r. status_code
I already tried:
r = requests.get('<MY_URI>',auth=('<MY_TOKEN>'))
r = requests.get('<MY_URI>',auth=('TOK','<MY_TOKEN>'))
r = requests.get('<MY_URI>',headers=('Authorization: TOK:<MY_TOKEN>'))
But none of these work.
In python:
('<MY_TOKEN>')
is equivalent to
'<MY_TOKEN>'
And requests interprets
('TOK', '<MY_TOKEN>')
As you wanting requests to use Basic Authentication and craft an authorization header like so:
'VE9LOjxNWV9UT0tFTj4K'
Which is the base64 representation of 'TOK:<MY_TOKEN>'
To pass your own header you pass in a dictionary like so:
r = requests.get('<MY_URI>', headers={'Authorization': 'TOK:<MY_TOKEN>'})
I was looking for something similar and came across this. It looks like in the first option you mentioned
r = requests.get('<MY_URI>', auth=('<MY_TOKEN>'))
"auth" takes two parameters: username and password, so the actual statement should be
r=requests.get('<MY_URI>', auth=('<YOUR_USERNAME>', '<YOUR_PASSWORD>'))
In my case, there was no password, so I left the second parameter in auth field empty as shown below:
r=requests.get('<MY_URI', auth=('MY_USERNAME', ''))
Hope this helps somebody :)
This worked for me:
access_token = #yourAccessTokenHere#
result = requests.post(url,
headers={'Content-Type':'application/json',
'Authorization': 'Bearer {}'.format(access_token)})
You can also set headers for the entire session:
TOKEN = 'abcd0123'
HEADERS = {'Authorization': 'token {}'.format(TOKEN)}
with requests.Session() as s:
s.headers.update(HEADERS)
resp = s.get('http://example.com/')
I found it here, it's working for me with Linkedin:
https://auth0.com/docs/flows/guides/auth-code/call-api-auth-code
The code I used with Linkedin login is:
ref = 'https://api.linkedin.com/v2/me'
headers = {"content-type": "application/json; charset=UTF-8",'Authorization':'Bearer {}'.format(access_token)}
Linkedin_user_info = requests.get(ref1, headers=headers).json()
Requests natively supports basic auth only with user-pass params, not with tokens.
You could, if you wanted, add the following class to have requests support token based basic authentication:
import requests
from base64 import b64encode
class BasicAuthToken(requests.auth.AuthBase):
def __init__(self, token):
self.token = token
def __call__(self, r):
authstr = 'Basic ' + b64encode(('token:' + self.token).encode('utf-8')).decode('utf-8')
r.headers['Authorization'] = authstr
return r
Then, to use it run the following request :
r = requests.get(url, auth=BasicAuthToken(api_token))
An alternative would be to formulate a custom header instead, just as was suggested by other users here.
You can try something like this
r = requests.get(ENDPOINT, params=params, headers={'Authorization': 'Basic %s' % API_KEY})
This worked for me:
r = requests.get('http://127.0.0.1:8000/api/ray/musics/', headers={'Authorization': 'Token 22ec0cc4207ebead1f51dea06ff149342082b190'})
My code uses user generated token.
You have a request needing an authorization maybe you have a result 401.
Suppose your request is like this :
REQ ='https://api.asite.com/something/else/else'
You have your token :
TOKEN = 'fliuzabuvdgfnsuczkncsq12454632'
build your header like this :
HEADER = {'Authorization': f'{TOKEN}'}
and use it like this :
req.get(REQ, headers=HEADER)
display your result like this :
req.get(COACH, headers=HEADER).json()

Python urllib2 Basic Auth Problem

Update: based on Lee's comment I decided to condense my code to a really simple script and run it from the command line:
import urllib2
import sys
username = sys.argv[1]
password = sys.argv[2]
url = sys.argv[3]
print("calling %s with %s:%s\n" % (url, username, password))
passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
passman.add_password(None, url, username, password)
urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(passman)))
req = urllib2.Request(url)
f = urllib2.urlopen(req)
data = f.read()
print(data)
Unfortunately it still won't generate the Authorization header (per Wireshark) :(
I'm having a problem sending basic AUTH over urllib2. I took a look at this article, and followed the example. My code:
passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
passman.add_password(None, "api.foursquare.com", username, password)
urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(passman)))
req = urllib2.Request("http://api.foursquare.com/v1/user")
f = urllib2.urlopen(req)
data = f.read()
I'm seeing the following on the Wire via wireshark:
GET /v1/user HTTP/1.1
Host: api.foursquare.com
Connection: close
Accept-Encoding: gzip
User-Agent: Python-urllib/2.5
You can see the Authorization is not sent, vs. when I send a request via curl: curl -u user:password http://api.foursquare.com/v1/user
GET /v1/user HTTP/1.1
Authorization: Basic =SNIP=
User-Agent: curl/7.19.4 (universal-apple-darwin10.0) libcurl/7.19.4 OpenSSL/0.9.8k zlib/1.2.3
Host: api.foursquare.com
Accept: */*
For some reason my code seems to not send the authentication - anyone see what I'm missing?
thanks
-simon
The problem could be that the Python libraries, per HTTP-Standard, first send an unauthenticated request, and then only if it's answered with a 401 retry, are the correct credentials sent. If the Foursquare servers don't do "totally standard authentication" then the libraries won't work.
Try using headers to do authentication:
import urllib2, base64
request = urllib2.Request("http://api.foursquare.com/v1/user")
base64string = base64.b64encode('%s:%s' % (username, password))
request.add_header("Authorization", "Basic %s" % base64string)
result = urllib2.urlopen(request)
Had the same problem as you and found the solution from this thread: http://forums.shopify.com/categories/9/posts/27662
(copy-paste/adapted from https://stackoverflow.com/a/24048772/1733117).
First you can subclass urllib2.BaseHandler or urllib2.HTTPBasicAuthHandler, and implement http_request so that each request has the appropriate Authorization header.
import urllib2
import base64
class PreemptiveBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
'''Preemptive basic auth.
Instead of waiting for a 403 to then retry with the credentials,
send the credentials if the url is handled by the password manager.
Note: please use realm=None when calling add_password.'''
def http_request(self, req):
url = req.get_full_url()
realm = None
# this is very similar to the code from retry_http_basic_auth()
# but returns a request object.
user, pw = self.passwd.find_user_password(realm, url)
if pw:
raw = "%s:%s" % (user, pw)
auth = 'Basic %s' % base64.b64encode(raw).strip()
req.add_unredirected_header(self.auth_header, auth)
return req
https_request = http_request
Then if you are lazy like me, install the handler globally
api_url = "http://api.foursquare.com/"
api_username = "johndoe"
api_password = "some-cryptic-value"
auth_handler = PreemptiveBasicAuthHandler()
auth_handler.add_password(
realm=None, # default realm.
uri=api_url,
user=api_username,
passwd=api_password)
opener = urllib2.build_opener(auth_handler)
urllib2.install_opener(opener)
Here's what I'm using to deal with a similar problem I encountered while trying to access MailChimp's API. This does the same thing, just formatted nicer.
import urllib2
import base64
chimpConfig = {
"headers" : {
"Content-Type": "application/json",
"Authorization": "Basic " + base64.encodestring("hayden:MYSECRETAPIKEY").replace('\n', '')
},
"url": 'https://us12.api.mailchimp.com/3.0/'}
#perform authentication
datas = None
request = urllib2.Request(chimpConfig["url"], datas, chimpConfig["headers"])
result = urllib2.urlopen(request)
The second parameter must be a URI, not a domain name. i.e.
passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
passman.add_password(None, "http://api.foursquare.com/", username, password)
I would suggest that the current solution is to use my package urllib2_prior_auth which solves this pretty nicely (I work on inclusion to the standard lib.

Categories

Resources