Module ucam_wls.context
Expand source code
import datetime
from . import status
from .errors import InvalidAuthRequest, ProtocolVersionUnsupported, NoMutualAuthType
from .signing import Key
from .response import AuthResponse
class AuthPrincipal:
def __init__(self, userid, auth_methods, ptags=None, session_expiry=None):
self.userid = userid
self.auth_methods = auth_methods
if ptags is None:
ptags = []
self.ptags = ptags
self.session_expiry = session_expiry
class LoginService:
"""High-level interface to implement a web login service (WLS).
This class provides a convenient interface for implementing a WLS with any
authentication backend. It is intended to be instantiated with a single
private key, which is used to sign the responses it generates.
Mechanisms deemed useful for WLS implementation are provided:
- storing the list of supported authentication methods, and checking
whether the WLS and a WAA's request have an method in common
- checking whether the protocol version specified in the WAA request is
supported by `ucam_wls`
These mechanisms can optionally be turned off.
Attributes:
key (ucam_wls.signing.Key): a private key to be used to sign responses
auth_methods (list): a list of supported authentication methods
"""
def __init__(self, key, auth_methods):
if not isinstance(key, Key):
raise TypeError("key must be a ucam_wls.signing.Key instance")
self.key = key
self.auth_methods = auth_methods
def have_mutual_auth_type(self, request):
if request.aauth and any(request.aauth):
return set(request.aauth) & set(self.auth_methods) != set()
else:
return True
def _pre_response(self, request, skip_handling_check, check_auth_types=True):
if not skip_handling_check:
if not request.data_valid:
raise InvalidAuthRequest
if check_auth_types and not self.have_mutual_auth_type(request):
raise NoMutualAuthType(
"WLS supports %s; WAA wants one of %s" % (
self.auth_methods, request.aauth
)
)
if not request.version_supported:
raise ProtocolVersionUnsupported(request.ver)
def _finish_response(self, response, sign=True, force_signature=False):
if sign or response.requires_signature:
if not response.is_signed or force_signature:
self.key.sign(response)
return response
def authenticate_active(self, request, principal, auth, life=None,
sign=True, skip_handling_check=False, *args, **kwargs):
"""Generate a WLS 'success' response based on interaction with the user
This function creates a WLS response specifying that the principal was
authenticated based on 'fresh' interaction with the user (e.g. input of
a username and password).
Args:
request (AuthRequest): the original WAA request
principal (AuthPrincipal): the principal authenticated by the WLS
auth (str): the authentication method used by the principal.
life (int): if specified, the validity (in seconds) of the
principal's session with the WLS.
sign (bool): whether to sign the response or not. Recommended to
leave this at the default value of `True` (see warning below).
*args: passed to `AuthResponse.respond_to_request`
**kwargs: passed to `AuthResponse.respond_to_request`
Returns:
An `AuthResponse` instance matching the given arguments.
Warning:
Responses indicating successful authentication *MUST* be signed by
the WLS. It is recommended that you leave `sign` set to `True`, or
make sure to sign the response manually afterwards.
"""
self._pre_response(request, skip_handling_check)
if request.iact == False:
raise ValueError("WAA demanded passive authentication (iact == 'no')")
if life is None and principal.session_expiry is not None:
life = int((principal.session_expiry - datetime.datetime.utcnow()).total_seconds())
response = AuthResponse.respond_to_request(
request=request, code=status.SUCCESS, principal=principal.userid,
auth=auth, ptags=principal.ptags, life=life, *args, **kwargs
)
return self._finish_response(response=response, sign=sign)
def authenticate_passive(self, request, principal, sso=[], sign=True,
skip_handling_check=False, *args, **kwargs):
"""Generate a WLS 'success' response based on a pre-existing identity
This function creates a WLS response specifying that the principal was
authenticated based on previous successful authentication (e.g. an
existing WLS session cookie).
Args:
request (AuthRequest): the original WAA request
principal (AuthPrincipal): the principal authenticated by the WLS
sso (list): a list of strings indicating the authentication methods
previously used for authentication by the principal. If an
empty list is passed, `principal.auth_methods` will be used.
sign (bool): whether to sign the response or not. Recommended to
leave this at the default value of `True` (see warning below).
*args: passed to `AuthResponse.respond_to_request`
**kwargs: passed to `AuthResponse.respond_to_request`
Returns:
An `AuthResponse` instance matching the given arguments.
Warning:
Responses indicating successful authentication *MUST* be signed by
the WLS. It is recommended that you leave `sign` set to `True`, or
make sure to sign the response manually afterwards.
"""
self._pre_response(request, skip_handling_check)
if request.iact == True:
raise ValueError("WAA demanded active authentication (iact == 'yes')")
if len(sso) == 0:
sso = principal.auth_methods
if len(sso) == 0:
raise ValueError("no authentication methods specified for `sso`")
if principal.session_expiry is not None:
life = int((principal.session_expiry - datetime.datetime.utcnow()).total_seconds())
else:
life = None
response = AuthResponse.respond_to_request(
request=request, code=status.SUCCESS, principal=principal.userid,
sso=sso, ptags=principal.ptags, life=life, *args, **kwargs
)
return self._finish_response(response=response, sign=sign)
def generate_failure(self, code, request, msg='', sign=True,
skip_handling_check=False, *args, **kwargs):
"""Generate a response indicating failure.
This is to be used in all cases where the outcome of user interaction
is not success. This function will refuse to handle a request where
the 'fail' parameter is 'yes' (in which case the WLS must not redirect
back to the WAA).
Args:
code (int): the response status code. Values specified in the
protocol are available as constants under `ucam_wls.status`.
request (AuthRequest): the original WAA request
msg (str): an optional message that could be shown to the end user
by the WAA
sign (bool): whether to sign the response or not.
*args: passed to `AuthResponse.respond_to_request`
**kwargs: passed to `AuthResponse.respond_to_request`
Returns:
An `AuthResponse` instance matching the given arguments.
Note:
Signatures on WLS responses indicating a non-success can optionally
be signed. In the interests of security, the default in this
function is to go ahead and sign anyway, but this can be turned off
if really desired.
"""
self._pre_response(request, skip_handling_check, check_auth_types=False)
if request.fail:
raise ValueError("WAA specified that WLS must not redirect "
"back to it on failure")
if code == status.SUCCESS:
raise ValueError("Failure responses must not have success status")
response = AuthResponse.respond_to_request(
request=request, code=code, *args, **kwargs
)
return self._finish_response(response=response, sign=sign)
Classes
class AuthPrincipal (userid, auth_methods, ptags=None, session_expiry=None)
-
Expand source code
class AuthPrincipal: def __init__(self, userid, auth_methods, ptags=None, session_expiry=None): self.userid = userid self.auth_methods = auth_methods if ptags is None: ptags = [] self.ptags = ptags self.session_expiry = session_expiry
class LoginService (key, auth_methods)
-
High-level interface to implement a web login service (WLS).
This class provides a convenient interface for implementing a WLS with any authentication backend. It is intended to be instantiated with a single private key, which is used to sign the responses it generates.
Mechanisms deemed useful for WLS implementation are provided: - storing the list of supported authentication methods, and checking whether the WLS and a WAA's request have an method in common - checking whether the protocol version specified in the WAA request is supported by
ucam_wls
These mechanisms can optionally be turned off.
Attributes
key
:Key
- a private key to be used to sign responses
auth_methods
:list
- a list of supported authentication methods
Expand source code
class LoginService: """High-level interface to implement a web login service (WLS). This class provides a convenient interface for implementing a WLS with any authentication backend. It is intended to be instantiated with a single private key, which is used to sign the responses it generates. Mechanisms deemed useful for WLS implementation are provided: - storing the list of supported authentication methods, and checking whether the WLS and a WAA's request have an method in common - checking whether the protocol version specified in the WAA request is supported by `ucam_wls` These mechanisms can optionally be turned off. Attributes: key (ucam_wls.signing.Key): a private key to be used to sign responses auth_methods (list): a list of supported authentication methods """ def __init__(self, key, auth_methods): if not isinstance(key, Key): raise TypeError("key must be a ucam_wls.signing.Key instance") self.key = key self.auth_methods = auth_methods def have_mutual_auth_type(self, request): if request.aauth and any(request.aauth): return set(request.aauth) & set(self.auth_methods) != set() else: return True def _pre_response(self, request, skip_handling_check, check_auth_types=True): if not skip_handling_check: if not request.data_valid: raise InvalidAuthRequest if check_auth_types and not self.have_mutual_auth_type(request): raise NoMutualAuthType( "WLS supports %s; WAA wants one of %s" % ( self.auth_methods, request.aauth ) ) if not request.version_supported: raise ProtocolVersionUnsupported(request.ver) def _finish_response(self, response, sign=True, force_signature=False): if sign or response.requires_signature: if not response.is_signed or force_signature: self.key.sign(response) return response def authenticate_active(self, request, principal, auth, life=None, sign=True, skip_handling_check=False, *args, **kwargs): """Generate a WLS 'success' response based on interaction with the user This function creates a WLS response specifying that the principal was authenticated based on 'fresh' interaction with the user (e.g. input of a username and password). Args: request (AuthRequest): the original WAA request principal (AuthPrincipal): the principal authenticated by the WLS auth (str): the authentication method used by the principal. life (int): if specified, the validity (in seconds) of the principal's session with the WLS. sign (bool): whether to sign the response or not. Recommended to leave this at the default value of `True` (see warning below). *args: passed to `AuthResponse.respond_to_request` **kwargs: passed to `AuthResponse.respond_to_request` Returns: An `AuthResponse` instance matching the given arguments. Warning: Responses indicating successful authentication *MUST* be signed by the WLS. It is recommended that you leave `sign` set to `True`, or make sure to sign the response manually afterwards. """ self._pre_response(request, skip_handling_check) if request.iact == False: raise ValueError("WAA demanded passive authentication (iact == 'no')") if life is None and principal.session_expiry is not None: life = int((principal.session_expiry - datetime.datetime.utcnow()).total_seconds()) response = AuthResponse.respond_to_request( request=request, code=status.SUCCESS, principal=principal.userid, auth=auth, ptags=principal.ptags, life=life, *args, **kwargs ) return self._finish_response(response=response, sign=sign) def authenticate_passive(self, request, principal, sso=[], sign=True, skip_handling_check=False, *args, **kwargs): """Generate a WLS 'success' response based on a pre-existing identity This function creates a WLS response specifying that the principal was authenticated based on previous successful authentication (e.g. an existing WLS session cookie). Args: request (AuthRequest): the original WAA request principal (AuthPrincipal): the principal authenticated by the WLS sso (list): a list of strings indicating the authentication methods previously used for authentication by the principal. If an empty list is passed, `principal.auth_methods` will be used. sign (bool): whether to sign the response or not. Recommended to leave this at the default value of `True` (see warning below). *args: passed to `AuthResponse.respond_to_request` **kwargs: passed to `AuthResponse.respond_to_request` Returns: An `AuthResponse` instance matching the given arguments. Warning: Responses indicating successful authentication *MUST* be signed by the WLS. It is recommended that you leave `sign` set to `True`, or make sure to sign the response manually afterwards. """ self._pre_response(request, skip_handling_check) if request.iact == True: raise ValueError("WAA demanded active authentication (iact == 'yes')") if len(sso) == 0: sso = principal.auth_methods if len(sso) == 0: raise ValueError("no authentication methods specified for `sso`") if principal.session_expiry is not None: life = int((principal.session_expiry - datetime.datetime.utcnow()).total_seconds()) else: life = None response = AuthResponse.respond_to_request( request=request, code=status.SUCCESS, principal=principal.userid, sso=sso, ptags=principal.ptags, life=life, *args, **kwargs ) return self._finish_response(response=response, sign=sign) def generate_failure(self, code, request, msg='', sign=True, skip_handling_check=False, *args, **kwargs): """Generate a response indicating failure. This is to be used in all cases where the outcome of user interaction is not success. This function will refuse to handle a request where the 'fail' parameter is 'yes' (in which case the WLS must not redirect back to the WAA). Args: code (int): the response status code. Values specified in the protocol are available as constants under `ucam_wls.status`. request (AuthRequest): the original WAA request msg (str): an optional message that could be shown to the end user by the WAA sign (bool): whether to sign the response or not. *args: passed to `AuthResponse.respond_to_request` **kwargs: passed to `AuthResponse.respond_to_request` Returns: An `AuthResponse` instance matching the given arguments. Note: Signatures on WLS responses indicating a non-success can optionally be signed. In the interests of security, the default in this function is to go ahead and sign anyway, but this can be turned off if really desired. """ self._pre_response(request, skip_handling_check, check_auth_types=False) if request.fail: raise ValueError("WAA specified that WLS must not redirect " "back to it on failure") if code == status.SUCCESS: raise ValueError("Failure responses must not have success status") response = AuthResponse.respond_to_request( request=request, code=code, *args, **kwargs ) return self._finish_response(response=response, sign=sign)
Methods
def authenticate_active(self, request, principal, auth, life=None, sign=True, skip_handling_check=False, *args, **kwargs)
-
Generate a WLS 'success' response based on interaction with the user
This function creates a WLS response specifying that the principal was authenticated based on 'fresh' interaction with the user (e.g. input of a username and password).
Args
request
:AuthRequest
- the original WAA request
principal
:AuthPrincipal
- the principal authenticated by the WLS
auth
:str
- the authentication method used by the principal.
life
:int
- if specified, the validity (in seconds) of the principal's session with the WLS.
sign
:bool
- whether to sign the response or not.
Recommended to
leave this at the default value of
True
(see warning below). *args
- passed to
AuthResponse.respond_to_request
**kwargs
- passed to
AuthResponse.respond_to_request
Returns
An
AuthResponse
instance matching the given arguments.Warning
Responses indicating successful authentication MUST be signed by the WLS. It is recommended that you leave
sign
set toTrue
, or make sure to sign the response manually afterwards.Expand source code
def authenticate_active(self, request, principal, auth, life=None, sign=True, skip_handling_check=False, *args, **kwargs): """Generate a WLS 'success' response based on interaction with the user This function creates a WLS response specifying that the principal was authenticated based on 'fresh' interaction with the user (e.g. input of a username and password). Args: request (AuthRequest): the original WAA request principal (AuthPrincipal): the principal authenticated by the WLS auth (str): the authentication method used by the principal. life (int): if specified, the validity (in seconds) of the principal's session with the WLS. sign (bool): whether to sign the response or not. Recommended to leave this at the default value of `True` (see warning below). *args: passed to `AuthResponse.respond_to_request` **kwargs: passed to `AuthResponse.respond_to_request` Returns: An `AuthResponse` instance matching the given arguments. Warning: Responses indicating successful authentication *MUST* be signed by the WLS. It is recommended that you leave `sign` set to `True`, or make sure to sign the response manually afterwards. """ self._pre_response(request, skip_handling_check) if request.iact == False: raise ValueError("WAA demanded passive authentication (iact == 'no')") if life is None and principal.session_expiry is not None: life = int((principal.session_expiry - datetime.datetime.utcnow()).total_seconds()) response = AuthResponse.respond_to_request( request=request, code=status.SUCCESS, principal=principal.userid, auth=auth, ptags=principal.ptags, life=life, *args, **kwargs ) return self._finish_response(response=response, sign=sign)
def authenticate_passive(self, request, principal, sso=[], sign=True, skip_handling_check=False, *args, **kwargs)
-
Generate a WLS 'success' response based on a pre-existing identity
This function creates a WLS response specifying that the principal was authenticated based on previous successful authentication (e.g. an existing WLS session cookie).
Args
request
:AuthRequest
- the original WAA request
principal
:AuthPrincipal
- the principal authenticated by the WLS
sso
:list
- a list of strings indicating the authentication methods
previously used for authentication by the principal.
If an
empty list is passed,
principal.auth_methods
will be used. sign
:bool
- whether to sign the response or not.
Recommended to
leave this at the default value of
True
(see warning below). *args
- passed to
AuthResponse.respond_to_request
**kwargs
- passed to
AuthResponse.respond_to_request
Returns
An
AuthResponse
instance matching the given arguments.Warning
Responses indicating successful authentication MUST be signed by the WLS. It is recommended that you leave
sign
set toTrue
, or make sure to sign the response manually afterwards.Expand source code
def authenticate_passive(self, request, principal, sso=[], sign=True, skip_handling_check=False, *args, **kwargs): """Generate a WLS 'success' response based on a pre-existing identity This function creates a WLS response specifying that the principal was authenticated based on previous successful authentication (e.g. an existing WLS session cookie). Args: request (AuthRequest): the original WAA request principal (AuthPrincipal): the principal authenticated by the WLS sso (list): a list of strings indicating the authentication methods previously used for authentication by the principal. If an empty list is passed, `principal.auth_methods` will be used. sign (bool): whether to sign the response or not. Recommended to leave this at the default value of `True` (see warning below). *args: passed to `AuthResponse.respond_to_request` **kwargs: passed to `AuthResponse.respond_to_request` Returns: An `AuthResponse` instance matching the given arguments. Warning: Responses indicating successful authentication *MUST* be signed by the WLS. It is recommended that you leave `sign` set to `True`, or make sure to sign the response manually afterwards. """ self._pre_response(request, skip_handling_check) if request.iact == True: raise ValueError("WAA demanded active authentication (iact == 'yes')") if len(sso) == 0: sso = principal.auth_methods if len(sso) == 0: raise ValueError("no authentication methods specified for `sso`") if principal.session_expiry is not None: life = int((principal.session_expiry - datetime.datetime.utcnow()).total_seconds()) else: life = None response = AuthResponse.respond_to_request( request=request, code=status.SUCCESS, principal=principal.userid, sso=sso, ptags=principal.ptags, life=life, *args, **kwargs ) return self._finish_response(response=response, sign=sign)
def generate_failure(self, code, request, msg='', sign=True, skip_handling_check=False, *args, **kwargs)
-
Generate a response indicating failure.
This is to be used in all cases where the outcome of user interaction is not success. This function will refuse to handle a request where the 'fail' parameter is 'yes' (in which case the WLS must not redirect back to the WAA).
Args
code
:int
- the response status code.
Values specified in the
protocol are available as constants under
ucam_wls.status
. request
:AuthRequest
- the original WAA request
msg
:str
- an optional message that could be shown to the end user by the WAA
sign
:bool
- whether to sign the response or not.
*args
- passed to
AuthResponse.respond_to_request
**kwargs
- passed to
AuthResponse.respond_to_request
Returns
An
AuthResponse
instance matching the given arguments.Note
Signatures on WLS responses indicating a non-success can optionally be signed. In the interests of security, the default in this function is to go ahead and sign anyway, but this can be turned off if really desired.
Expand source code
def generate_failure(self, code, request, msg='', sign=True, skip_handling_check=False, *args, **kwargs): """Generate a response indicating failure. This is to be used in all cases where the outcome of user interaction is not success. This function will refuse to handle a request where the 'fail' parameter is 'yes' (in which case the WLS must not redirect back to the WAA). Args: code (int): the response status code. Values specified in the protocol are available as constants under `ucam_wls.status`. request (AuthRequest): the original WAA request msg (str): an optional message that could be shown to the end user by the WAA sign (bool): whether to sign the response or not. *args: passed to `AuthResponse.respond_to_request` **kwargs: passed to `AuthResponse.respond_to_request` Returns: An `AuthResponse` instance matching the given arguments. Note: Signatures on WLS responses indicating a non-success can optionally be signed. In the interests of security, the default in this function is to go ahead and sign anyway, but this can be turned off if really desired. """ self._pre_response(request, skip_handling_check, check_auth_types=False) if request.fail: raise ValueError("WAA specified that WLS must not redirect " "back to it on failure") if code == status.SUCCESS: raise ValueError("Failure responses must not have success status") response = AuthResponse.respond_to_request( request=request, code=code, *args, **kwargs ) return self._finish_response(response=response, sign=sign)
def have_mutual_auth_type(self, request)
-
Expand source code
def have_mutual_auth_type(self, request): if request.aauth and any(request.aauth): return set(request.aauth) & set(self.auth_methods) != set() else: return True