check whether a session cookie is still alive (Python) - python

Please take a look at the following python code snippet :
import cookielib, urllib, urllib2
def login(username, password):
cookie_jar = cookielib.LWPCookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie_jar))
login = urllib.urlencode({'username': username, 'password': password})
try:
login_data = opener.open("http://www.example.com/login.php", login).read()
except IOError:
return 'Network Error'
# on successful login the I'm storing the 'SESSION COOKIE'
# that the site sends on a local file called cookie.txt
cookie_jar.save('./cookie.txt', True, True)
return login_data
# this method is called after quite sometime
# from calling the above method "login"
def re_accessing_the _site():
cookie_jar = cookielib.LWPCookieJar()
# Here I'm re-loading the saved cookie
# from the file to the cookie jar
cookie_jar.revert('./cookie.txt', True, True)
# there's only 1 cookie in the cookie jar
for Cookie in cookie_jar:
print 'Expires : ', Cookie.expires ## prints None
print 'Discard : ', Cookie.discard ## prints True , means that the cookie is a
## session cookie
print 'Is Expired : ', Cookie.is_expired() ## prints False
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie_jar))
try:
data = opener.open("http://www.example.com/send.php")
# Sometimes the opening fails as the cookie has expired
# & sometimes it doesn't. Here I want a way to determine
# whether the (session) cookie is still alive
except IOError:
return False
return True
First I'm calling the method login & saves the retrieved cookie (which is a session cookie)to a local file called cookie.txt.
Next after quite sometime (15-20 mins) I'm calling the other method re_accessing_the _site . This time I'm also re loading the previously saved cookie into the cookie jar.
Sometimes it's working fine but sometimes it's blocking me to access (as the session cookie has expired) . So all I need is a way to check whether the cookie is still alive during the call ...

Related

How to call a variable and prevent to run another module?

