Code

mah

This file contains the flask app object and describes the main entry points to the web application and maps these to urls.

class mah.MAH(*args, **kwargs)

MAH-specific Flask object. Installs the SeaSurf instance under the csrf attribute, and initialises configuration and logging.

route(rule, **options)

Modified route decorator that wraps all routes in an error handling routing to centralise logging and error handling.

unauthenticated_route(rule, **options)

Special route decorator that marks this route as unauthenticated. Handlers for these routes must be public, or handle authentication checks themselves.

mah.verification

class mah.verification.Verification(source_uid, dest_uid)

Represents a one-way authentication between two users. An authentication consists of a username/uid of the authenticator (referred to as the “source”) and the username/uid of the authenticated party (destination).

This class generates a short, human manageable shared secret and puts a short (ideally around 5 - 15 minutes) life span on the authentication. The shared secret is a pseudo random hash truncated to a reasonable (human memorable) configurable length (default: 6).

The expiry time is set a configurable number of seconds into the future (default 300).

This could be prone to collisions, but a given a reasonable length of the shared secret, a short expiry time and the fact authentication occurs between humans it provides a reasonable solution.

classmethod all(uid)

Retrieve all authentication objects for which the uid matches either the source or destination, regardless of expiry time.

Parameters:
  • src_uid – the source user id
  • dst_uid – the destination user id.
Return type:

a list of authentication objects or None.

auth_id = Column(None, Integer(), table=None, primary_key=True, nullable=False, default=Sequence('user_id_seq'))

A unique identifer for the authentication. This is never displayed to the end user and is automatically incremented as authentications are added.

classmethod by_dst(uid)

Retrieve non-expired authentication objects with a given destination uid.

Parameters:
  • src_uid – the source user id
  • dst_uid – the destination user id.
Return type:

a list of authentication objects or None.

classmethod by_id(auth_id)

Retreive the authentication object which matches the unique id.

Parameters:
  • src_uid – the source user id
  • dst_uid – the destination user id.
Return type:

a single authentication object or None.

classmethod by_src(uid)

Retrieve non-expired authentication objects with a given source uid.

Parameters:
  • src_uid – the source user id
  • dst_uid – the destination user id.
Return type:

a list of authentication objects or None.

dest_name = Column(None, String(length=200), table=None, nullable=False)

The human readable name of the source of the authentication. This is looked up from the staff directory.

dest_uid = Column(None, String(length=32), table=None, nullable=False)

The username/uid of the person who was authenticated.

static exists(src_uid, dst_uid)

Given the source and destination of a authentication, check if an authentication already exists and if it does, return true.

Parameters:
  • src_uid – the source user id
  • dst_uid – the destination user id.
Return type:

bool

expiry = Column(None, DateTime(), table=None, nullable=False, default=ColumnDefault(datetime.datetime(2019, 7, 8, 6, 44, 13, 533642)))

The expiry time of this authentication.

expiry_string = ''

A user friendly and timezone independent string to represent the time before an auth expires. e.g. “20 seconds”, “4 minutes”. This is used to improve usability, by not having users exposed to timezone issues, where possible. This is not stored in the database, but instead calculated when objects are queried.

classmethod get(src_uid, dst_uid)

Retreive the unexpired authentication object, given a source and a destination uid. This function can only ever return 1 or 0 elements.

Parameters:
  • src_uid – the source user id
  • dst_uid – the destination user id.
Return type:

a single authentication object or None.

nato_code = ''

The authentication strings in the phonetic alphabet (NATO) codes. This is not stored in the database, but instead generated when objected are queried.

reciprocated = Column(None, Boolean(), table=None, nullable=False, default=ColumnDefault(False))

A flag to show if the authentication exists in the other direction (destination user -> source user). This is primarily used to simplify template logic.

shared_secret = Column(None, String(length=128), table=None, nullable=False)

A short, (relatively) unguessable string represent the shared secret in this authentication.

