Advanced CGI Scripts

With cookies we can write more clever cgi scripts.

Cookies

Cookies enable personalized browsing. With cookies we can store information about a client. For example, about previous selections made, in passing data from one script to another. We can store personal information such as identification and passwords; or information from previous visits to the site. Potential applications include customizing displayed content, storing encrypted password for fast access, personalized pricing ...

Cookies are data

  • stored by web server on client computer,
  • managed by the browser.

The session below illustrates the use of the cookies module.

>>> from http import cookies
>>> c = cookies.SimpleCookie()
>>> c['L'] = 35
>>> c['data'] = 'Fri 8 Apr 2016'
>>> print(c)
Set-Cookie: L=35
Set-Cookie: data="Fri 8 Apr 2016"

Cookies are objects like dictionaries. Two keys are reserved: expires and path.

To compute with values stored in cookies, observe the following:

  • The value of cookie is the attribute value.
  • The type of the value is a string.

To update the number of visits stored in the cookie cnt, consider

>>> from http import cookies
>>> cnt = cookies.SimpleCookie()
>>> cnt['visits'] = 0
>>> cnt['visits']
<Morsel: visits=0>
>>> cnt['visits'].value
'0'
>>> cnt['visits'] = str(1 + int(cnt['visits'].value))
>>> cnt['visits'].value
'1'

A simple cookie stores all data as strings. We can initialize a cookie with keys and values:

>>> from http.cookies import SimpleCookie
>>> c = SimpleCookie('x=1; y=2')
>>> c['x'].value
'1'
>>> c['y'].value
'2'

The environment variable HTTP_COOKIE is set by the web server. When we rerun a web page the cookies are retrieved as follows:

>>> from os import environ
>>> environ['HTTP_COOKIE'] = 'z = 3'
>>> d = SimpleCookie(environ['HTTP_COOKIE'])
>>> d['z'].value
'3'

To illustrate an application of cookies, let us count the number of visits made to a site.

We will write a script to

  1. retrieve cookie, initialize counter to zero, or
  2. increment the value of counter by one, and then
  3. display the counter value on the page.

The name of the script is cookie_counter.py. The browser settings must accept cookies. Note that each browser has its own cookies. A screenshot of the running of the script is shown in Fig. 99.

_images/figcookiecounter.png

Fig. 99 Running a script to count the number of visits.

The listing of the code for cookie_counter.py is below. We start with the function to increment the counter.

#!/usr/bin/python

import os
from http.cookies import SimpleCookie

def increment():
    """
    Retrieves cookie, either initializes counter,
    or increments the counter by one.
    """
    if 'HTTP_COOKIE' in os.environ:
        cnt = SimpleCookie(os.environ['HTTP_COOKIE'])
    else:
        cnt = SimpleCookie()
    if 'visits' not in cnt:
        cnt['visits'] = 0
    else:
        cnt['visits'] = str(1 + int(cnt['visits'].value))
    return cnt

The main function in cookie_counter.py is then

def main():
    """
    Retrieves a cookie and writes
    the value of counter to the page,
    after incrementing the counter.
    """
    counter = increment()
    print(counter)
    print("Content-Type: text/plain\n")
    print("counter: %s" % counter['visits'].value)

main()

Password Encryption

We can use cookies for login names and passwords. For security purposes, we store only the encrypted passwords.

A secure hash algorithm is often called a trapdoor function, as it is computationally infeasible to compute its inverse. As a hash function, the collision rate is low, which means that there is a very low chance that two different messages will generate the same key.

The server encrypts the password before sending it to the client. The authentication happens by comparing encrypted passwords.

A session with the hashlib module is illustrated below.

>>> from hashlib import sha1
>>> m = 'this is me'
>>> h = sha1(m.encode())
>>> h
<sha1 HASH object @ 0x1019a53f0>
>>> h.hexdigest()
'99cf08cddcc3ae19fae7ec8f53f5179786b11406'

As an example, we consider the application of cookies to remember data from login forms.

