コード例 #1
0
ファイル: validate.py プロジェクト: ppires/LinOTP
    def checkTokenList(self, tokenList, passw, user=User(), options=None):
        """
        identify a matching token and test, if the token is valid, locked ..
        This function is called by checkSerialPass and checkUserPass to

        :param tokenList: list of identified tokens
        :param passw: the provided passw (mostly pin+otp)
        :param user: the identified use - as class object
        :param options: additional parameters, which are passed to the token

        :return: tuple of boolean and optional response
        """
        reply = None

        #  add the user to the options, so that every token could see the user
        if not options:
            options = {}

        options['user'] = user

        # if there has been one token in challenge mode, we only handle
        # challenges

        # if we got a validation against a sub_challenge, we extend this to
        # be a validation to all challenges of the transaction id
        import copy
        check_options = copy.deepcopy(options)
        state = check_options.get(
            'state', check_options.get('transactionid', ''))
        if state and '.' in state:
            transid = state.split('.')[0]
            if 'state' in check_options:
                check_options['state'] = transid
            if 'transactionid' in check_options:
                check_options['transactionid'] = transid

        # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

        # transaction id optimization - part 1:
        #
        # if we have a transaction id, we check only those tokens
        # that belong to this transaction id:

        challenges = []
        transaction_serials = []
        transid = check_options.get('state',
                                    check_options.get('transactionid', ''))
        if transid:
            expired, challenges = Challenges.get_challenges(transid=transid,
                                                            filter_open=True)
            for challenge in challenges:
                serial = challenge.tokenserial
                transaction_serials.append(serial)

        # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

        audit_entry = {}
        audit_entry['action_detail'] = "no token found!"

        challenge_tokens = []
        pin_matching_tokens = []
        invalid_tokens = []
        valid_tokens = []
        related_challenges = []

        # we have to preserve the result / reponse for token counters
        validation_results = {}

        for token in tokenList:

            # transaction id optimization - part 2:
            if transid:
                if token.getSerial() not in transaction_serials:
                    continue

            audit_entry['serial'] = token.getSerial()
            audit_entry['token_type'] = token.getType()

            # preselect: the token must be in the same realm as the user
            if user is not None:
                t_realms = token.token.getRealmNames()
                u_realm = user.realm
                if (len(t_realms) > 0 and len(u_realm) > 0 and
                        u_realm.lower() not in t_realms):

                    audit_entry['action_detail'] = ("Realm mismatch for "
                                                    "token and user")

                    continue

            # check if the token is the list of supported tokens
            # if not skip to the next token in list
            typ = token.getType()
            if typ.lower() not in tokenclass_registry:
                log.error('token typ %r not found in tokenclasses: %r' %
                          (typ, list(tokenclass_registry.keys())))
                audit_entry['action_detail'] = "Unknown Token type"
                continue

            if not token.isActive():
                audit_entry['action_detail'] = "Token inactive"
                continue

            if token.getFailCount() >= token.getMaxFailCount():
                audit_entry['action_detail'] = "Failcounter exceeded"
                token.incOtpFailCounter()
                continue

            # ---------------------------------------------------------------------- --

            # check for restricted path usage

            path = context['Path'].strip('/').partition('/')[0]
            token_path = token.getFromTokenInfo('scope', {}).get('path', [])

            if token_path and path not in token_path:
                continue

            # -------------------------------------------------------------- --

            # token validity handling

            now = datetime.now()

            if (token.validity_period_start and
                now < token.validity_period_start):

                audit_entry['action_detail'] = ("Authentication validity "
                                                "period mismatch!")

                token.incOtpFailCounter()

                continue

            token_success_excceed = (
                token.count_auth_success_max > 0 and
                token.count_auth_success >= token.count_auth_success_max)

            token_access_exceed = (
                token.count_auth_max > 0 and
                token.count_auth >= token.count_auth_max)

            token_expiry = (
                token.validity_period_end and
                now >= token.validity_period_end)

            if token_success_excceed or token_access_exceed or token_expiry:

                if token_access_exceed:
                    msg = "Authentication counter exceeded"

                if token_success_excceed:
                    msg = "Authentication sucess counter exceeded"

                if token_expiry:
                    msg = "Authentication validity period exceeded!"

                audit_entry['action_detail'] = msg

                token.incOtpFailCounter()

                # what should happen with exceeding tokens

                t_realms = None

                if not user.login and not user.realm:
                    t_realms = token.token.getRealmNames()

                if disable_on_authentication_exceed(user, realms=t_realms):
                    token.enable(False)

                if delete_on_authentication_exceed(user, realms=t_realms):
                    token.deleteToken()

                continue

            # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

            # start the token validation

            if not transid:
                # if there is no transaction id given we check all token
                # related challenges
                (_ex_challenges,
                 challenges) = Challenges.get_challenges(token,
                                                         options=check_options,
                                                         filter_open=True)

            try:
                (ret, reply) = token.check_token(
                    passw, user, options=check_options, challenges=challenges)
            except Exception as exx:
                # in case of a failure during checking token, we log the error
                # and continue with the next one
                log.exception("checking token %r failed: %r" % (token, exx))
                ret = -1
                reply = "%r" % exx
                audit_entry['action_detail'] = ("checking token %r "
                                                "failed: %r" % (token, exx))

                audit_entry['info'] = audit_entry.get('info','') + "%r" % exx

                continue
            finally:
                validation_results[token.getSerial()] = (ret, reply)

            (cToken, pToken, iToken, vToken) = token.get_verification_result()
            related_challenges.extend(token.related_challenges)

            challenge_tokens.extend(cToken)
            pin_matching_tokens.extend(pToken)
            invalid_tokens.extend(iToken)
            valid_tokens.extend(vToken)

        valid_tokens = list(set(valid_tokens))
        invalid_tokens = list(set(invalid_tokens))
        pin_matching_tokens = list(set(pin_matching_tokens))
        challenge_tokens = list(set(challenge_tokens))

        # end of token verification loop
        matching_challenges = []
        for token in valid_tokens:
            matching_challenges.extend(token.matching_challenges)

        matching_challenges = list(set(matching_challenges))

        # if there are related / sub challenges, we have to call their janitor
        Challenges.handle_related_challenge(matching_challenges)

        # now we finalize the token validation result
        fh = FinishTokens(valid_tokens,
                          challenge_tokens,
                          pin_matching_tokens,
                          invalid_tokens,
                          validation_results,
                          user, options,
                          audit_entry=audit_entry)

        (res, reply) = fh.finish_checked_tokens()

        # ------------------------------------------------------------------ --

        # add to all tokens the last accessed time stamp

        add_last_accessed_info(valid_tokens + pin_matching_tokens +
                               challenge_tokens + invalid_tokens)

        # add time stamp to all valid tokens

        add_last_verified_info(valid_tokens)

        # ------------------------------------------------------------------ --

        # now we care for all involved tokens and their challenges

        for token in (valid_tokens + pin_matching_tokens +
                      challenge_tokens + invalid_tokens):
            expired, _valid = Challenges.get_challenges(token)
            if expired:
                Challenges.delete_challenges(None, expired)

        log.debug("Number of valid tokens found "
                  "(validTokenNum): %d" % len(valid_tokens))

        return (res, reply)
