Example #1
0
 def test_return_url(self):
     u = "http://elsewhere.example"
     with self.settings(UCAMWEBAUTH_RETURN_URL=u):
         req = (RequestFactory().get(reverse('raven_return')))
         self.assertEqual(get_return_url(req), u)
         self.client.get(reverse('raven_return'),
                         {'WLS-Response': create_wls_response()})
     self.assertIn('_auth_user_id', self.client.session)
 def test_return_url(self):
     u = "http://elsewhere.example"
     with self.settings(UCAMWEBAUTH_RETURN_URL=u):
         req = (RequestFactory().get(reverse('raven_return')))
         self.assertEqual(get_return_url(req), u)
         self.client.get(reverse('raven_return'),
                         {'WLS-Response': create_wls_response()})
     self.assertIn('_auth_user_id', self.client.session)
Example #3
0
def raven_login(request):
    # Get the Raven object and return a redirect to the Raven server
    login_url = setting('UCAMWEBAUTH_LOGIN_URL')
    return_url = get_return_url(request)
    desc = setting('UCAMWEBAUTH_DESC', default='')
    # aauth is ignored as v3 only supports 'pwd', therefore we do not need it.
    iact = setting('UCAMWEBAUTH_IACT', default='')
    msg = setting('UCAMWEBAUTH_MSG', default='')
    fail = setting('UCAMWEBAUTH_FAIL', default='')
    next_p = request.GET.get('next', None)
    if next_p is not None:
        params = urlencode([('next', next_p)])
        msg = urlencode([('ver', 3), ('url', return_url), ('desc', desc), ('iact', iact), ('msg', msg),
                         ('params', params), ('fail', fail)])
    else:
        msg = urlencode([('ver', 3), ('url', return_url), ('desc', desc), ('iact', iact), ('msg', msg),
                         ('fail', fail)])

    return HttpResponseSeeOther("%s?%s" % (login_url, msg))
Example #4
0
 def get_wls_response(self, raven_user=RAVEN_TEST_USER, raven_pwd=RAVEN_TEST_PWD, raven_ver='3',
                      raven_url=None, raven_desc='',
                      raven_aauth='pwd', raven_iact='', raven_msg='',
                      raven_params='', raven_fail='', cancel=False):
     # This request only test when raven_aauth is pwd and raven_iact is omitted
     if raven_url is None:
         raven_url = (
             get_return_url(RequestFactory().get(reverse('raven_return'))))
     if cancel:
         response = requests.post('https://demo.raven.cam.ac.uk/auth/authenticate2.html',
                                  {'userid': raven_user, 'pwd': raven_pwd, 'ver': raven_ver, 'url': raven_url,
                                   'params': raven_params, 'fail': raven_fail, 'cancel': 'Cancel'},
                                  allow_redirects=False)
     else:
         response = requests.post('https://demo.raven.cam.ac.uk/auth/authenticate2.html',
                                  {'userid': raven_user, 'pwd': raven_pwd, 'ver': raven_ver, 'url': raven_url,
                                   'params': raven_params, 'fail': raven_fail},
                                  allow_redirects=False)
     self.assertEqual(303, response.status_code)
     return unquote(response.headers['location']).split('WLS-Response=')[1]
 def get_wls_response(self, raven_user=RAVEN_TEST_USER, raven_pwd=RAVEN_TEST_PWD, raven_ver='3',
                      raven_url=None, raven_desc='',
                      raven_aauth='pwd', raven_iact='', raven_msg='',
                      raven_params='', raven_fail='', cancel=False):
     # This request only test when raven_aauth is pwd and raven_iact is omitted
     if raven_url is None:
         raven_url = (
             get_return_url(RequestFactory().get(reverse('raven_return'))))
     if cancel:
         response = requests.post('https://demo.raven.cam.ac.uk/auth/authenticate2.html',
                                  {'userid': raven_user, 'pwd': raven_pwd, 'ver': raven_ver, 'url': raven_url,
                                   'params': raven_params, 'fail': raven_fail, 'cancel': 'Cancel'},
                                  allow_redirects=False)
     else:
         response = requests.post('https://demo.raven.cam.ac.uk/auth/authenticate2.html',
                                  {'userid': raven_user, 'pwd': raven_pwd, 'ver': raven_ver, 'url': raven_url,
                                   'params': raven_params, 'fail': raven_fail},
                                  allow_redirects=False)
     self.assertEqual(303, response.status_code)
     return unquote(response.headers['location']).split('WLS-Response=')[1]