Accessing cookie_login.py for the first time:

  1. The user submits login name and password.
  2. The submitted data is processed by cookie_userpass.py.
  3. The cookie stores the login name and encrypted password.

Connecting to cookie_login.py a second time:

  1. The cookie is retrieved.
  2. The login name is displayed if not empty.
  3. The user must type no password if in cookie.

The main function in the script cookie_login.py is listed below.

#!/usr/bin/python

import cgitb
cgitb.enable()
from http.cookies import SimpleCookie
from os import environ

def main():
    """
    Form to process login.
    """
    clp = get_cookie()
    print(clp)
    print("Content-type: text/html\n")
    print("<html><body>\n")
    ask_name(clp)
    print("</body></html>\n")

The main function calls two functions: get_cookie() and ask_name(). The function get_cookie() is defined below.

def get_cookie():
    """
    Retrieves cookie, and initializes it.
    """
    if 'HTTP_COOKIE' in environ:
        sim = SimpleCookie(environ['HTTP_COOKIE'])
    else:
        sim = SimpleCookie()
    if 'login' not in sim:
        sim['login'] = ''
        sim['passw'] = ''
    return sim

The function ask_name() is defined next.

def ask_name(cki):
    """
    Form to enter user name, using cookie cki
    to show user name.
    """
    print("""<form method = "post"
                   action = "cookie_userpass.py">
    """)
    vlog = cki['login'].value
    vpas = cki['passw'].value
    if vlog == '':
        print("""<p>
        Login Name:
        <input type = "text" name = "login" size = 20>""")
    else:
        print("""<p>
        Login Name:
        <input type = "text" name = "login" size = 20
            value = %s>""" % vlog)
    if vpas == '':
        print("""<p> Password:
        <input type = "password" name = "passw"
            size = 20>""")
    print("""
        <input type = "submit" value = "submit">
        </form>""")

The main function in the script cookie_userpass.py is listed below.

#!/usr/bin/python
import cgi

def main():
    """
    Form to process login.
    """
    form = cgi.FieldStorage()
    cki = get_cookie(form)
    print(cki)
    print("Content-type: text/html\n")
    print("<html><body>\n")
    error = process_name(form)
    if not error:
        process_pass(cki)
    print("</body></html>\n")

The function get\_cookie() gets the data from the form.

def get_cookie(form):
    """
    Retrieves cookie and uses form to update.
    """
    if 'HTTP_COOKIE' in environ:
        sim = SimpleCookie(environ['HTTP_COOKIE'])
    else:
        sim = SimpleCookie()
    if 'login' in sim:
        if 'login' in form:
            sim['login'] = form['login'].value
    if 'passw' in sim:
        if 'passw' in form:
            vpw = form['passw'].value
            data = sha1(vpw).hexdigest()
            sim['passw'] = data
    return sim

The processing the name is done by the function below.

def process_name(form):
    """
    Processes name of login form.
    Returns True if error, else False.
    """
    error = False
    try:
        name = form['login'].value
    except KeyError:
        print("please enter your name")
        error = True
    if not error:
        print('welcome ' + name + '\n')
    return error

Processing password is done by process_pass().

def process_pass(cki):
    """
    Processes password of login form,
    stored in the cookie.
    """
    print("<p>your password is ")
    print(cki['passw'].value)

In a real application, instead of printing the password, one would compare the retrieved password against the password on file.

Authentication via Email

We can restrict access to our web application via a third party email authentication. For example, we may grant access only to those with a valid UIC email account.

The Simple Mail Transfer Protocol (SMTP) is an internet standard for email transmission. In Python we can send email using smtplib. For example, to connect to the {tt gmail} email server, consider the session below:

>>> from smtplib import SMTP
>>> s = SMTP('smtp.gmail.com', 587)
>>> s.starttls()
(220, b'2.0.0 Ready to start TLS')

The Transport Layer Security (TLS) protocol provides data encryption for socket based communication.

Consider the code for the script emailauth.py below. A user with a valid UIC netid and corresponding email address should be able to pass the authentication.

from smtplib import SMTP
from getpass import getpass