I am kind of new in python, trying to develop an user interaction with an API.
In this API I have to authenticate first and then with a token received I can do multiple things,
I have a module authfile.py that returns a cookie which is in a variable
url = "https://172.16.1.77:8089/api"
headers = {
'Content-Type': 'application/json;charset=UTF-8',
'Connection': 'close',
}
def auth(self, url, headers):
data = '{"request": {"action":"challenge","user":"apiuser","version":"1.0"}}'
# First send the challenge.
response = requests.post(url, headers=headers, data=data, verify=False)
# Get the challenge into variable.
a = response.json()
b = a['response']
c = b['challenge']
print("This is the challenge number: " + c)
# Getting the challenge with the Password with MD5
e = input("Please enter the Password\n")
user = c+e
h = hashlib.md5(user.encode())
md = h.hexdigest()
print("This is the Token: " + md)
datas = '{"request":{"action":"login", "token":"' + str(md) + '", "url":"' + str(url) + '" , "user": "cdrapi"}}'
#Send the token to get the Cookie
response2 = requests.post(url, headers=headers, data=datas, verify=False)
#Getting the cookie into a variable
global cookie
f = response2.json()
g = f['response']
cookie = g['cookie']
print("This is the Cookie: " +cookie)
return cookie
cookie = auth(url, headers)
So I have another file that I use to apply some changes, it will need to be in a different module since the applychanges() functions will be called many times
applychanes.py:
from authfile import url, headers, cookie
import requests
def changes():
while True:
q = (input("Are you sure you would like to apply changes?\nPlease enter only Y for Yes and N for No\n").lower())
if q.lower() not in ("y", "n"):
print("This is not a valid entry")
if q == "y":
postta = '{"request":{"action":"applyChanges", "cookie":"' + str(cookie) + '"}}'
format_to_json = requests.post(url, headers=headers, data=postta, verify=False)
jsonresponse = format_to_json.json()
# Get the response in a variable
presponse = jsonresponse['response']
# Get the status code into a variable
codestatus = jsonresponse['status']
# Print the reponse
print(presponse)
print(codestatus)
print("The changes has been applied")
break
if q == "n":
print("NO changes has been made")
break
else:
continue
return q
When I call the Cookie, the auth file is excecuted again changing the Cookie and the changes will not be applied since the new cookie means a new session**
How do I call a variable preventing to run the function?
On the Main program the auth MUST be excecuted before anything else.
when I call the cookie, the authfile is executed again
If I read you right here, you mean than when you do import authfile or from authfile import cookie, authfile gets evaluated again, and the cookie changes.
This is impossible, unless you are using reload (or a reload-like trick, like del sys.modules["authfile"]. Python will only ever evaluate an imported module once: after that it just binds a new name to it.* Thus if your debugging is premised on the idea that importing the same file multiple times is causing the file to be evaluated multiple times, it's based on a misconception, and something else is going wrong.
On the other hand, I could easily be misreading you here! Do clarify if I'm wrong; I'll check back on this tomorrow [and delete this sentence either way].
In any case, this is probably an instance of a bigger problem: modules are not really for state: that's more what classes are for. Modules just insulate namespaces, but that can have problems if you rely on module-level variables to hold state. So if you really do have a problem with state changing stored in a module, either make a class, to be the 'single source of truth' with respect to this state:
class Authentication:
def __init__(self, url, headers):
# put your auth code here
self._cookie = ...
#property
def cookie(self):
return self._cookie
With appropriate methods to refresh the cookie when needed, or possibly with a dict of cookies if you have multiple realms.
If you don't want to do that and you still think something else is modifying your cookie, replace all instances of cookie with a get_cookie() function defined as:
_cookie = auth(url, headers)
def get_cookie():
return _cookie
That will throw an error when somebody tries to write to your cookie.
Note that all this assumes that cookie is an immuteable type. A possible cause of your problem is cookie being e.g. a dict or list, and something else modifying that---which will modify the original object.
Lastly, if your problem is simply that you want to define cookie in authfile but only generate it later, wrap it in a function or method on a class (personally I would do this---classes are for state, modules for namespace), and call the method later. Something like:
class Authentication:
def __init__(self, url, headers):
self._url = url
self._headers = headers
#staticmethod
def authenticate(url, headers):
# your auth fn
#property
def cookie(self):
if not self._cookie:
self._cookie = self.authenticate(self._url, self._headers)
return self._cookie
auth = Authentication(url, headers)
This will generate the cookie the first time it's accessed. So import as:
from authfile import auth
...
with auth.cookie as cookie:
...
*Alright, I don't actually know what happens in the case of circular imports, whether Python 'evaluates' the same file multiple times before bailing. But it bails, so the question is moot.

Python 3 NameError in __init__, 'session' not defined

I am working on a small project that gets the following of a given user's Instagram. I have this working flawlessly as a script using a function, however I plan to make this into an actual program so I decided to write a class. I believe I am using "self" correctly in all the right places, but I am failing to see why I am getting this name error. Here is my code:
# Library imports
import requests
import json
import time
# Class decleration
class NodesCursor:
# Class variables
# Login url
LOGIN_URL = 'https://www.instagram.com/accounts/login/ajax/'
# Referer url
REFERER_URL = 'https://www.instagram.com/accounts/login/'
# User agent
USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1'
# Class constructor
def __init__(self, USERNAME, PASSWD):
# Login username
self.USERNAME = USERNAME
# Login password
self.PASSWD = PASSWD
# Creating a session
self.session = requests.Session()
# Get request to login url
self.req = session.get(LOGIN_URL)
# Setting user agent for session header
self.session.headers = {'user-agent': USER_AGENT}
# Setting referer url for session header
self.session.headers.update({'Referer': REFERER_URL})
# Updating session header with x-csrftoken cookie
self.session.headers.update({'x-csrftoken': self.req.cookies['csrftoken']})
# Login data for website
self.login_data = {'username': self.USERNAME, 'password': self.PASSWD}
# Login with a post requests
self.login = session.post(LOGIN_URL, data=self.login_data, allow_redirects=True)
# Updating the session with x-csrftoken cookie
self.session.headers.update({'x-csrftoken': self.login.cookies['csrftoken']})
# Function to parse following
def parse(self):
# An array of usernames
usernames = []
# Variable to handle continuous scrolling
has_next_page = True
# Variable to handle continuous scrolling
end_cursor = None
# Loop to handle the scrolling to get the needed data
while has_next_page == True:
# Sleep for 30 seconds to not get rate limited
#time.sleep(30)
# Query url
queryUrl = "https://www.instagram.com/graphql/query/"
# Parameters for the get request
payload = {"query_hash":"9335e35a1b280f082a47b98c5aa10fa4", "id":"8164444379","first":24, "after": end_cursor}
# Variable for GETting all of the user's following
following = self.session.get(queryUrl, params=payload).json()
# Parsing the node to check to see what has_next_page equals to
has_next_page = following['data']['user']['edge_follow']['page_info']['has_next_page']
# Parse all user followings until there are no more
if has_next_page == True or has_next_page == False:
# Parsing end cursor id
end_cursor = following['data']['user']['edge_follow']['page_info']['end_cursor']
# Sleep for 30 seconds to not get rate limited
time.sleep(30)
# Parsing to get to username node
userList = following['data']['user']['edge_follow']
# Loop to interate through all of the names
for eachName in userList['edges']:
# Add each name to the array
usernames.append(eachName['node']['username'])
# Print the array of usernames, along with the length
print(usernames)
print(len(usernames))
if __name__ == '__main__':
checkFollowing = NodesCursor('username', 'password')
checkFollowing().parse()
Error:
Traceback (most recent call last):
File "test.py", line 115, in <module>
turboOne = NodesCursor('moola.ig', 'yeet1234')
File "test.py", line 42, in __init__
self.req = session.get(LOGIN_URL)
NameError: name 'session' is not defined
Though as I stated earlier that I think I am using "self" correctly, it is possible that is where my error is coming from but I'm unsure. Any help is greatly appreciated.
You’re missing the self. when accessing session:
# Creating a session
self.session = requests.Session()
# Get request to login url
self.req = self.session.get(LOGIN_URL)
To fix to error with LOGIN_URL:
self.req = self.session.get(NodesCursor.LOGIN_URL)
Try replacing
self.req = session.get(LOGIN_URL)
With
self.req = self.session.get(LOGIN_URL)

How to set cookie in Python Flask?

In this way, I want to set my cookie. But it fails to set.
#app.route('/')
def index():
res = flask.make_response()
res.set_cookie("name", value="I am cookie")
When I print res it shows <Response 0 bytes [200 OK] But not set cookie
You have to return the response after setting the cookie.
#app.route('/')
def index():
resp = make_response(render_template(...))
resp.set_cookie('somecookiename', 'I am cookie')
return resp
This way a cookie will be generated in your browser, but you can get this cookie in the next request.
#app.route('/get-cookie/')
def get_cookie():
username = request.cookies.get('somecookiename')
The cookie you set will be visible if you either use other tools to see it (ex: Press F12 for Developer Tools in Firefox or Chrome) or use a piece of code of JavaScript inside the rendered response.
The cookies are set on the browser by either browser itself (JavaScript) or as a response from a server.
The difference is of great importance as even if the cookie is set by the server the cookie might not be set on the browser (ex: cases where cookie are completely disabled or discarded).
So even if the server might tell "I set up the cookie" - the cookie might not be present on the browser.
For the server to be sure that the cookie was set a subsequent request from the browser is needed (with the cookie present in the request's headers).
So even if the Flask's response (res variable) will mention that the cookie is set we can only be sure that it was set by the server but it will have no confirmation about it from the browser.
Advanced
Another aspect is about how Flask or any other API is creating the responses. As the payload (html/template code) and headers (cookie) are set at same time - the "code" inside the payload (html/template code) might not have access to the cookie.
So you might not be able to set a cookie and display it in the response.
An idea might be to (be able to) first set the cookies and THEN to render the context and the order of setup to be important - so that the html/template to be aware of already setup values. But even in this case is only the server's confirmation that it set up the cookie.
A solution
#app.route('/')
def index():
res = flask.make_response()
res.set_cookie("name", value="I am cookie")
# redirect to a page that display the cookie
resp.headers['location'] = url_for('showcookies')
return resp, 302
This response will set cookie in you browser
def func():
response = make_response( render_template() )
response.set_cookie( "name", "value" )
return response

How to request pages from website that uses OpenID?

This question has been asked here before. The accepted answer was probably obvious to both questioner and answerer---but not to me. I have commented on the above question to get more precisions, but there was no response. I also approached the meta Q&A for help on how to bring back questions from their grave, and got no answer either.
The answer to the here above question was:
From the client's perspective, an OpenID login is very similar to any other web-based login. There isn't a defined protocol for the client; it is an ordinary web session that varies based on your OpenID provider. For this reason, I doubt that any such libraries exist. You will probably have to code it yourself.
I know how to log onto a website with Python already, using the Urllib2 module. But that's not enough for me to guess how to authenticate to an OpenID.
I'm actually trying to get my StackOverflow inbox in json format, for which I need to be logged in.
Could someone provide a short intro or a link to a nice tutorial on how to do that?
Well I myself don't know much about OpenID but your post (and the bounty!!) got me interested.
This link tells the exact flow of OpenID authentication sequence (Atleast for v1.0. The new version is 2.0). From what I could make out, the steps would be something like
You fetch the login page of stackoverflow that will also provide an option to login using OpenID (As a form field).
You send ur openID which is actually a form of uri and NOT username/email(If it is Google profile it is your profile ID)
Stackoverflow will then connect to your ID provider (in this case google) and send you a redirect to google login page and another link to where you should redirect later (lets say a)
You can login to the google provided page conventionally (using POST method from Python)
Google provides a cryptographic token (Not pretty sure about this step) in return to your login request
You send the new request to a with this token.
Stackoverflow will contact google with this token. If authenticity established, it will return a session ID
Later requests to STackOverflow should have this session ID
No idea about logging out!!
This link tells about various responses in OpenID and what they mean. So maybe it will come in handy when your code your client.
Links from the wiki page OpenID Explained
EDIT: Using Tamper Data Add on for Firefox, the following sequence of events can be constructed.
User sends a request to the SO login page. On entering the openID in the form field the resulting page sends a 302 redirecting to a google page.
The redirect URL has a lot of OpenID parameters (which are for the google server). One of them is return_to=https://stackoverflow.com/users/authenticate/?s=some_value.
The user is presented with the google login page. On login there are a few 302's which redirect the user around in google realm.
Finally a 302 is received which redirects user to stackoverflow's page specified in 'return_to' earlier
During this entire series of operation a lot of cookie's have been generated which must be stored correctly
On accessing the SO page (which was 302'd by google), the SO server processes your request and in the response header sends a field "Set-Cookie" to set cookies named gauth and usr with a value along with another 302 to stackoverflow.com. This step completes your login
Your client simply stores the cookie usr
You are logged in as long as you remeber to send the Cookie usr with any request to SO.
You can now request your inbox just remeber to send the usr cookie with the request.
I suggest you start coding your python client and study the responses carefully. In most cases it will be a series of 302's with minimal user intervention (except for filling out your Google username and password and allowing the site page).
However to make it easier, you could just login to SO using your browser, copy all the cookie values and make a request using urllib2 with the cookie values set.
Of course in case you log out on the browser, you will have to login again and change the cookie value in your python program.
I know this is close to archeology, digging a post that's two years old, but I just wrote a new enhanced version of the code from the validated answer, so I thought it may be cool to share it here, as this question/answers has been a great help for me to implement that.
So, here's what's different:
it uses the new requests library that is an enhancement over urllib2 ;
it supports authenticating using google's and stackexchange's openid provider.
it is way shorter and simpler to read, though it has less printouts
here's the code:
#!/usr/bin/env python
import sys
import urllib
import requests
from BeautifulSoup import BeautifulSoup
def get_google_auth_session(username, password):
session = requests.Session()
google_accounts_url = 'http://accounts.google.com'
authentication_url = 'https://accounts.google.com/ServiceLoginAuth'
stack_overflow_url = 'http://stackoverflow.com/users/authenticate'
r = session.get(google_accounts_url)
dsh = BeautifulSoup(r.text).findAll(attrs={'name' : 'dsh'})[0].get('value').encode()
auto = r.headers['X-Auto-Login']
follow_up = urllib.unquote(urllib.unquote(auto)).split('continue=')[-1]
galx = r.cookies['GALX']
payload = {'continue' : follow_up,
'followup' : follow_up,
'dsh' : dsh,
'GALX' : galx,
'pstMsg' : 1,
'dnConn' : 'https://accounts.youtube.com',
'checkConnection' : '',
'checkedDomains' : '',
'timeStmp' : '',
'secTok' : '',
'Email' : username,
'Passwd' : password,
'signIn' : 'Sign in',
'PersistentCookie' : 'yes',
'rmShown' : 1}
r = session.post(authentication_url, data=payload)
if r.url != authentication_url: # XXX
print "Logged in"
else:
print "login failed"
sys.exit(1)
payload = {'oauth_version' : '',
'oauth_server' : '',
'openid_username' : '',
'openid_identifier' : ''}
r = session.post(stack_overflow_url, data=payload)
return session
def get_so_auth_session(email, password):
session = requests.Session()
r = session.get('http://stackoverflow.com/users/login')
fkey = BeautifulSoup(r.text).findAll(attrs={'name' : 'fkey'})[0]['value']
payload = {'openid_identifier': 'https://openid.stackexchange.com',
'openid_username': '',
'oauth_version': '',
'oauth_server': '',
'fkey': fkey,
}
r = session.post('http://stackoverflow.com/users/authenticate', allow_redirects=True, data=payload)
fkey = BeautifulSoup(r.text).findAll(attrs={'name' : 'fkey'})[0]['value']
session_name = BeautifulSoup(r.text).findAll(attrs={'name' : 'session'})[0]['value']
payload = {'email': email,
'password': password,
'fkey': fkey,
'session': session_name}
r = session.post('https://openid.stackexchange.com/account/login/submit', data=payload)
# check if url changed for error detection
error = BeautifulSoup(r.text).findAll(attrs={'class' : 'error'})
if len(error) != 0:
print "ERROR:", error[0].text
sys.exit(1)
return session
if __name__ == "__main__":
prov = raw_input('Choose your openid provider [1 for StackOverflow, 2 for Google]: ')
name = raw_input('Enter your OpenID address: ')
pswd = getpass('Enter your password: ')
if '1' in prov:
so = get_so_auth_session(name, pswd)
elif '2' in prov:
so = get_google_auth_session(name, pswd)
else:
print "Error no openid provider given"
r = so.get('http://stackoverflow.com/inbox/genuwine')
print r.json()
the code is also available as a github gist
HTH
This answer sums up what others have said below, especially RedBaron, plus adding a method I used to get to the StackOverflow Inbox using Google Accounts.
Using the Tamper Data developer tool of Firefox and logging on to StackOVerflow, one can see that OpenID works this way:
StackOverflow requests authentication from a given service (here Google), defined in the posted data;
Google Accounts takes over and checks for an already existing cookie as proof of authentication;
If no cookie is found, Google requests authentication and sets a cookie;
Once the cookie is set, StackOverflow acknowledges authentication of the user.
The above sums up the process, which in reality is more complicated, since many redirects and cookie exchanges occur indeed.
Because reproducing the same process programmatically proved somehow difficult (and that might just be my illiteracy), especially trying to hunt down the URLs to call with all locale specifics etc. I opted for loging on to Google Accounts first, getting a well deserved cookie and then login onto Stackoverflow, which would use the cookie for authentication.
This is done simply using the following Python modules: urllib, urllib2, cookielib and BeautifulSoup.
Here is the (simplified) code, it's not perfect, but it does the trick. The extended version can be found on Github.
#!/usr/bin/env python
import urllib
import urllib2
import cookielib
from BeautifulSoup import BeautifulSoup
from getpass import getpass
# Define URLs
google_accounts_url = 'http://accounts.google.com'
authentication_url = 'https://accounts.google.com/ServiceLoginAuth'
stack_overflow_url = 'https://stackoverflow.com/users/authenticate'
genuwine_url = 'https://stackoverflow.com/inbox/genuwine'
# Build opener
jar = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(jar))
def request_url(request):
'''
Requests given URL.
'''
try:
response = opener.open(request)
except:
raise
return response
def authenticate(username='', password=''):
'''
Authenticates to Google Accounts using user-provided username and password,
then authenticates to StackOverflow.
'''
# Build up headers
user_agent = 'Mozilla/5.0 (Ubuntu; X11; Linux i686; rv:8.0) Gecko/20100101 Firefox/8.0'
headers = {'User-Agent' : user_agent}
# Set Data to None
data = None
# Build up URL request with headers and data
request = urllib2.Request(google_accounts_url, data, headers)
response = request_url(request)
# Build up POST data for authentication
html = response.read()
dsh = BeautifulSoup(html).findAll(attrs={'name' : 'dsh'})[0].get('value').encode()
auto = response.headers.getheader('X-Auto-Login')
follow_up = urllib.unquote(urllib.unquote(auto)).split('continue=')[-1]
galx = jar._cookies['accounts.google.com']['/']['GALX'].value
values = {'continue' : follow_up,
'followup' : follow_up,
'dsh' : dsh,
'GALX' : galx,
'pstMsg' : 1,
'dnConn' : 'https://accounts.youtube.com',
'checkConnection' : '',
'checkedDomains' : '',
'timeStmp' : '',
'secTok' : '',
'Email' : username,
'Passwd' : password,
'signIn' : 'Sign in',
'PersistentCookie' : 'yes',
'rmShown' : 1}
data = urllib.urlencode(values)
# Build up URL for authentication
request = urllib2.Request(authentication_url, data, headers)
response = request_url(request)
# Check if logged in
if response.url != request._Request__original:
print '\n Logged in :)\n'
else:
print '\n Log in failed :(\n'
# Build OpenID Data
values = {'oauth_version' : '',
'oauth_server' : '',
'openid_username' : '',
'openid_identifier' : 'https://www.google.com/accounts/o8/id'}
data = urllib.urlencode(values)
# Build up URL for OpenID authetication
request = urllib2.Request(stack_overflow_url, data, headers)
response = request_url(request)
# Retrieve Genuwine
data = None
request = urllib2.Request(genuwine_url, data, headers)
response = request_url(request)
print response.read()
if __name__ == '__main__':
username = raw_input('Enter your Gmail address: ')
password = getpass('Enter your password: ')
authenticate(username, password)
You need to implement cookies on any "login" page, in Python you use cookiejar. For example:
jar = cookielib.CookieJar()
myopener = urllib2.build_opener(urllib2.HTTPCookieProcessor(jar))
#myopener now supports cookies.
....
I made a simple script that logins to stackoverflow.com using Mozilla Firefox cookies. It's not entirely automated, because you need to login manually, but it's all i managed to do.
Scipt is actual for latest versions of FF ( i'm using 8.0.1 ), but you need to get latest sqlite dll, because default one that comes with python 2.7 can't open DB. You can get it here: http://www.sqlite.org/sqlite-dll-win32-x86-3070900.zip
import urllib2
import webbrowser
import cookielib
import os
import sqlite3
import re
from time import sleep
#login in Firefox. Must be default browser. In other cases log in manually
webbrowser.open_new('http://stackoverflow.com/users/login')
#wait for user to log in
sleep(60)
#Process profiles.ini to get path to cookies.sqlite
profile = open(os.path.join(os.environ['APPDATA'],'Mozilla','Firefox','profiles.ini'), 'r').read()
COOKIE_DB = os.path.join(os.environ['APPDATA'],'Mozilla','Firefox','Profiles',re.findall('Profiles/(.*)\n',profile)[0],'cookies.sqlite')
CONTENTS = "host, path, isSecure, expiry, name, value"
#extract cookies for specific host
def get_cookies(host):
cj = cookielib.LWPCookieJar()
con = sqlite3.connect(COOKIE_DB)
cur = con.cursor()
sql = "SELECT {c} FROM moz_cookies WHERE host LIKE '%{h}%'".format(c=CONTENTS, h=host)
cur.execute(sql)
for item in cur.fetchall():
c = cookielib.Cookie(0, item[4], item[5],
None, False,
item[0], item[0].startswith('.'), item[0].startswith('.'),
item[1], False,
item[2],
item[3], item[3]=="",
None, None, {})
cj.set_cookie(c)
return cj
host = 'stackoverflow'
cj = get_cookies(host)
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
response = opener.open('http://stackoverflow.com').read()
# if username in response - Auth successful
if 'Stanislav Golovanov' in response:
print 'Auth successful'

Unit testing authorization in a Pylons app fails; cookies aren't been correctly set or recorded

I'm having an issue running unit tests for authorization in a Pylons app. It appears as though certain cookies set in the test case may not be correctly written or parsed. Cookies work fine when hitting the app with a browser.
Here is my test case inside a paste-generated TestController:
def test_good_login(self):
r = self.app.post('/dologin', params={'login': self.user['username'], 'password': self.password})
r = r.follow() # Should only be one redirect to root
assert 'http://localhost/' == r.request.url
assert 'Dashboard' in r
This is supposed to test that a login of an existing account forwards the user to the dashboard page. Instead, what happens is that the user is redirected back to the login. The first POST works, sets the user in the session and returns cookies. Although those cookies are sent in the follow request, they don't seem to be correctly parsed.
I start by setting a breakpoint at the beginning of the above method and see what the login response returns:
> nosetests --pdb --pdb-failure -s foo.tests.functional.test_account:TestMainController.test_good_login
Running setup_config() from foo.websetup
> /Users/istevens/dev/foo/foo/tests/functional/test_account.py(33)test_good_login()
-> r = self.app.post('/dologin', params={'login': self.user['username'], 'password': self.password})
(Pdb) n
> /Users/istevens/dev/foo/foo/tests/functional/test_account.py(34)test_good_login()
-> r = r.follow() # Should only be one redirect to root
(Pdb) p r.cookies_set
{'auth_tkt': '"4c898eb72f7ad38551eb11e1936303374bd871934bd871833d19ad8a79000000!"'}
(Pdb) p r.request.environ['REMOTE_USER']
'4bd871833d19ad8a79000000'
(Pdb) p r.headers['Location']
'http://localhost/?__logins=0'
A session appears to be created and a cookie sent back. The browser is redirected to the root, not the login, which also indicates a successful login. If I step past the follow(), I get:
> /Users/istevens/dev/foo/foo/tests/functional/test_account.py(35)test_good_login()
-> assert 'http://localhost/' == r.request.url
(Pdb) p r.request.headers
{'Host': 'localhost:80', 'Cookie': 'auth_tkt=""\\"4c898eb72f7ad38551eb11e1936303374bd871934bd871833d19ad8a79000000!\\"""; '}
(Pdb) p r.request.environ['REMOTE_USER']
*** KeyError: KeyError('REMOTE_USER',)
(Pdb) p r.request.environ['HTTP_COOKIE']
'auth_tkt=""\\"4c898eb72f7ad38551eb11e1936303374bd871934bd871833d19ad8a79000000!\\"""; '
(Pdb) p r.request.cookies
{'auth_tkt': ''}
(Pdb) p r
<302 Found text/html location: http://localhost/login?__logins=1&came_from=http%3A%2F%2Flocalhost%2F body='302 Found...y. '/149>
This indicates to me that the cookie was passed in on the request, although with dubious escaping. The environ appears to be without the session created on the prior request. The cookie has been copied to the environ from the headers, but the cookies in the request seems incorrectly set. Lastly, the user is redirected to the login page, indicating that the user isn't logged in.
Authorization in the app is done via repoze.who and repoze.who.plugins.ldap with repoze.who_friendlyform performing the challenge. I'm using the stock tests.TestController created by paste:
class TestController(TestCase):
def __init__(self, *args, **kwargs):
if pylons.test.pylonsapp:
wsgiapp = pylons.test.pylonsapp
else:
wsgiapp = loadapp('config:%s' % config['__file__'])
self.app = TestApp(wsgiapp)
url._push_object(URLGenerator(config['routes.map'], environ))
TestCase.__init__(self, *args, **kwargs)
That's a webtest.TestApp, by the way.
The encoding of the cookie is done in webtest.TestApp using Cookie:
>>> from Cookie import _quote
>>> _quote('"84533cf9f661f97239208fb844a09a6d4bd8552d4bd8550c3d19ad8339000000!"')
'"\\"84533cf9f661f97239208fb844a09a6d4bd8552d4bd8550c3d19ad8339000000!\\""'
I trust that that's correct.
My guess is that something on the response side is incorrectly parsing the cookie data into cookies in the server-side request. But what? Any ideas?
This issue disappeared after downgrading WebTest from 1.2.1 to 1.2.
The issue continually appeared for me regardless of the version of WebTest. However, after much mucking around I noticed that when the cookie was first set it was using 127.0.0.1 as the REMOTE_ADDR value but on the second request it changed to 0.0.0.0.
If I did the get request and set the REMOTE_ADDR to 127.0.0.1 all was well!
response = response.goto(url('home'), extra_environ=dict(REMOTE_ADDR='127.0.0.1'))

Categories