import urllib.request
import urllib.parse
import getpass
class CosignPasswordMgr(object):
"""A password manager for CosignHandler objects.
"""
def newcred(self):
"""Default callback.
Ask user for username and password."""
return {'login': input('username: '),
'password': getpass.getpass()}
def __init__(self, cred=None, max_tries=5, callback=newcred):
"""Create a new CosignPasswordMgr.
Args:
cred: Initial credentials. Will be returned by the first
call to get_cred(). Should be a dictionary of the form:
{'login': username, 'password': password}
max_tries: Maximum number of times get_cred() may be called
before IndexError is raised.
callback: A function to be called to get new
credentials. The current object instance (self) will be
passed as the first argument.
"""
self.set_cred(cred)
self.try_count = 1
self.max_tries = max_tries
self.callback = callback
def set_cred(self, cred):
"""Set stored credentials to cred.
cred should be of the form:
{'login': username, 'password': password}
"""
self.cred = cred
self.dirty = False
def get_cred(self):
"""Get new credentials.
Return a credentials dictionary (see set_cred()). Raise an
IndexError exception if self.max_tries have already been made.
"""
if not self.dirty and self.cred is not None:
self.try_count = self.try_count + 1
self.dirty = True
return self.cred
if self.try_count > self.max_tries:
raise IndexError("Exceeded max_tries ({})".format(self.max_tries))
self.cred = self.callback(self)
self.try_count = self.try_count + 1
self.dirty = True
return self.cred
class CosignHandler(urllib.request.BaseHandler):
"""urllib.request style handler for Cosign protected URLs.
See http://weblogin.org
SYNOPSIS:
# Cosign relies on cookies.
cj = http.cookiejar.MozillaCookieJar('cookies.txt')
# We need an opener that handles cookies and any cosign redirects and
# logins.
opener = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(cj),
# Here's the CosignHandler.
CosignHandler('https://cosign.login/page',
cj,
CosignPasswordMgr()
# If you've got one big program you'll probably
# want to keep the cookies in memory, but for
# lots of little programs we get single sign on
# behaviour by saving and loading to/from a
# file.
save_cookies=True
)
)
# Construct a request for the page we actually want
req = urllib.request.Request(
url='https://some.cosign.protected/url',
)
# make the request
res = opener.open(req)
# If all went well, res encapsulates the desired result, use res.read()
# to get at the data and so on.
"""
def __init__(self, login_url, cj, pw_mgr, save_cookies=True):
"""Construct new CosignHandler.
Args:
login_url: URL of cosign login page. Used to figure out if we
have been redirected to the login page after a failed
authentication, and as the URL to POST to to log in.
cj: An http.cookiejar.CookieJar or equivalent. You'll need
something that implements the FileCookieJar interface if
you want to load/save cookies.
pw_mgr: A CosignPasswordMgr object or equivalent. This
object will provide (and if necessary prompt for) the
username and password.
save_cookies: Whether or not to save cookies to a file after
each request. Required for single sign on between
different scripts.
"""
super().__init__()
self.login_url = login_url
self.cj = cj
self.pw_mgr = pw_mgr
self.save_cookies = save_cookies
# try to load cookies from file (specified when constructing cj)
try:
self.cj.load(ignore_discard=True)
except IOError:
pass
def https_response(self, req, res):
"""Handle https_response.
If the response is from the cosign login page (starts with
self.login_url) then log in to cosign and retry. Otherwise
continue as normal.
"""
if res.code == 200 and res.geturl().startswith(self.login_url + '?'):
# Been redirected to login page.
# We'll need the cosign cookies later
self.cj.extract_cookies(res, req)
# Grab a username and password.
data = urllib.parse.urlencode(self.pw_mgr.get_cred())
# Construct a login POST request to the login page.
req2 = urllib.request.Request(
self.login_url,
data.encode('iso-8859-1'),
)
# We need a different opener that doesn't have a CosignHandler.
opener = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(self.cj)
)
# Try the login
res2 = opener.open(req2)
# Cookies, cookies, cookies
self.cj.extract_cookies(res2, req2)
# We should be logged in, go back and get what was asked for
res = opener.open(req)
# If we end up back at the login page then login failed
if res.geturl().startswith(self.login_url + '?'):
raise Exception('Login failed.')
if self.save_cookies:
self.cj.extract_cookies(res,req)
self.cj.save(ignore_discard=True)
return res