Example #6
0
def create_wls_response(raven_ver='3', raven_status='200', raven_msg='',
                        raven_issue=datetime.utcnow().strftime('%Y%m%dT%H%M%SZ'),
                        raven_id='1347296083-8278-2',
                        raven_url=None,
                        raven_principal=RAVEN_TEST_USER, raven_ptags='current',
                        raven_auth='pwd', raven_sso='', raven_life='36000',
                        raven_params='', raven_kid='901',
                        raven_key_pem=GOOD_PRIV_KEY_PEM, raven_sig_input=True):
    """Creates a valid WLS Response as the Raven test server would
    using keys from https://raven.cam.ac.uk/project/keys/demo_server/
    """
    if raven_url is None:
        raven_url = (
            get_return_url(RequestFactory().get(reverse('raven_return'))))
    raven_pkey = load_privatekey(FILETYPE_PEM, raven_key_pem)

    # This is the data which is signed by Raven with their private key
    # Note data consists of full payload with exception of kid and sig
    # source: http://raven.cam.ac.uk/project/waa2wls-protocol-3.0.txt
    wls_response_data = [raven_ver, raven_status, raven_msg,
                         raven_issue, raven_id, raven_url,
                         raven_principal, raven_ptags, raven_auth,
                         raven_sso, raven_life, raven_params]

    data = '!'.join(wls_response_data)
    raven_sig = b64encode(sign(raven_pkey, data.encode(), 'sha1'))

    # Full WLS-Response also includes the Raven-variant b64encoded sig
    # and the requisite Key ID which has been used for the signing
    # process
    wls_response_data.append(raven_kid)
    if raven_sig_input:
        wls_response_data.append(raven_sig.decode().replace("+", "-").replace("/", ".").replace("=", "_"))
    else:
        wls_response_data.append('')

    return '!'.join(map(wls_response_escape, wls_response_data))
def create_wls_response(raven_ver='3', raven_status='200', raven_msg='',
                        raven_issue=datetime.utcnow().strftime('%Y%m%dT%H%M%SZ'),
                        raven_id='1347296083-8278-2',
                        raven_url=None,
                        raven_principal=RAVEN_TEST_USER, raven_ptags='current',
                        raven_auth='pwd', raven_sso='', raven_life='36000',
                        raven_params='', raven_kid='901',
                        raven_key_pem=GOOD_PRIV_KEY_PEM, raven_sig_input=True):
    """Creates a valid WLS Response as the Raven test server would
    using keys from https://raven.cam.ac.uk/project/keys/demo_server/
    """
    if raven_url is None:
        raven_url = (
            get_return_url(RequestFactory().get(reverse('raven_return'))))
    raven_pkey = load_privatekey(FILETYPE_PEM, raven_key_pem)

    # This is the data which is signed by Raven with their private key
    # Note data consists of full payload with exception of kid and sig
    # source: http://raven.cam.ac.uk/project/waa2wls-protocol-3.0.txt
    wls_response_data = [raven_ver, raven_status, raven_msg,
                         raven_issue, raven_id, raven_url,
                         raven_principal, raven_ptags, raven_auth,
                         raven_sso, raven_life, raven_params]

    data = '!'.join(wls_response_data)
    raven_sig = b64encode(sign(raven_pkey, data.encode(), 'sha1'))

    # Full WLS-Response also includes the Raven-variant b64encoded sig
    # and the requisite Key ID which has been used for the signing
    # process
    wls_response_data.append(raven_kid)
    if raven_sig_input:
        wls_response_data.append(raven_sig.decode().replace("+", "-").replace("/", ".").replace("=", "_"))
    else:
        wls_response_data.append('')

    return '!'.join(map(wls_response_escape, wls_response_data))