コード例 #2
0
ファイル: validate.py プロジェクト: kuffz/LinOTP
    def checkTokenList(self, tokenList, passw, user=User(), options=None):
        """
        identify a matching token and test, if the token is valid, locked ..
        This function is called by checkSerialPass and checkUserPass to

        :param tokenList: list of identified tokens
        :param passw: the provided passw (mostly pin+otp)
        :param user: the identified use - as class object
        :param options: additonal parameters, which are passed to the token

        :return: tuple of boolean and optional response
        """
        log.debug("[__checkTokenList] checking tokenlist: %r" % tokenList)
        reply = None

        tokenclasses = config['tokenclasses']

        #  add the user to the options, so that every token could see the user
        if not options:
            options = {}

        options['user'] = user

        # if there has been one token in challenge mode, we only handle challenges

        # if we got a validation against a sub_challenge, we extend this to
        # be a validation to all challenges of the transaction id
        import copy
        check_options = copy.deepcopy(options)
        state = check_options.get('state', check_options.get('transactionid', ''))
        if state and '.' in state:
            transid = state.split('.')[0]
            if 'state' in check_options:
                check_options['state'] = transid
            if 'transactionid' in check_options:
                check_options['transactionid'] = transid

        audit_entry = {}
        audit_entry['action_detail'] = "no token found!"

        challenge_tokens = []
        pin_matching_tokens = []
        invalid_tokens = []
        valid_tokens = []
        related_challenges = []

        # we have to preserve the result / reponse for token counters
        validation_results = {}

        for token in tokenList:
            log.debug('Found user with loginId %r: %r:\n',
                      token.getUserId(), token.getSerial())

            audit_entry['serial'] = token.getSerial()
            audit_entry['token_type'] = token.getType()

            # preselect: the token must be in the same realm as the user
            if user is not None:
                t_realms = token.token.getRealmNames()
                u_realm = user.getRealm()
                if (len(t_realms) > 0 and len(u_realm) > 0 and
                        u_realm.lower() not in t_realms):

                    audit_entry['action_detail'] = ("Realm mismatch for "
                                                    "token and user")

                    continue

            # check if the token is the list of supported tokens
            # if not skip to the next token in list
            typ = token.getType()
            if typ.lower() not in tokenclasses:
                log.error('token typ %r not found in tokenclasses: %r' %
                          (typ, tokenclasses))
                audit_entry['action_detail'] = "Unknown Token type"
                continue

            if not token.isActive():
                audit_entry['action_detail'] = "Token inactive"
                continue
            if token.getFailCount() >= token.getMaxFailCount():
                audit_entry['action_detail'] = "Failcounter exceeded"
                continue
            if not token.check_auth_counter():
                audit_entry['action_detail'] = "Authentication counter exceeded"
                continue
            if not token.check_validity_period():
                audit_entry['action_detail'] = "validity period mismatch"
                continue

            # start the token validation
            try:
                # are there outstanding challenges
                (_ex_challenges,
                 challenges) = Challenges.get_challenges(token,
                                                         options=check_options)

                (ret, reply) = token.check_token(
                    passw, user, options=check_options, challenges=challenges)
            except Exception as exx:
                # in case of a failure during checking token, we log the error
                # and continue with the next one
                log.exception("checking token %r failed: %r" % (token, exx))
                ret = -1
                reply = "%r" % exx
                audit_entry['action_detail'] = ("checking token %r "
                                                "failed: %r" % (token, exx))
                continue
            finally:
                validation_results[token.getSerial()] = (ret, reply)

            (cToken, pToken, iToken, vToken) = token.get_verification_result()
            related_challenges.extend(token.related_challenges)

            challenge_tokens.extend(cToken)
            pin_matching_tokens.extend(pToken)
            invalid_tokens.extend(iToken)
            valid_tokens.extend(vToken)

        # end of token verification loop

        # if there are related / sub challenges, we have to call their janitor
        Challenges.handle_related_challenge(related_challenges)

        # now we finalize the token validation result
        fh = FinishTokens(valid_tokens,
                          challenge_tokens,
                          pin_matching_tokens,
                          invalid_tokens,
                          validation_results,
                          user, options,
                          audit_entry=audit_entry)

        (res, reply) = fh.finish_checked_tokens()

        # add to all tokens the last accessd time stamp
        linotp.lib.token.add_last_accessed_info(
            [valid_tokens, pin_matching_tokens, challenge_tokens, valid_tokens])

        # now we care for all involved tokens and their challenges
        for token in (valid_tokens + pin_matching_tokens +
                      challenge_tokens + valid_tokens):
            expired, _valid = Challenges.get_challenges(token)
            if expired:
                Challenges.delete_challenges(None, expired)

        log.debug("Number of valid tokens found "
                  "(validTokenNum): %d" % len(valid_tokens))

        return (res, reply)
