\section{The {\Tt{}AutologinSession\nwendquote} class}% ===> this file was generated automatically by noweave --- better not edit it

Here we document the {\Tt{}weblogin\nwendquote} main module.
We provide an abstract class, {\Tt{}weblogin.AutologinSession\nwendquote}, through which we 
interact with the web page's API.
This is a subclass to {\Tt{}requests.Session\nwendquote} and intercepts responses to check 
for redirects to the login service.
If we detect a redirect to the login service, we automatically log in, then 
return the new response.
The logging in will become completely invisible, as if it never happened.

We do this by inheriting from {\Tt{}requests.Session\nwendquote} and then overloading the 
{\Tt{}request\nwendquote} method.
This allows us to always intercept any request, make it (by using the original 
method), check the response.
If the response indicates that we need a login, we log in.
To determine if we need to log in, we use login handlers (\cref{LoginHandler}).
(See \cref{WebloginOverview}.)

\begin{figure}
  \centering
  \includegraphics[width=0.8\columnwidth]{figs/weblogin.pdf}
  \caption{An overview of how an HTTP request passes from the caller, through 
  the requests library, to autologin, which runs the request through each login 
  handler and finally returns the request response to the caller.}
  \label{WebloginOverview}
\end{figure}

\nwfilename{init.nw}\nwbegincode{1}\sublabel{NW22lJrC-1GuT0M-1}\nwmargintag{{\nwtagstyle{}\subpageref{NW22lJrC-1GuT0M-1}}}\moddef{\code{}init.py\edoc{}~{\nwtagstyle{}\subpageref{NW22lJrC-1GuT0M-1}}}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
import requests

\LA{}exceptions~{\nwtagstyle{}\subpageref{NW22lJrC-3bbFxR-1}}\RA{}
\LA{}classes~{\nwtagstyle{}\subpageref{NW22lJrC-Z8J9d-1}}\RA{}

class AutologinSession(requests.Session):
  """
  Maintains an authenticated session to a web system. This class intercepts any 
  requests made in a requests.Session and ensures that we log in when 
  redirected to the login page.
  """

  def __init__(self, handlers):
    """
    Takes a list of handlers. A handler should derive from AutologinHandler.
    """
    super().__init__()
    self.__handlers = handlers

  def __getstate__(self):
    \LA{}return state for pickling~{\nwtagstyle{}\subpageref{NW22lJrC-A7jc1-1}}\RA{}

  def request(self, *args, **kwargs):
    """
    Wrapper around requests.Session.request(...) to check we must log in.
    """
    response = super().request(*args, **kwargs)
    
    \LA{}log in if necessary~{\nwtagstyle{}\subpageref{NW22lJrC-GNEjt-1}}\RA{}

    return response
\nwnotused{[[init.py]]}\nwendcode{}\nwbegindocs{2}\nwdocspar

\subsection{Pickling an {\Tt{}AutologinSession\nwendquote} object}

The {\Tt{}requests.Session\nwendquote} class defined a custom {\Tt{}{\_}{\_}getstate{\_}{\_}\nwendquote} method which 
overrides the default one.
This means that we must also provide an override, otherwise the 
{\Tt{}requests.Session\nwendquote} version of {\Tt{}{\_}{\_}getstate{\_}{\_}\nwendquote} will be used.
This will mean that our {\Tt{}{\_}{\_}handler\nwendquote} attribute will be left out of pickling.