Example #8
0
    def __init__(self, response_req=None):
        """Creates a RavenResponse object from the response of the Web login service (WLS) of the University of
        Cambridge
        @param response_req The HTTP request that contains the WLS response.
        """

        if response_req is None:
            raise MalformedResponseError("no request supplied")
        try:
            response_str = response_req.GET['WLS-Response']
        except KeyError:
            raise MalformedResponseError("no WLS-Response")

        # The WLS sends an authentication response message as follows:  First a 'encoded response string' is formed by
        # concatenating the values of the response fields below, in the order shown, using '!' as a separator character.
        # If the characters '!'  or '%' appear in any
        # field value they MUST be replaced by their %-encoded representation
        # before concatenation.
        # Parameters with no relevant value MUST be encoded as the empty string.
        rawtokens = response_str.split('!')
        tokens = list(map(
            unquote, rawtokens))  # return a list for python3 compatibility

        # ver: The version of the WLS protocol in use. May be the same as the 'ver' parameter
        # supplied in the request
        try:
            self.ver = int(tokens[0])
        except ValueError:
            raise MalformedResponseError(
                "Version number must be an integer, not %s" % tokens[0])
        if not 4 > self.ver > 0:
            raise MalformedResponseError("Unsupported version: %d" % self.ver)

        if self.ver == 3:
            versioni = 0
        else:
            versioni = 1

        # Check that the number of parameters in the response is correct
        if len(tokens) != (14 - versioni):
            raise MalformedResponseError(
                "Wrong number of parameters in response: expected %d, got %d" %
                ((14 - versioni), len(tokens)))

        # status: A three digit status code indicating the status of the authentication request. The list of possible
        # statuses can be seen in the STATUS dict of the RavenResponse object.
        try:
            self.status = int(tokens[1])
        except ValueError:
            raise MalformedResponseError(
                "Status code must be an integer, not %s" % tokens[1])

        if self.status not in self.STATUS:
            raise InvalidResponseError("Status returned not known")

        # msg (optional): A text message further describing the status of the authentication request,
        # suitable for display to end-user.
        self.msg = tokens[2]

        # issue: The date and time that the authentication response was created.
        try:
            self.issue = parse_time(tokens[3])
        except ValueError:
            raise MalformedResponseError(
                "Issue time is not a valid time, got %s" % tokens[3])

        # Check that the response is recent by comparing 'issue' with the current time. The WLS MUST and the WAA SHOULD
        # have their clocks synchronised by NTP or a similar mechanism. Providing the WAA has access to an
        # NTP-synchronised clock then allowing for a transmission time of 30-60 seconds is probably appropriate.
        # Otherwise allowance must be made for the maximum expected clock skew.
        if self.issue > time.time():
            raise InvalidResponseError(
                "The timestamp on the response is in the future")
        if self.issue < time.time() - setting('UCAMWEBAUTH_TIMEOUT', 30):
            raise InvalidResponseError(
                "Response has timed out - issued %s, now %s" %
                (time.asctime(time.gmtime(self.issue)), time.asctime()))

        # ident: An identifier for this response. 'ident', combined with 'issue' provides a uid for this response.
        self.ident = tokens[4]

        if self.ident == "":
            raise MalformedResponseError("Empty ID")

        # url: The value of url supplied in the authentication request and used to form the authentication response.
        self.url = tokens[5]

        # Check that 'url' represents the resource currently being
        # accessed.  The request has already been checked against
        # ALLOWED_HOSTS by Django.
        if self.url != get_return_url(response_req):
            raise InvalidResponseError(
                "The URL in the response does not match the URL expected")

        # principal: Only present if status == 200, indicates the authenticated identity of the user
        if self.status == 200:
            if tokens[6] != "":
                self.principal = tokens[6]
            else:
                raise InvalidResponseError(
                    "The username is not present in the WLS response")
        else:
            if tokens[6] != "":
                raise InvalidResponseError(
                    "The username should not be present if the status code is not 200"
                )

        # ptags (optional): A potentially empty sequence of text tokens separated by ',' indicating attributes
        # or properties of the identified principal. Possible values of this tag are not standardised and are
        # a matter for local definition by individual WLS operators (see note below). Web application agent (WAA)
        # SHOULD ignore values that they do not recognise.
        if versioni == 0:
            self.ptags = tokens[7].split(',')

        # auth (not-empty only if authentication was successfully established by interaction with the user):
        # This indicates which authentication type was used. v3 only supports 'pwd'
        self.auth = tokens[8 - versioni]

        # sso (not-empty only if 'auth' is empty): Authentication must have been established based on previous
        # successful authentication interaction(s) with the user. This indicates which authentication types were used
        # on these occasions. This value consists of a sequence of text tokens as described below, separated by ','.
        self.sso = tokens[9 - versioni].split(',')

        # life (optional): If the user has established an authenticated 'session' with the WLS, this indicates the
        # remaining life (in seconds) of that session. If present, a WAA SHOULD use this to establish an upper limit
        # to the lifetime of any session that it establishes.
        # TODO https://docs.djangoproject.com/en/dev/topics/http/sessions/#django.contrib.sessions.backends.base.SessionBase.set_expiry
        if tokens[10 - versioni] != "":
            try:
                self.life = int(tokens[10 - versioni])
            except ValueError:
                raise MalformedResponseError(
                    "Life parameter must be an integer, not %s" %
                    tokens[10 - versioni])

        # params: a copy of the params parameter from the request
        try:
            self.params = parse_qs(tokens[11 - versioni])
        except Exception:
            raise MalformedResponseError(
                "The params field contains wrong characters: %s" %
                tokens[11 - versioni])

        # REQUIRED to be a copy of the params parameter from the request
        # if self.params != setting('UCAMWEBAUTH_PARAMS', default=''):
        #     raise InvalidResponseError("The params are not equals to the request ones")

        # kid (not-empty only if 'sig' is present): A string which identifies the RSA key which was used to form the
        # signature supplied with the response. Typically these will be small integers.
        if tokens[12 - versioni] != "":
            try:
                self.kid = int(tokens[12 - versioni])
            except ValueError:
                raise MalformedResponseError(
                    "kid parameter must be an integer, not %s" %
                    tokens[12 - versioni])

        # sig (not-empty only if 'status' is 200): A public-key signature of the response data constructed from the
        # entire parameter value except 'kid' and 'sig' (and their separating ':' characters) using the private key
        # identified by 'kid', the SHA-1 hash algorithm and the 'RSASSA-PKCS1-v1_5' scheme as specified in PKCS #1 v2.1
        # [RFC 3447] and the resulting signature encoded using the base64 scheme [RFC 1521] except that the
        # characters '+', '/', and '=' are replaced by '-', '.' and '_' to reduce the URL-encoding overhead.
        if tokens[13 - versioni] != "":
            if self.kid is None:
                raise InvalidResponseError(
                    "kid must be present if signature is present")
            self.sig = decode_sig(tokens[13 - versioni])
        else:
            if self.status == 200:
                raise InvalidResponseError(
                    "Signature must be present if status is 200")

        # Check that 'kid', corresponds to a key/certificate present in the WAA. Is the only way to check the
        # signature. The WAA has to use the public key/certificate made available by the WLS.
        if (self.sig is not None) or (self.status == 200):
            try:
                cert = load_certificate(FILETYPE_PEM,
                                        settings.UCAMWEBAUTH_CERTS[self.kid])
            except Exception:
                raise PublicKeyNotFoundError(
                    "The server do not have the public key corresponding to the key the web "
                    "login service signed the response with")

            # Check that the signature matches the data supplied. To check this, the WAA uses the public key identified
            # by 'kid'.
            data = '!'.join(rawtokens[0:(12 - versioni)])
            # The data string that was signed in the WLS (everything from the  WLS-Response except 'kid' and 'sig'
            try:
                verify(cert, self.sig, data.encode(), 'sha1')
            except Exception:
                raise InvalidResponseError(
                    "The signature for this response is not valid.")

        if self.status == 200:

            # Check that 'auth' and/or 'sso' contain values acceptable to the WAA. Simply setting 'aauth' and 'iact'
            # values in an authentication request is not sufficient since an attacker could construct its own request.
            # Conversely, the WAA MUST ensure that the values of 'aauth' and/or 'iact' in its authentication requests
            # correctly reflect its requirement, to prevent the WLS sending it unacceptable responses.

            UCAMWEBAUTH_IACT = setting('UCAMWEBAUTH_IACT', '')

            # the authentication was successfully establish by interaction with the user
            if self.auth != "":
                # auth only supports 'pwd' in current version, therefore we compare it with 'pwd' only
                # If more are supported in the future, a setting will be added to specify which ones the WAA wants to
                # support and check that auth and sso match any element in this list.
                if self.auth != "pwd":
                    raise InvalidResponseError(
                        "The response used the wrong type of authentication (auth)"
                    )

                if UCAMWEBAUTH_IACT == 'no':
                    # We had required a non-interactive authentication, but didn't get one
                    raise InvalidResponseError(
                        "Non-interactive authentication required but not received"
                    )

            # authentication was established on a previous interaction(s) with the user
            else:
                if self.sso != [""]:
                    if self.sso != ["pwd"]:
                        raise InvalidResponseError(
                            "The response used the wrong type of authentication (sso)"
                        )

                    if UCAMWEBAUTH_IACT == 'yes':
                        # We had required an interactive authentication, but didn't get one
                        raise InvalidResponseError(
                            "Interactive authentication required but not received"
                        )
                else:
                    # Both auth and sso are empty, which is not allowed
                    raise MalformedResponseError(
                        "No authentication types supplied")
    def __init__(self, response_req=None):
        """Creates a RavenResponse object from the response of the Web login service (WLS) of the University of
        Cambridge
        @param response_req The HTTP request that contains the WLS response.
        """

        if response_req is None:
            raise MalformedResponseError("no request supplied")
        try:
            response_str = response_req.GET['WLS-Response']
        except KeyError:
            raise MalformedResponseError("no WLS-Response")

        # The WLS sends an authentication response message as follows:  First a 'encoded response string' is formed by
        # concatenating the values of the response fields below, in the order shown, using '!' as a separator character.
        # If the characters '!'  or '%' appear in any
        # field value they MUST be replaced by their %-encoded representation
        # before concatenation.
        # Parameters with no relevant value MUST be encoded as the empty string.
        rawtokens = response_str.split('!')
        tokens = list(map(unquote, rawtokens))  # return a list for python3 compatibility

        # ver: The version of the WLS protocol in use. May be the same as the 'ver' parameter
        # supplied in the request
        try:
            self.ver = int(tokens[0])
        except ValueError:
            raise MalformedResponseError("Version number must be an integer, not %s" % tokens[0])
        if not 4 > self.ver > 0:
            raise MalformedResponseError("Unsupported version: %d" % self.ver)

        if self.ver == 3:
            versioni = 0
        else:
            versioni = 1

        # Check that the number of parameters in the response is correct
        if len(tokens) != (14-versioni):
            raise MalformedResponseError("Wrong number of parameters in response: expected %d, got %d" %
                                         ((14-versioni), len(tokens)))

        # status: A three digit status code indicating the status of the authentication request. The list of possible
        # statuses can be seen in the STATUS dict of the RavenResponse object.
        try:
            self.status = int(tokens[1])
        except ValueError:
            raise MalformedResponseError("Status code must be an integer, not %s" % tokens[1])

        if self.status not in self.STATUS:
            raise InvalidResponseError("Status returned not known")

        # msg (optional): A text message further describing the status of the authentication request,
        # suitable for display to end-user.
        self.msg = tokens[2]

        # issue: The date and time that the authentication response was created.
        try:
            self.issue = parse_time(tokens[3])
        except ValueError:
            raise MalformedResponseError("Issue time is not a valid time, got %s" % tokens[3])

        # Check that the response is recent by comparing 'issue' with the current time. The WLS MUST and the WAA SHOULD
        # have their clocks synchronised by NTP or a similar mechanism. Providing the WAA has access to an
        # NTP-synchronised clock then allowing for a transmission time of 30-60 seconds is probably appropriate.
        # Otherwise allowance must be made for the maximum expected clock skew.
        if self.issue > time.time():
            raise InvalidResponseError("The timestamp on the response is in the future")
        if self.issue < time.time() - setting('UCAMWEBAUTH_TIMEOUT', 30):
            raise InvalidResponseError("Response has timed out - issued %s, now %s" %
                                       (time.asctime(time.gmtime(self.issue)), time.asctime()))

        # ident: An identifier for this response. 'ident', combined with 'issue' provides a uid for this response.
        self.ident = tokens[4]

        if self.ident == "":
            raise MalformedResponseError("Empty ID")

        # url: The value of url supplied in the authentication request and used to form the authentication response.
        self.url = tokens[5]

        # Check that 'url' represents the resource currently being
        # accessed.  The request has already been checked against
        # ALLOWED_HOSTS by Django.
        if self.url != get_return_url(response_req):
            raise InvalidResponseError("The URL in the response does not match the URL expected")

        # principal: Only present if status == 200, indicates the authenticated identity of the user
        if self.status == 200:
            if tokens[6] != "":
                self.principal = tokens[6]
            else:
                raise InvalidResponseError("The username is not present in the WLS response")
        else:
            if tokens[6] != "":
                raise InvalidResponseError("The username should not be present if the status code is not 200")

        # ptags (optional): A potentially empty sequence of text tokens separated by ',' indicating attributes
        # or properties of the identified principal. Possible values of this tag are not standardised and are
        # a matter for local definition by individual WLS operators (see note below). Web application agent (WAA)
        # SHOULD ignore values that they do not recognise.
        if versioni == 0:
            self.ptags = tokens[7].split(',')

        # auth (not-empty only if authentication was successfully established by interaction with the user):
        # This indicates which authentication type was used. v3 only supports 'pwd'
        self.auth = tokens[8-versioni]

        # sso (not-empty only if 'auth' is empty): Authentication must have been established based on previous
        # successful authentication interaction(s) with the user. This indicates which authentication types were used
        # on these occasions. This value consists of a sequence of text tokens as described below, separated by ','.
        self.sso = tokens[9-versioni].split(',')

        # life (optional): If the user has established an authenticated 'session' with the WLS, this indicates the
        # remaining life (in seconds) of that session. If present, a WAA SHOULD use this to establish an upper limit
        # to the lifetime of any session that it establishes.
        # TODO https://docs.djangoproject.com/en/dev/topics/http/sessions/#django.contrib.sessions.backends.base.SessionBase.set_expiry
        if tokens[10-versioni] != "":
            try:
                self.life = int(tokens[10-versioni])
            except ValueError:
                raise MalformedResponseError("Life parameter must be an integer, not %s" % tokens[10-versioni])

        # params: a copy of the params parameter from the request
        try:
            self.params = parse_qs(tokens[11-versioni])
        except Exception:
            raise MalformedResponseError("The params field contains wrong characters: %s" % tokens[11-versioni])

        # REQUIRED to be a copy of the params parameter from the request
        # if self.params != setting('UCAMWEBAUTH_PARAMS', default=''):
        #     raise InvalidResponseError("The params are not equals to the request ones")

        # kid (not-empty only if 'sig' is present): A string which identifies the RSA key which was used to form the
        # signature supplied with the response. Typically these will be small integers.
        if tokens[12-versioni] != "":
            try:
                self.kid = int(tokens[12-versioni])
            except ValueError:
                raise MalformedResponseError("kid parameter must be an integer, not %s" % tokens[12-versioni])

        # sig (not-empty only if 'status' is 200): A public-key signature of the response data constructed from the
        # entire parameter value except 'kid' and 'sig' (and their separating ':' characters) using the private key
        # identified by 'kid', the SHA-1 hash algorithm and the 'RSASSA-PKCS1-v1_5' scheme as specified in PKCS #1 v2.1
        # [RFC 3447] and the resulting signature encoded using the base64 scheme [RFC 1521] except that the
        # characters '+', '/', and '=' are replaced by '-', '.' and '_' to reduce the URL-encoding overhead.
        if tokens[13-versioni] != "":
            if self.kid is None:
                raise InvalidResponseError("kid must be present if signature is present")
            self.sig = decode_sig(tokens[13-versioni])
        else:
            if self.status == 200:
                raise InvalidResponseError("Signature must be present if status is 200")

        # Check that 'kid', corresponds to a key/certificate present in the WAA. Is the only way to check the
        # signature. The WAA has to use the public key/certificate made available by the WLS.
        if (self.sig is not None) or (self.status == 200):
            try:
                cert = load_certificate(FILETYPE_PEM, settings.UCAMWEBAUTH_CERTS[self.kid])
            except Exception:
                raise PublicKeyNotFoundError("The server do not have the public key corresponding to the key the web "
                                             "login service signed the response with")

            # Check that the signature matches the data supplied. To check this, the WAA uses the public key identified
            # by 'kid'.
            data = '!'.join(rawtokens[0:(12-versioni)])
            # The data string that was signed in the WLS (everything from the  WLS-Response except 'kid' and 'sig'
            try:
                verify(cert, self.sig, data.encode(), 'sha1')
            except Exception:
                raise InvalidResponseError("The signature for this response is not valid.")

        if self.status == 200:

            # Check that 'auth' and/or 'sso' contain values acceptable to the WAA. Simply setting 'aauth' and 'iact'
            # values in an authentication request is not sufficient since an attacker could construct its own request.
            # Conversely, the WAA MUST ensure that the values of 'aauth' and/or 'iact' in its authentication requests
            # correctly reflect its requirement, to prevent the WLS sending it unacceptable responses.

            UCAMWEBAUTH_IACT = setting('UCAMWEBAUTH_IACT', '')

            # the authentication was successfully establish by interaction with the user
            if self.auth != "":
                # auth only supports 'pwd' in current version, therefore we compare it with 'pwd' only
                # If more are supported in the future, a setting will be added to specify which ones the WAA wants to
                # support and check that auth and sso match any element in this list.
                if self.auth != "pwd":
                    raise InvalidResponseError("The response used the wrong type of authentication (auth)")

                if UCAMWEBAUTH_IACT == 'no':
                    # We had required a non-interactive authentication, but didn't get one
                    raise InvalidResponseError("Non-interactive authentication required but not received")

            # authentication was established on a previous interaction(s) with the user
            else:
                if self.sso != [""]:
                    if self.sso != ["pwd"]:
                        raise InvalidResponseError("The response used the wrong type of authentication (sso)")

                    if UCAMWEBAUTH_IACT == 'yes':
                        # We had required an interactive authentication, but didn't get one
                        raise InvalidResponseError("Interactive authentication required but not received")
                else:
                    # Both auth and sso are empty, which is not allowed
                    raise MalformedResponseError("No authentication types supplied")