コード例 #3
0
    def checkTokenList(self, tokenList, passw, user=User(), options=None):
        """
        identify a matching token and test, if the token is valid, locked ..
        This function is called by checkSerialPass and checkUserPass to

        :param tokenList: list of identified tokens
        :param passw: the provided passw (mostly pin+otp)
        :param user: the identified use - as class object
        :param options: additonal parameters, which are passed to the token

        :return: tuple of boolean and optional response
        """
        log.debug("[__checkTokenList] checking tokenlist: %r" % tokenList)
        reply = None

        tokenclasses = config['tokenclasses']

        #  add the user to the options, so that every token could see the user
        if not options:
            options = {}

        options['user'] = user

        # if there has been one token in challenge mode, we only handle
        # challenges

        # if we got a validation against a sub_challenge, we extend this to
        # be a validation to all challenges of the transaction id
        import copy
        check_options = copy.deepcopy(options)
        state = check_options.get('state',
                                  check_options.get('transactionid', ''))
        if state and '.' in state:
            transid = state.split('.')[0]
            if 'state' in check_options:
                check_options['state'] = transid
            if 'transactionid' in check_options:
                check_options['transactionid'] = transid

        # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

        # transaction id optimization - part 1:
        #
        # if we have a transaction id, we check only those tokens
        # that belong to this transaction id:

        challenges = []
        transaction_serials = []
        transid = check_options.get('state',
                                    check_options.get('transactionid', ''))
        if transid:
            expired, challenges = Challenges.get_challenges(transid=transid,
                                                            filter_open=True)
            for challenge in challenges:
                serial = challenge.tokenserial
                transaction_serials.append(serial)

        # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

        audit_entry = {}
        audit_entry['action_detail'] = "no token found!"

        challenge_tokens = []
        pin_matching_tokens = []
        invalid_tokens = []
        valid_tokens = []
        related_challenges = []

        # we have to preserve the result / reponse for token counters
        validation_results = {}

        for token in tokenList:
            log.debug('Found user with loginId %r: %r:\n', token.getUserId(),
                      token.getSerial())

            # transaction id optimization - part 2:
            if transid:
                if token.getSerial() not in transaction_serials:
                    continue

            audit_entry['serial'] = token.getSerial()
            audit_entry['token_type'] = token.getType()

            # preselect: the token must be in the same realm as the user
            if user is not None:
                t_realms = token.token.getRealmNames()
                u_realm = user.getRealm()
                if (len(t_realms) > 0 and len(u_realm) > 0
                        and u_realm.lower() not in t_realms):

                    audit_entry['action_detail'] = ("Realm mismatch for "
                                                    "token and user")

                    continue

            # check if the token is the list of supported tokens
            # if not skip to the next token in list
            typ = token.getType()
            if typ.lower() not in tokenclasses:
                log.error('token typ %r not found in tokenclasses: %r' %
                          (typ, tokenclasses))
                audit_entry['action_detail'] = "Unknown Token type"
                continue

            if not token.isActive():
                audit_entry['action_detail'] = "Token inactive"
                continue

            if token.getFailCount() >= token.getMaxFailCount():
                audit_entry['action_detail'] = "Failcounter exceeded"
                token.incOtpFailCounter()
                continue

            if not token.check_auth_counter():
                audit_entry[
                    'action_detail'] = "Authentication counter exceeded"
                token.set_count_auth(token.get_count_auth() + 1)
                continue

            if not token.check_validity_period():
                audit_entry['action_detail'] = "validity period mismatch"
                token.incOtpFailCounter()
                continue

            # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

            # start the token validation

            if not transid:
                # if there is no transaction id given we check all token
                # related challenges
                (_ex_challenges,
                 challenges) = Challenges.get_challenges(token,
                                                         options=check_options,
                                                         filter_open=True)

            try:
                (ret, reply) = token.check_token(passw,
                                                 user,
                                                 options=check_options,
                                                 challenges=challenges)
            except Exception as exx:
                # in case of a failure during checking token, we log the error
                # and continue with the next one
                log.exception("checking token %r failed: %r" % (token, exx))
                ret = -1
                reply = "%r" % exx
                audit_entry['action_detail'] = ("checking token %r "
                                                "failed: %r" % (token, exx))
                continue
            finally:
                validation_results[token.getSerial()] = (ret, reply)

            (cToken, pToken, iToken, vToken) = token.get_verification_result()
            related_challenges.extend(token.related_challenges)

            challenge_tokens.extend(cToken)
            pin_matching_tokens.extend(pToken)
            invalid_tokens.extend(iToken)
            valid_tokens.extend(vToken)

        # end of token verification loop
        matching_challenges = []
        for token in valid_tokens:
            matching_challenges.extend(token.matching_challenges)

        # if there are related / sub challenges, we have to call their janitor
        Challenges.handle_related_challenge(matching_challenges)

        # now we finalize the token validation result
        fh = FinishTokens(valid_tokens,
                          challenge_tokens,
                          pin_matching_tokens,
                          invalid_tokens,
                          validation_results,
                          user,
                          options,
                          audit_entry=audit_entry)

        (res, reply) = fh.finish_checked_tokens()

        # add to all tokens the last accessd time stamp
        linotp.lib.token.add_last_accessed_info([
            valid_tokens, pin_matching_tokens, challenge_tokens, valid_tokens
        ])

        # now we care for all involved tokens and their challenges
        for token in (valid_tokens + pin_matching_tokens + challenge_tokens +
                      invalid_tokens):
            expired, _valid = Challenges.get_challenges(token)
            if expired:
                Challenges.delete_challenges(None, expired)

        log.debug("Number of valid tokens found "
                  "(validTokenNum): %d" % len(valid_tokens))

        return (res, reply)