try:
    SERVER = SMTP(host='mail.uic.edu', port=25)
    RESULT = SERVER.starttls()
    print(RESULT)
except:
    print('Failed to connect to the server.')

SUCCESS = False
USER = input('Type your UIC netid : ')
USER = USER + '@uic.edu'
for _ in range(3):  # allow for three attempts
    PASW = getpass('Type your UIC password : ')
    try:
        RESULT = SERVER.login(USER, PASW)
        print(RESULT)
        SUCCESS = True
        break
    except:
        print('Login failed, please try again.')

Another application of smtplib is to send email. First we connect to the server and then go through the authentication process, as defined in the function below.

HOSTNAME = 'mail.uic.edu'
PORTNUMBER = 25

from smtplib import SMTP
from email.mime.text import MIMEText
from getpass import getpass

def authenticate(hostname, portnumber):
    """
    Returns the server and the email address of the user,
    after a successful authentication,
    otherwise None is returned.
    """
    try:
        server = SMTP(host=hostname, port=portnumber)
        result = server.starttls()
    except:
        print('Failed to connect to the server.')
    success = False
    user = input('Type your UIC netid : ')
    user = user + '@uic.edu'
    for _ in range(3):  # allow for three attempts
        pasw = getpass('Type your UIC password : ')
        try:
            result = server.login(user, pasw)
            success = True
            break
        except:
            print('Login failed, please try again.')
    if success:
        print('You are authenticated via UIC email.')
        return server, user
    else:
        print('Authentication via UIC email failed.')
        return None

The function main() in the script is below:

def main():
    """
    First we authenticate and then we send the email.
    """
    srvusr = authenticate(HOSTNAME, PORTNUMBER)
    if srvusr != None:
        server, user = srvusr
        ret = sendanemail(server, user)
        if ret == {}:
            print('The message was accepted for delivery.')
        else:
            print('The message was rejected for delivery.')
            print(ret)

Once connected and authenticated, we can compose a message. MIME (Multipurpose Internet Mail Extensions) is an internet standard to extend the format of email. In Python, email.mime.text represents text/* type MIME documents.

>>> from email.mime.text import MIMEText
>>> msg = MIMEText('This is a test.')
>>> msg['Subject'] = 'hello'
>>> msg['To'] = 'someuser@gmail.com'
>>> print(msg.as_string())
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: hello
To: someuser@gmail.com

This is a test.
>>>

The sending of an email is defined by the function below.

def sendanemail(server, user):
    """
    Prompts the user for a destination,
    subject and a message body.
    """
    dest = input('Give the destination email address : ')
    subj = input('Give the subject : ')
    body = input('Give the message : ')
    msg = MIMEText(body)
    msg['Subject'] = subj
    msg['To'] = dest
    result = server.sendmail(user, dest, msg.as_string())
    return result

An application of the sending of an email via a Python script is the automatic registration and activation of an account.

The registration/activation is a two step process:

  1. The user connects to our web site and submits a form with the email address of the user (which will be the name of the account) and a password, which is encrypted.

    The register.py script

    1. stores the account information,
    2. generates a random code for one time use,
    3. sends the email to the user with link to the activate script that takes the random code as input.

    The email sent contains an URL of the following form:

    /cgi-bin/activate.py?login=%s&code=%s
    

    This login and the code are the email address and the code.

  2. The activation process is executed by {tt activate.py}.

    This script checks whether the two parameters in the activate script match what is stored in the database. If okay, if the access code gives a match for the login name, then the user can login via the login form.

    In this process, the server only sends email, does not read it.

Exercises

  1. Extend cookie_counter.py so that a different HTML page is displayed based on whether the counter is zero or not.
  2. Extend cookie_userpass.py for it to do proper password authentication: compare the given password with the one that was earlier entered and stored on file.
  3. Consider an mysql table customers which contains as its fields the identification number, the name, and the email address of each customer. Write a script to email every customer a greeting.
  4. Write code for the register.py described earlier.
  5. Write code for the activate.py described earlier.