The {\Tt{}{\_}{\_}getstate{\_}{\_}\nwendquote} method should return a dictionary.
We'll get the dictionary from the parent method, then add our attribute.
\nwenddocs{}\nwbegincode{3}\sublabel{NW22lJrC-A7jc1-1}\nwmargintag{{\nwtagstyle{}\subpageref{NW22lJrC-A7jc1-1}}}\moddef{return state for pickling~{\nwtagstyle{}\subpageref{NW22lJrC-A7jc1-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NW22lJrC-1GuT0M-1}}\nwenddeflinemarkup
state = super().__getstate__()
state["_AutologinSession__handlers"] = self.__handlers
return state
\nwused{\\{NW22lJrC-1GuT0M-1}}\nwendcode{}\nwbegindocs{4}\nwdocspar

\subsection{The {\Tt{}AutologinHandler\nwendquote} abstract class}\label{LoginHandler}

A handler should derive from this class:
\nwenddocs{}\nwbegincode{5}\sublabel{NW22lJrC-Z8J9d-1}\nwmargintag{{\nwtagstyle{}\subpageref{NW22lJrC-Z8J9d-1}}}\moddef{classes~{\nwtagstyle{}\subpageref{NW22lJrC-Z8J9d-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NW22lJrC-1GuT0M-1}}\nwenddeflinemarkup
class AutologinHandler:
  """
  An abstract class for a handler for the AutologinSession class.
  """

  def login(self, session, response, args=None, kwargs=None):
    """
    Performs a login based on the response from a request.
    - `session` is an instance of requests.Session, most likely an instance of 
      AutologinSession.
    - `response` is the response of the latest request.
    - `args` and `kwargs` are the options from the latest request, this is so 
      that we can redo that request after logging in.

    Raises an AuthenticationError exception if authentication fails.
    """
    raise NotImplementedError()

  def need_login(self, response):
    """
    Checks a response to determine if logging in is needed,
    returns True if needed.
    """
    raise NotImplementedError()
\nwused{\\{NW22lJrC-1GuT0M-1}}\nwendcode{}\nwbegindocs{6}\nwdocspar

\subsection{Errors}

We must report authentication errors.
For this we need specialized exceptions.
\nwenddocs{}\nwbegincode{7}\sublabel{NW22lJrC-3bbFxR-1}\nwmargintag{{\nwtagstyle{}\subpageref{NW22lJrC-3bbFxR-1}}}\moddef{exceptions~{\nwtagstyle{}\subpageref{NW22lJrC-3bbFxR-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NW22lJrC-1GuT0M-1}}\nwenddeflinemarkup
class AuthenticationError(Exception):
  pass
\nwused{\\{NW22lJrC-1GuT0M-1}}\nwendcode{}\nwbegindocs{8}\nwdocspar

\subsection{Log in if necessary}

The login procedure is quite straightforward, we simply call each handler.
Each handler will itself keep track of state if they require several steps when 
logging in.
Each handler will possibly update the response for the next handler.
\nwenddocs{}\nwbegincode{9}\sublabel{NW22lJrC-GNEjt-1}\nwmargintag{{\nwtagstyle{}\subpageref{NW22lJrC-GNEjt-1}}}\moddef{log in if necessary~{\nwtagstyle{}\subpageref{NW22lJrC-GNEjt-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NW22lJrC-1GuT0M-1}}\nwenddeflinemarkup
for handler in self.__handlers:
  if handler.need_login(response):
    response = handler.login(self, response, args, kwargs)
\nwused{\\{NW22lJrC-1GuT0M-1}}\nwendcode{}

\nwixlogsorted{c}{{\code{}init.py\edoc{}}{NW22lJrC-1GuT0M-1}{\nwixd{NW22lJrC-1GuT0M-1}}}%
\nwixlogsorted{c}{{classes}{NW22lJrC-Z8J9d-1}{\nwixu{NW22lJrC-1GuT0M-1}\nwixd{NW22lJrC-Z8J9d-1}}}%
\nwixlogsorted{c}{{exceptions}{NW22lJrC-3bbFxR-1}{\nwixu{NW22lJrC-1GuT0M-1}\nwixd{NW22lJrC-3bbFxR-1}}}%
\nwixlogsorted{c}{{log in if necessary}{NW22lJrC-GNEjt-1}{\nwixu{NW22lJrC-1GuT0M-1}\nwixd{NW22lJrC-GNEjt-1}}}%
\nwixlogsorted{c}{{return state for pickling}{NW22lJrC-A7jc1-1}{\nwixu{NW22lJrC-1GuT0M-1}\nwixd{NW22lJrC-A7jc1-1}}}%
\nwbegindocs{10}\nwdocspar

The benefit of this design comes when trying to log in using an SSO:
We first have a handler for the main service and another handler for the SSO.
\nwenddocs{}