コード例 #4
0
ファイル: validate.py プロジェクト: soitun/LinOTP
    def checkTokenList(self, tokenList, passw, user=User(), options=None):
        """
        identify a matching token and test, if the token is valid, locked ..
        This function is called by checkSerialPass and checkUserPass to

        :param tokenList: list of identified tokens
        :param passw: the provided passw (mostly pin+otp)
        :param user: the identified use - as class object
        :param options: additional parameters, which are passed to the token

        :return: tuple of boolean and optional response
        """
        reply = None

        #  add the user to the options, so that every token could see the user
        if not options:
            options = {}

        options["user"] = user

        # if there has been one token in challenge mode, we only handle
        # challenges

        # if we got a validation against a sub_challenge, we extend this to
        # be a validation to all challenges of the transaction id
        import copy

        check_options = copy.deepcopy(options)
        state = check_options.get("state",
                                  check_options.get("transactionid", ""))
        if state and "." in state:
            transid = state.split(".")[0]
            if "state" in check_options:
                check_options["state"] = transid
            if "transactionid" in check_options:
                check_options["transactionid"] = transid

        # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

        # transaction id optimization - part 1:
        #
        # if we have a transaction id, we check only those tokens
        # that belong to this transaction id:

        challenges = []
        transaction_serials = []
        transid = check_options.get("state",
                                    check_options.get("transactionid", ""))

        # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

        audit_entry = {}
        audit_entry["action_detail"] = "no token found!"

        challenge_tokens = []
        pin_matching_tokens = []
        invalid_tokens = []
        valid_tokens = []
        related_challenges = []

        # we have to preserve the result / reponse for token counters
        validation_results = {}

        for token in tokenList:

            audit_entry["serial"] = token.getSerial()
            audit_entry["token_type"] = token.getType()

            # preselect: the token must be in the same realm as the user
            if user is not None:
                t_realms = token.token.getRealmNames()
                u_realm = user.realm
                if (len(t_realms) > 0 and len(u_realm) > 0
                        and u_realm.lower() not in t_realms):

                    audit_entry[
                        "action_detail"] = "Realm mismatch for token and user"

                    continue

            # check if the token is the list of supported tokens
            # if not skip to the next token in list
            typ = token.getType()
            if typ.lower() not in tokenclass_registry:
                log.error(
                    "token typ %r not found in tokenclasses: %r",
                    typ,
                    list(tokenclass_registry.keys()),
                )
                audit_entry["action_detail"] = "Unknown Token type"
                continue

            if not token.isActive():
                audit_entry["action_detail"] = "Token inactive"
                continue

            if token.getFailCount() >= token.getMaxFailCount():
                audit_entry["action_detail"] = "Failcounter exceeded"
                token.incOtpFailCounter()
                continue

            # ---------------------------------------------------------------------- --

            # check for restricted path usage

            path = context["Path"].strip("/").partition("/")[0]
            token_path = token.getFromTokenInfo("scope", {}).get("path", [])

            if token_path and path not in token_path:
                continue

            # -------------------------------------------------------------- --

            # token validity handling

            if token.is_not_yet_valid():
                msg = "Authentication validity period mismatch!"
                audit_entry["action_detail"] = msg
                token.incOtpFailCounter()
                continue

            if not token.is_valid():
                if token.has_exceeded_usage():
                    msg = "Authentication counter exceeded"
                elif token.has_exceeded_success():
                    msg = "Authentication sucess counter exceeded"
                elif token.is_expired():
                    msg = "Authentication validity period exceeded"
                else:
                    raise Exception("Validity check failed without reason")

                audit_entry["action_detail"] = msg
                token.incOtpFailCounter()

                # what should happen with exceeding tokens

                t_realms = None

                if not user.login and not user.realm:
                    t_realms = token.token.getRealmNames()

                if disable_on_authentication_exceed(user, realms=t_realms):
                    token.enable(False)

                if delete_on_authentication_exceed(user, realms=t_realms):
                    token.deleteToken()

                continue

            # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

            # gather all open challenges for this token
            if transid:
                _expired, challenges = Challenges.get_challenges(
                    token=token, transid=transid, filter_open=True)

            else:
                # if there is no transaction id given we check all challenges
                # related to the given token

                _expired, challenges = Challenges.get_challenges(
                    token=token, filter_open=True, options=check_options)

            # -------------------------------------------------------------- --

            # finally we check the token

            try:
                (ret, reply) = token.check_token(passw,
                                                 user,
                                                 options=check_options,
                                                 challenges=challenges)

            except Exception as exx:
                # in case of a failure during checking token, we log the error
                # and continue with the next one
                log.error("checking token %r failed: %r", token, exx)
                ret = -1
                reply = "%r" % exx
                audit_entry[
                    "action_detail"] = "checking token %r failed: %r" % (token,
                                                                         exx)

                audit_entry["info"] = audit_entry.get("info", "") + "%r" % exx

                continue
            finally:
                validation_results[token.getSerial()] = (ret, reply)

            (cToken, pToken, iToken, vToken) = token.get_verification_result()
            related_challenges.extend(token.related_challenges)

            challenge_tokens.extend(cToken)
            pin_matching_tokens.extend(pToken)
            invalid_tokens.extend(iToken)
            valid_tokens.extend(vToken)

        valid_tokens = list(set(valid_tokens))
        invalid_tokens = list(set(invalid_tokens))
        pin_matching_tokens = list(set(pin_matching_tokens))
        challenge_tokens = list(set(challenge_tokens))

        # end of token verification loop
        matching_challenges = []
        for token in valid_tokens:
            matching_challenges.extend(token.matching_challenges)

        matching_challenges = list(set(matching_challenges))

        # if there are related / sub challenges, we have to call their janitor
        Challenges.handle_related_challenge(matching_challenges)

        # now we finalize the token validation result
        fh = FinishTokens(
            valid_tokens,
            challenge_tokens,
            pin_matching_tokens,
            invalid_tokens,
            validation_results,
            user,
            options,
            audit_entry=audit_entry,
        )

        (res, reply) = fh.finish_checked_tokens()

        # ------------------------------------------------------------------ --

        # add to all tokens the last accessed time stamp

        add_last_accessed_info(
            set(valid_tokens + pin_matching_tokens + challenge_tokens +
                invalid_tokens))

        # add time stamp to all valid tokens

        add_last_verified_info(valid_tokens)

        # ------------------------------------------------------------------ --

        # now we care for all involved tokens and their challenges

        for token in set(valid_tokens + pin_matching_tokens +
                         challenge_tokens + invalid_tokens):
            expired, _valid = Challenges.get_challenges(token)
            if expired:
                Challenges.delete_challenges(None, expired)

        log.debug(
            "Number of valid tokens found (validTokenNum): %d",
            len(valid_tokens),
        )

        return (res, reply)
