ucam_webauth¶
The ucam_webauth module implements version 3 of the WAA to WLS protocol.
It is not set up to talk to a specific WAA (i.e., Raven), and subclassing
this modules’ classes is required to make it functional. In particular, you
probably want to use ucam_webauth.raven
.
The protocol is implemented as defined at https://raven.cam.ac.uk/project/waa2wls-protocol.txt at the time of writing (though that URL may have since been replaced with a newer version). A copy of wawa2wls-protocol.txt is included with python-raven, and more information can be found at https://raven.cam.ac.uk/project/.
- WAA
- A WAA is a “Web Application Agent” (i.e., an application using this module)
- WLS
- The “Web Login Service” (i.e., Raven)
-
ucam_webauth.
ATYPE_PWD
¶ -
ucam_webauth.
STATUS_SUCCESS
¶ -
ucam_webauth.
STATUS_CANCELLED
¶ -
ucam_webauth.
STATUS_NOATYPES
¶ -
ucam_webauth.
STATUS_UNSUPPORTED_VERSION
¶ -
ucam_webauth.
STATUS_BAD_REQUEST
¶ -
ucam_webauth.
STATUS_INTERACTION_REQUIRED
¶ -
ucam_webauth.
STATUS_WAA_NOT_AUTHORISED
¶ -
ucam_webauth.
STATUS_AUTHENTICATION_DECLINED
¶ AuthenticationType
andStatus
instances used as constants in requests and responsesThey compare equal with their corresponding integers (for status codes) and strings (for atypes).
-
ucam_webauth.
STATUS_CODES
¶ A dict mapping status.code (i.e., the integer status code) to the relevant status object
-
class
ucam_webauth.
AuthenticationType
(name, description)[source]¶ An Authentication Type
This class exists to create the
ucam_webauth.AUTH_PWD
constant.-
name
¶ the name by which Ucam-webauth knows it
-
description
¶ a sentence describing it
Note that comparing an
AuthenticationType
object with astr
(or anotherAuthenticationType
object) will compare thename
attribute only. Further,str(atype) == atype.name
.-
-
class
ucam_webauth.
Status
(code, name, description)[source]¶ A WLS response Status
-
code
¶ a (three digit) integer
-
name
¶ short name for the status
-
description
¶ description: a sentence describing the status
Note that comparing a
Status
object with an integer (or anotherStatus
object) will compare thecode
attribute only. Further, int(status_object) == status_object.code-
-
class
ucam_webauth.
Request
(url, desc=None, aauth=None, iact=None, msg=None, params=None, fail=None, encode_strings=True)[source]¶ A Request to the WLS
Parameters: - url (
str
) – a fully qualified URL; the user will be returned here (along with the Response as a query parameter) afterwards - desc (
str
) – optional description of the resource/website (encoding - see below) - aauth (
set
ofAuthenticationType
objects) – optional set of permissible authentication types; we require the user to use one of them (if empty, the WLS uses its default set) - iact (
True
,False
orNone
) – interaction required, forbidden or don’t care (respectively) - msg (
str
) – optional message explaining why authentication is required (encoding - see below) - params (
str
) – data, which is returned unaltered in theResponse
- fail (
bool
) – if True, and authentication fails, the WLS must show an error message and not redirect back to the WAA
All parameters are available as attributes as of Request object, once created.
-
iact
¶
True
: the user must re-authenticateFalse
: no interaction with the user is permitted (the request will only succeed if the user’s identity can be returned without interacting at all)None
(default): interacts if required
-
msg
¶ -
desc
¶ The ‘msg’ and ‘desc’ parameters are restricted to printable ASCII characters (0x20 - 0x7e). The WLS will convert ‘<’ and ‘>’ to ‘<’ and ‘>’ before using either string in HTML, preventing the inclusion of markup. However, it does not touch ‘&’, so HTML character and numeric entities may be used to represent other characters.
If encode_strings is
True
,&
will be escaped to&
, and non-ascii characters in msg and desc will be converted to their numeric entities.Otherwise, it is up to you to encode your strings. An error will be raised if msg or desc contain non-printable-ASCII characters.
-
params
¶ The ucam-webauth protocol does not specify any restrictions on the content of params. However, awful things may happen if you put arbitrary binary data in here. The Raven server appears to interpret non-ascii contents as latin-1, turn them into html entities in order to put them in a hidden HTML input element, then turn them back into (hopefully) the same binary data to be returned in the Response. As a result it outright rejects ‘params’ containing bytes below 0x20, and has the potential to go horribly wrong and land you in encoding hell.
Basically, you probably want to base64 params before giving it to a Request object.
- url (
-
class
ucam_webauth.
Response
(string)[source]¶ A Response from the WLS
Constructed by parsing string, the ‘encoded response string’ from the WLS.
The Response class has the following attributes, which must be set by subclassing it (see
raven.Response
):A
set
ofstr
objectsThe ptags attribute is set to this value if the version of the response is less than 3
-
keys
¶ A dict mapping key identifiers (kid) to a RSA public key (which must be an object with a
verify(digest, signature)
method that returns abool
)
A Response object has the following attributes:
Always present
-
ver
¶ response protocol version (
int
)
-
msg
¶ a text message describing the status of the authentication request, suitable for display to the end-user (
str
)
-
issue
¶ response creation time (
datetime
, timezone naive - the values are UTC)
-
id
¶ an “identifier” for the response. (
int
)The tuple (issue, id) is guaranteed to be unique
-
url
¶ the value of url supplied in the request, or equivalently, the URL to which this response was delivered (
str
)
-
success
¶ shorthand for
status == STATUS_SUCCESS
(bool
)
-
params
¶ a copy of params from the request (
str
)
-
signed
¶ whether the signature was present and has been verified (
bool
)Note that a present but invalid signature will produce an exception when parsed.
Present if authentication was successful, otherwise ``None``:
-
principal:
the authenticated identity of the user (
str
)
attributes or properties of the principal (
frozenset
ofstr
objects)
-
auth
¶ method of authentication used (
AuthenticationType
constant, orNone
)If authentication was not established by interaction (i.e., the client was already authenticated) then auth is
None
-
sso
¶ previous successful authentication types used (
frozenset
ofAuthenticationType
constants)sso will not be the empty set if auth is
None
Optional if authentication was successful, otherwise ``None``:
-
life
¶ remaining life of the user’s WLS session (
int
, in seconds)
Required if signed is True:
-
kid
¶ identifies the RSA key used to sign the request (
str
)
flask_glue
¶
This module provides glue to make using python-raven with Flask easy
-
class
ucam_webauth.flask_glue.
AuthDecorator
(desc=None, aauth=None, iact=None, msg=None, max_life=7200, use_wls_life=False, inactive_timeout=None, issue_bounds=(15, 5), require_principal=None, require_ptags=frozenset([u'current']), can_trust_request_host=False)[source]¶ An instance of this class decorates views to add authentication.
To use it, you’ll need to subclass it and set response_class, request_class and logout_url (see
raven.flask_glue.AuthDecorator
). Then:auth_decorator = AuthDecorator() # settings, e.g., desc="..." go here @app.route("/some_url") @auth_decorator def my_view(): return "You are " + auth_decorator.principal
Or to require users be authenticated for all views:
app.before_request(auth_decorator.before_request)
Note that since it uses flask.session, you’ll need to set
app.secret_key
.We need to be able to reliably determine the hostname of the current website. This is retrieved from
flask.Request.url
. By default, Werkzeug will respect the value of aX-Forwarded-Host
header, which means that a man-in-the-middle can have someone authenticate to their website, and forward the response from the WLS on to you. You must either setflask.Request.trusted_hosts
, for example like so:class R(flask.Request): trusted_hosts = {'www.danielrichman.co.uk'} app.request_class = R
… or sanitise both the Host header and the X-Forwarded-Host header in your web-server. If you choose the second option, set can_trust_request_host.
This tries to emulate the feel of applying mod_ucam_webauth to a file.
The decorator wraps the view in a function that calls
before_request()
first, calling the original view function if it does not return a redirect or abort.You may wish to catch the 401 and 403 aborts with
app.errorhandler
.The
principal
, theirptags
, theissue
andlife
from the WLS are available as attributes of theAuthDecorator
object (magic properties that retrieve the current values fromflask.session
). Further, the attributesexpires
andexpires_all
give information on when the ucam_webauth session will expire.For the desc, aauth, iact, msg parameters, see
ucam_webauth.Request
.Note that the max_life, use_wls_life and inactive_timeout parameters deal with the ucam_webauth session only; they only affect
flask.session["_ucam_webauth"]
. Flask’s session expiry, cookie lifetimes, etc. are independent.Parameters: - max_life (
int
(seconds) orNone
) – upper bound on how long a successful authentication can last before it expires and the user must reauthenticate - use_wls_life (
bool
) – should we lower the life of the session to the life reported by the WLS, if it is less than max_life? - inactive_timeout (
int
(seconds) orNone
) – expire the session if no request is processed via this decorator in inactive_timeout seconds - issue_bounds (
tuple
: (int
,int
) (seconds)) – a tuple, (lower, upper) - how close the issue (datetime that the WLS says the authentication happened at) must be to now (i.e., requirenow - lower < issue < now + upper
; this is a combination of two settings found in mod_ucam_webauth: clock skew and response timeout,issue_bounds=(clock_skew + response_timeout, clock_skew)
is equivalent) - require_principal (
set
ofstr
, orNone
) – require the principal to be in the set - require_ptags (
set
ofstr
, orNone
) – require the ptags to contain any string in require_ptags (i.e., non empty intersection) - can_trust_request_host (
bool
) – Can we trust the hostname inrequest.url
? (see Checking response values)
More complex customisation is possible:
override
check_authorised()
to do more complex checking than require_principal, require_ptags (note that this replaces checking require_principal, require_ptags)override
session_new()
The
AuthDecorator
only touchesflask.session["_ucam_webauth"]
. If you’ve saved other (important) things to the session object, you may want to clear them out when the state changes.You can do this by subclassing and overriding session_new. It is called whenever a response is received from the WLS, except if the response is a successful re-authentication after session expiry, with the same principal and ptags as before.
To log the user out, call
logout()
, which will clear the session state. Further,logout()
returns aflask.redirect()
to the Raven logout page. Be aware that the default flask session handlers are susceptible to replay attacks.POST requests: Since it will redirect to the WLS and back, the auth decorator will discard any POST data in the process. You may wish to either work around this (by subclassing and saving it somewhere before redirecting) or ensure that when it returns (with a GET request) to the URL, a sensible page is displayed (the form, or an error message).
-
__call__
(view_function)[source]¶ Wraps view_function with the auth decorator
(
AuthDecorator
objects are callable so that they can be used as function decorators.)Calling it returns a ‘wrapper’ view function that calls
request()
first.
-
principal
¶ The current principal, or
None
The current ptags, or
None
-
issue
¶ When the last WLS response was issued
issue is converted to a unix timestamp (
int
), rather than thedatetime
object used byucam_webauth.Response
. (issue isNone
if there is no current session.)
-
life
¶ life of the last WLS response (
int
seconds), orNone
-
last
¶ Time (
int
unix timestamp) of the last decorated request
-
expires
¶ When (
int
unix timestamp) the current auth. will expire
-
expires_all
¶ A list of all things that could cause the current auth. to expire
A list of (
str
,int
unix timestamp) tuples; (reason, when).reason will be one of “config max life”, “wls life” or “inactive”.
-
before_request
()[source]¶ The “main” method
- checks if there is a response from the WLS
- checks if the current URL matches that which the WLS said it redirected to (avoid an evil admin of another site replaying successful authentications)
- checks if
flask.session
is empty - if so, then we deduce that the user has cookies disabled, and must abort immediately with 403 Forbidden, or we will start a redirect loop - checks if params matches the token we set (and saved in
flask.session
) when redirecting to Raven - checks if the authentication method used is permitted by aauth and user-interaction respected iact - if not, abort with 400 Bad Request
- updates the state with the response: updating the principal, ptags and issue information if it was a success, or clearing them (but setting a flag - see below: 401 Authentication Required will be thrown after redirect) if it was a failure
- returns a redirect that removes
WLS-Response
fromrequest.args
- checks if the “response was an authentication failure” flag is set
in
flask.session
- if so, clears the flag and aborts with 401 Authentication Required - checks to see if we are authenticated (and the session hasn’t
expired)
- if not, returns a redirect that will sends the user to the WLS to authenticate
- checks to see if the principal / ptags are permitted
- if not, aborts with a 403 Forbidden
- updates the ‘last used’ time in the state (to implement inactive_timeout)
Returns
None
, if the request should proceed to the actual view function.- checks if there is a response from the WLS
Check if an authenticated user is authorised.
The default implementation requires the principal to be in the whitelist
require_principal
(if it is notNone
, in which case any principal is allowed) and the intersection ofrequire_ptags
and ptags to be non-empty (unlessrequire_ptags
isNone
, in which case any ptags (or no ptags at all) is permitted).Note that the default value of
require_ptags
inraven.flask_glue.AuthDecorator
is{"current"}
.
- max_life (