Source code for privacyidea.lib.token.otp

# SPDX-FileCopyrightText: 2026 NetKnights GmbH <https://netknights.it>
# SPDX-License-Identifier: AGPL-3.0-or-later
"""OTP value retrieval and reverse lookup of tokens by OTP."""

import datetime
import logging


from privacyidea.lib import _
from privacyidea.lib.error import (TokenAdminError)
from privacyidea.lib.log import log_with
from privacyidea.lib.tokenclass import TokenClass

from privacyidea.lib.token.query import get_one_token


log = logging.getLogger(__name__)



[docs] @log_with(log) def get_otp(serial: str, current_time: datetime.datetime | None = None) -> tuple: """ This function returns the current OTP value for a given Token. The tokentype needs to support this function. if the token does not support getting the OTP value, a -2 is returned. If the token could not be found, ResourceNotFoundError is raised. :param serial: serial number of the token :param current_time: a fake servertime for testing of TOTP token :type current_time: datetime.datetime :return: tuple with (result, pin, otpval, passw) :rtype: tuple """ token = get_one_token(serial=serial) return token.get_otp(current_time=current_time)
[docs] @log_with(log) def get_multi_otp(serial: str, count: int = 0, epoch_start: int = 0, epoch_end: int = 0, current_time: datetime.datetime | None = None, timestamp: int | None = None) -> dict: """ This function returns a list of OTP values for the given Token. Please note, that the tokentype needs to support this function. :param serial: the serial number of the token :type serial: basestring :param count: number of the next otp values (to be used with event or time based tokens) :param epoch_start: unix time start date (used with time based tokens) :param epoch_end: unix time end date (used with time based tokens) :param current_time: Simulate the servertime :type current_time: datetime :param timestamp: Simulate the servertime (unix time in seconds) :type timestamp: int :return: dictionary of otp values :rtype: dictionary """ ret = {"result": False} token = get_one_token(serial=serial) log.debug(f"Getting multiple otp values for token {token}. curTime={current_time}") res, error, otp_dict = token.get_multi_otp(count=count, epoch_start=epoch_start, epoch_end=epoch_end, curTime=current_time, timestamp=timestamp) log.debug(f"Received {res!r}, {error!r}, and {len(otp_dict)} otp values") if res: ret = otp_dict ret["result"] = True else: ret["error"] = error return ret
[docs] @log_with(log) def get_token_by_otp(token_list: list[TokenClass], otp: str = "", window: int = 10) -> TokenClass | None: """ Search the token in the token_list, that creates the given OTP value. :param token_list: the list of token objects to be investigated :type token_list: list of token objects :param otp: the otp value, that needs to be found :type otp: basestring :param window: the window of search :type window: int :return: The token, that creates this OTP value :rtype: TokenClass """ result_token = None result_list = [] for token in token_list: log.debug(f"Checking token {token.get_serial()}") try: r = token.check_otp_exist(otp=otp, window=window) log.debug(f"Result = {int(r):d}") if r >= 0: result_list.append(token) except Exception as err: # A flaw in a single token should not stop privacyidea from finding the right token log.warning(f"Error calculating OTP for token {token.get_serial()}: {err}") if len(result_list) == 1: result_token = result_list[0] elif result_list: raise TokenAdminError(_('multiple tokens are matching this OTP value!'), id=1200) return result_token
[docs] @log_with(log) def get_serial_by_otp(token_list: list[TokenClass], otp: str = "", window: int = 10) -> str | None: """ Returns the serial for a given OTP value The token_list would be created by get_tokens() :param token_list: the list of token objects to be investigated :type token_list: list of token objects :param otp: the otp value, that needs to be found :param window: the window of search :type window: int :return: the serial for a given OTP value and the user :rtype: basestring """ serial = None token = get_token_by_otp(token_list, otp=otp, window=window) if token is not None: serial = token.get_serial() return serial
[docs] @log_with(log) def get_serial_by_otp_list(token_list: list, otp_list: list, window: int = 10, counter: int = None) -> list[str]: """ Returns a list of serials for a given list of OTP values The tokenobject_list would be created by get_tokens() :param token_list: the list of token objects to be investigated :param otp_list: a list of otp values, that need to be found :param window: the window of search :param counter: the counter value to be used for the OTP calculation, if None the actual counter of the token is used :return: a list of serials for the given OTP values and the user """ result_list = [] for otp in otp_list: for token in token_list: log.debug(f"checking token {token.get_serial()}") try: if token.type == "hotp": r = token.check_otp_exist(otp=otp, window=window, inc_counter=False, counter=counter) else: r = token.check_otp_exist(otp=otp, window=window, inc_counter=False) log.debug(f"otp_exists = {r > 0}") if r >= 0: result_list.append(token) except Exception as err: # A flaw in a single token should not stop privacyidea from finding # the right token log.warning(f"error in calculating OTP for token {token.get_serial()}: {err}") token_list = result_list result_list = [] serials = [token.get_serial() for token in token_list] return serials