source_name = Column(None, String(length=200), table=None, nullable=False)

The human readable name of the source. This is looked up from the staff directory.

source_uid = Column(None, String(length=32), table=None, nullable=False)

The username/uid of the person who initiated the authentication.

mah.database

class mah.database.database
Base = None
classmethod done(ok=False)
engine = None
classmethod init()

Initialised the database connection for SQL Alchemy.

session = None

mah.config

A simple, shared configuration configuration object based on the standard python ConfigParser.

config

Global configuration object. Can be treated as a dictionary with case-insensitive keys, or as an object with attributes that correspond to configuration sections. For instance, the following are equivalent:

config['Application']
config['application']
config.application
config.Application

The value of which can be another configuration object or a string, boolean, integer, or float value, or a list of strings, booleans, integers or floats.

See the Configuration section for details.

mah.log

A custom logger for the MAH application. This was implemented to be easily used by all parts of the mah package, without having to pass around the flask.app.logger object and to seperate the log formatting and configuration features.

Usually, this will be used like so:

from mah.log import log

log.debug("This is a debugging message")
mah.log.init(config)

Initialise the logging system.

Parameters:config – the logging subsection of the core configuration object, also available as mah.config.config.logging. See the Configuration section for details.
mah.log.log = <logging.Logger object>

This instance is imported and used in various places in MAH.

mah.report

Enable sending of suspicious authentication reports to administrators.

mah.report.email_report(text, reporter_uid, auth)

A email a report of a suspicious authentication interaction. The destination email address, from address, subject and mail server is read from a configuration file.

Parameters:
  • text – the text of the report
  • reporter_uid – the uid (user id) of the reporter
  • auth – the authentication id (a uniquely identifying int)

mah.nato

Nato alphabet mapping along with helper code to make the alphabet easy to use. Attempts to extract unknown NATO words will result in the unknown word being returned verbatim.

NATO source: https://en.wikipedia.org/wiki/NATO_phonetic_alphabet

>>> from mah.nato import NATO
>>> NATO['a']
'Alpha'
>>> NATO['A']
'Alpha'
>>> NATO['b']
'Bravo'
>>> NATO['.']
'Decimal'
>>> NATO['1']
'One'
>>> NATO['blah'] # Not a known NATO word
'blah'

mah.directory

Base classes for directory services.

class mah.directory.Directory

Base class that directory modules should inherit from. Directory modules MUST expose their child class under the name ‘Directory’ - therefore, import this like:

from mah.directory import Directory as DirBase

class Directory(DirBase):
    pass

Or just refer to it by its full name like so:

import mah.directory

class Directory(mah.directory.Directory):
    pass
config = None

The directory section of the main app configuration. cls.config is the same as mah.config.config.directory. This is None until the init classmethod is called successfully.

classmethod init(config, src)

Do class level initialisation. Should ONLY be called by the mah.config package on startup. config will be the same as mah.config.directory.

Parameters:
  • config – directory specific configuration object. Once configuration setup is complete, this will be available in mah.config.config.directory.
  • src – raw source config as read by ConfigParser
search(query)

Search a staff directory for a staff member.

Parameters:query (str) – the query string to search with.
Returns:a list of Person (or a subclass) objects
Return type:list
user(uid)

Search a staff directory for a specific staff member.

Parameters:uid (str) – the user ID for a staff member to find.
Returns:a Person (or subclass) object or None
Return type:mah.directory.Person
class mah.directory.Person(attributes)

Simple generic container to hold the information about a staff member.

mah.directory.ldap

Use LDAP or AD for directory services.

class mah.directory.ldap.Directory
classmethod init(config, src)

Expects the following configuration variables set:

ldap_filter
Fields to compare search strings against. Defaults to [‘uid’, ‘cn’]
ldap_size_limit
Maximum number of search results to return. Defaults to 250.
ldap_paged_size
Maximum number of search results per page. Defaults to None, which turns off paging.
ldap_time_limit
Maximum number of seconds a search should run for. Defaults to 15.
ldap_url