コード例 #5
0
    def checkTokenList(self, tokenList, passw, user=User(), options=None):
        """
        identify a matching token and test, if the token is valid, locked ..
        This function is called by checkSerialPass and checkUserPass to

        :param tokenList: list of identified tokens
        :param passw: the provided passw (mostly pin+otp)
        :param user: the identified use - as class object
        :param options: additional parameters, which are passed to the token

        :return: tuple of boolean and optional response
        """
        reply = None

        #  add the user to the options, so that every token could see the user
        if not options:
            options = {}

        options['user'] = user

        # if there has been one token in challenge mode, we only handle
        # challenges

        # if we got a validation against a sub_challenge, we extend this to
        # be a validation to all challenges of the transaction id
        import copy
        check_options = copy.deepcopy(options)
        state = check_options.get(
            'state', check_options.get('transactionid', ''))
        if state and '.' in state:
            transid = state.split('.')[0]
            if 'state' in check_options:
                check_options['state'] = transid
            if 'transactionid' in check_options:
                check_options['transactionid'] = transid

        # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

        # transaction id optimization - part 1:
        #
        # if we have a transaction id, we check only those tokens
        # that belong to this transaction id:

        challenges = []
        transaction_serials = []
        transid = check_options.get('state',
                                    check_options.get('transactionid', ''))
        if transid:
            expired, challenges = Challenges.get_challenges(transid=transid,
                                                            filter_open=True)
            for challenge in challenges:
                serial = challenge.tokenserial
                transaction_serials.append(serial)

        # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

        audit_entry = {}
        audit_entry['action_detail'] = "no token found!"

        challenge_tokens = []
        pin_matching_tokens = []
        invalid_tokens = []
        valid_tokens = []
        related_challenges = []

        # we have to preserve the result / reponse for token counters
        validation_results = {}

        for token in tokenList:

            # transaction id optimization - part 2:
            if transid:
                if token.getSerial() not in transaction_serials:
                    continue

            audit_entry['serial'] = token.getSerial()
            audit_entry['token_type'] = token.getType()

            # preselect: the token must be in the same realm as the user
            if user is not None:
                t_realms = token.token.getRealmNames()
                u_realm = user.realm
                if (len(t_realms) > 0 and len(u_realm) > 0 and
                        u_realm.lower() not in t_realms):

                    audit_entry['action_detail'] = ("Realm mismatch for "
                                                    "token and user")

                    continue

            # check if the token is the list of supported tokens
            # if not skip to the next token in list
            typ = token.getType()
            if typ.lower() not in tokenclass_registry:
                log.error('token typ %r not found in tokenclasses: %r' %
                          (typ, tokenclass_registry.keys()))
                audit_entry['action_detail'] = "Unknown Token type"
                continue

            if not token.isActive():
                audit_entry['action_detail'] = "Token inactive"
                continue

            if token.getFailCount() >= token.getMaxFailCount():
                audit_entry['action_detail'] = "Failcounter exceeded"
                token.incOtpFailCounter()
                continue

            # ---------------------------------------------------------------------- --

            # check for restricted path usage

            path = context['Path'].strip('/').partition('/')[0]
            token_path = token.getFromTokenInfo('scope', {}).get('path', [])

            if token_path and path not in token_path:
                continue

            # -------------------------------------------------------------- --

            # token validity handling

            now = datetime.now()

            if (token.validity_period_start and
                now < token.validity_period_start):

                audit_entry['action_detail'] = ("Authentication validity "
                                                "period mismatch!")

                token.incOtpFailCounter()

                continue

            token_success_excceed = (
                token.count_auth_success_max > 0 and
                token.count_auth_success >= token.count_auth_success_max)

            token_access_exceed = (
                token.count_auth_max > 0 and
                token.count_auth >= token.count_auth_max)

            token_expiry = (
                token.validity_period_end and
                now >= token.validity_period_end)

            if token_success_excceed or token_access_exceed or token_expiry:

                if token_access_exceed:
                    msg = "Authentication counter exceeded"

                if token_success_excceed:
                    msg = "Authentication sucess counter exceeded"

                if token_expiry:
                    msg = "Authentication validity period exceeded!"

                audit_entry['action_detail'] = msg

                token.incOtpFailCounter()

                # what should happen with exceeding tokens

                t_realms = None

                if not user.login and not user.realm:
                    t_realms = token.token.getRealmNames()

                if disable_on_authentication_exceed(user, realms=t_realms):
                    token.enable(False)

                if delete_on_authentication_exceed(user, realms=t_realms):
                    token.deleteToken()

                continue

            # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

            # start the token validation

            if not transid:
                # if there is no transaction id given we check all token
                # related challenges
                (_ex_challenges,
                 challenges) = Challenges.get_challenges(token,
                                                         options=check_options,
                                                         filter_open=True)

            try:
                (ret, reply) = token.check_token(
                    passw, user, options=check_options, challenges=challenges)
            except Exception as exx:
                # in case of a failure during checking token, we log the error
                # and continue with the next one
                log.exception("checking token %r failed: %r" % (token, exx))
                ret = -1
                reply = "%r" % exx
                audit_entry['action_detail'] = ("checking token %r "
                                                "failed: %r" % (token, exx))

                audit_entry['info'] = audit_entry.get('info','') + "%r" % exx

                continue
            finally:
                validation_results[token.getSerial()] = (ret, reply)

            (cToken, pToken, iToken, vToken) = token.get_verification_result()
            related_challenges.extend(token.related_challenges)

            challenge_tokens.extend(cToken)
            pin_matching_tokens.extend(pToken)
            invalid_tokens.extend(iToken)
            valid_tokens.extend(vToken)

        # end of token verification loop
        matching_challenges = []
        for token in valid_tokens:
            matching_challenges.extend(token.matching_challenges)

        # if there are related / sub challenges, we have to call their janitor
        Challenges.handle_related_challenge(matching_challenges)

        # now we finalize the token validation result
        fh = FinishTokens(valid_tokens,
                          challenge_tokens,
                          pin_matching_tokens,
                          invalid_tokens,
                          validation_results,
                          user, options,
                          audit_entry=audit_entry)

        (res, reply) = fh.finish_checked_tokens()

        # add to all tokens the last accessd time stamp
        add_last_accessed_info(
            [valid_tokens, pin_matching_tokens, challenge_tokens, valid_tokens])

        # now we care for all involved tokens and their challenges
        for token in (valid_tokens + pin_matching_tokens +
                      challenge_tokens + invalid_tokens):
            expired, _valid = Challenges.get_challenges(token)
            if expired:
                Challenges.delete_challenges(None, expired)

        log.debug("Number of valid tokens found "
                  "(validTokenNum): %d" % len(valid_tokens))

        return (res, reply)