LDAP connection URL of the form SCHEME://USER:PASS @ HOST:PORT/BASE

  • If SCHEME is ldaps, SSL will be used.
  • USER and PASS are optional, and will be used as credentials.
  • HOST is the LDAP or AD host to connect to.
  • PORT is the port that the LDAP or AD service is listening on. By default this is 389
  • BASE is the LDAP search base, something akin to ou=users,dc=corp,dc=com
search(query)

Search the LDAP or AD directory, comparing against the fields listed in mah.config.config.directory.ldap_filter.

user(uid)

Find a specific user in the LDAP/AD directory.

class mah.directory.ldap.Person(data, attributes)

Ensure all requested attributes are listed, even if they are set to None

mah.authentication

Base classes for authentication services.

class mah.authentication.Authentication

Base class that authentication modules should inherit from. Authentication modules MUST expose their child class under the name ‘Authentication’ - therefore, import this like:

from mah.authentication import Authentication as AuthBase

class Authentication(AuthBase):
    pass

Or just refer to it by its full name like so:

import mah.authentication

class Authentication(mah.authentication.Authentication):
    pass
classmethod authenticate(form)

The main authentication method. Takes a form (a dict of form field names to string values), and uses the data therein to authenticate the user. Should return a tuple of the username and whether authentication was successful.

Parameters:form – A form object (dict-like) with fields entered by the user
Returns:A 2-tuple of username (str) and success (bool)
config = None

The login section of the main app configuration. cls.config is the same as mah.config.config.login. This is None until the init classmethod is called successfully.

static for_production()

If this is a ‘dummy’ authentication method (like the built in ‘none’ authentication module), this should return False - a warning will be displayed on the login page.

classmethod init(config, src)

Initialise the authenticator. By default we make sure the username and password inputs are updated by the config, and we store the config for future use

Parameters:
  • config – login specific configuration object. Once configuration setup is complete, this will be available in mah.config.config.login.
  • src – raw source config as read by ConfigParser
inputs = {'password': {'description': None, 'label': 'Password', 'name': 'password', 'order': 2, 'required': True, 'secret': True}, 'username': {'description': None, 'label': 'Username', 'name': 'username', 'order': 1, 'required': True, 'secret': False}}

A dictionary of fields accepted by the authenticator. Each key should be a field name (ie, will be used in an <input> tag as the name attribute) with a value of a dict describing the key. That dict should have the following keys.

name
The name of the field (ie, identical to the key of this input).
label
Display name for the field. This should be changable in config by using a *_label configuration variable.
secret
If True, the field will be considered password-like and a type of ‘password’ will be used for the <input> tag instead of ‘text’.
required
Boolean. Can be used to modify the interface, however the authenticator MUST check validity itself.
description
None, or a text string that will be displayed near the input to describe what is expected. This should be changable in config by using a *_description configuration variable.
order
A number defining in which order (ascending) the fields should be shown. If two fields have the same order, they will be sorted correctly in respect to all other fields, but will not be reliably ordered amongst themselves.

mah.authentication.none

A debugging authentication method that doesn’t use any form of password - just a username

class mah.authentication.none.Authentication
classmethod authenticate(form)

Always succeeds, and logs a ‘fake’ authentication.

static for_production()

This authentication method is NOT for production use.

classmethod init(config, src)

Removes the ‘password’ input as it is not required.

mah.authentication.radius

A simple wrapper around the pyrad API to talk to radius authentication servers.

class mah.authentication.radius.Authentication
classmethod authenticate(form)

Authenticates to the configured RADIUS server.

classmethod init(config, src)

Expects the following configuration variables set:

radius_server
The host or IP of the radius server to authenticate against.
radius_secret
The secret for connecting to the radius server
radius_dictionary
The attribute dictionary
radius_nas_identifier
The NAS identifier attribute
radius_nas_ip_address
The IP address attribute