"Fossies" - the Fresh Open Source Software Archive

Member "LinOTP-release-2.10.5.2/linotpd/src/linotp/lib/auth/validate.py" (13 May 2019, 32596 Bytes) of package /linux/misc/LinOTP-release-2.10.5.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "validate.py" see the Fossies "Dox" file reference documentation.

    1 # -*- coding: utf-8 -*-
    2 #
    3 #    LinOTP - the open source solution for two factor authentication
    4 #    Copyright (C) 2010 - 2019 KeyIdentity GmbH
    5 #
    6 #    This file is part of LinOTP server.
    7 #
    8 #    This program is free software: you can redistribute it and/or
    9 #    modify it under the terms of the GNU Affero General Public
   10 #    License, version 3, as published by the Free Software Foundation.
   11 #
   12 #    This program is distributed in the hope that it will be useful,
   13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
   14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   15 #    GNU Affero General Public License for more details.
   16 #
   17 #    You should have received a copy of the
   18 #               GNU Affero General Public License
   19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
   20 #
   21 #
   22 #    E-mail: linotp@keyidentity.com
   23 #    Contact: www.linotp.org
   24 #    Support: www.keyidentity.com
   25 #
   26 """ validation processing logic"""
   27 
   28 import json
   29 from hashlib import sha256
   30 from datetime import datetime
   31 
   32 from pylons.configuration import config as env
   33 
   34 from linotp.lib.auth.finishtokens import FinishTokens
   35 
   36 from linotp.lib.challenges import Challenges
   37 
   38 from linotp.lib.context import request_context as context
   39 
   40 from linotp.lib.error import ParameterError
   41 
   42 from linotp.lib.realm import getDefaultRealm
   43 
   44 from linotp.lib.resolver import getResolverObject
   45 
   46 from linotp.lib.token import TokenHandler
   47 from linotp.lib.token import get_token_owner
   48 from linotp.lib.token import getTokens4UserOrSerial
   49 from linotp.lib.token import add_last_accessed_info
   50 from linotp.tokens import tokenclass_registry
   51 
   52 from linotp.lib.user import User, getUserId, getUserInfo
   53 from linotp.lib.util import modhex_decode
   54 
   55 from linotp.lib.policy import supports_offline
   56 from linotp.lib.policy import get_auth_forward
   57 
   58 from linotp.lib.policy import disable_on_authentication_exceed
   59 from linotp.lib.policy import delete_on_authentication_exceed
   60 from linotp.lib.policy import get_pin_policies
   61 from linotp.lib.policy import get_auth_passthru
   62 from linotp.lib.policy import get_auth_passOnNoToken
   63 from linotp.lib.policy import get_auth_forward_on_no_token
   64 
   65 from linotp.lib.policy.forward import ForwardServerPolicy
   66 
   67 import logging
   68 
   69 
   70 log = logging.getLogger(__name__)
   71 
   72 def _get_otppin_mode(pin_policies):
   73     """
   74         helper to get the otppin operational mode from policies
   75     """
   76 
   77     if 0 in pin_policies or "token_pin" in pin_policies:
   78         return 0
   79 
   80     elif 1 in pin_policies or "password" in pin_policies:
   81         return 1
   82 
   83     elif 2 in pin_policies or "only_otp" in pin_policies:
   84         return 2
   85 
   86     elif 3 in pin_policies or "ignore_pin" in pin_policies:
   87         return 3
   88 
   89     return 0
   90 
   91 def check_pin(token, passw, user=None, options=None):
   92     '''
   93     check the provided pin w.r.t. the policy definition
   94 
   95     :param passw: the to be checked pass
   96     :param user: if otppin==1, this is the user, which resolver should
   97                  be checked
   98     :param options: the optional request parameters
   99 
  100     :return: boolean, if pin matched True
  101     '''
  102     res = False
  103 
  104     otppin_mode = _get_otppin_mode(get_pin_policies(user))
  105 
  106     if 1 == otppin_mode:
  107         # We check the Users Password as PIN
  108         log.debug("pin policy=1: checking the users password as pin")
  109         # this should not be the case
  110         if not options:
  111             options = {}
  112 
  113         if 'pin_match' not in options:
  114             options['pin_match'] = {}
  115 
  116         hashed_passw = sha256(passw.encode('utf-8')).hexdigest()
  117 
  118         # if password already found, we can return result again
  119         if hashed_passw in options['pin_match']:
  120             log.debug("check if password already checked! %r " %
  121                       options['pin_match'][hashed_passw])
  122             return options['pin_match'][hashed_passw]
  123 
  124         # if a password already matched, this one will fail
  125         if 'found' in options['pin_match']:
  126             log.debug("check if password already found but its not this one!")
  127             return False
  128 
  129         if user is None or not user.login:
  130             log.info("fail for pin policy == 1 with user = None")
  131             res = False
  132         else:
  133             (uid, _resolver, resolver_class) = getUserId(user)
  134             resolver = getResolverObject(resolver_class)
  135             if resolver.checkPass(uid, passw):
  136                 log.debug("Successfully authenticated user %r." % uid)
  137                 res = True
  138             else:
  139                 log.info("user %r failed to authenticate." % uid)
  140 
  141         # we register our result
  142         key = sha256(passw.encode('utf-8')).hexdigest()
  143         options['pin_match'][key] = res
  144         # and register the success, to shorten lookups after
  145         # already one positive was found
  146         if res is True:
  147             options['pin_match']['found'] = True
  148 
  149         return res
  150 
  151     elif otppin_mode == 2:
  152         # NO PIN should be entered atall
  153         log.debug("[__checkToken] pin policy=2: checking no pin")
  154         return len(passw) == 0
  155 
  156     elif otppin_mode == 3:
  157         # ignore pin or password
  158 
  159         log.debug("[__checkToken] pin policy=3: ignoreing pin")
  160 
  161         if token.type in ['spass']:
  162             return token.checkPin(passw, options=options)
  163 
  164         return True
  165 
  166     else:
  167         # old stuff: We check The fixed OTP PIN
  168         log.debug("[__checkToken] pin policy=0: checkin the PIN")
  169         return token.checkPin(passw, options=options)
  170 
  171 
  172 def check_otp(token, otpval, options=None):
  173     """
  174     check the otp value
  175 
  176     :param token: the corresponding token
  177     :param otpval: the to be checked otp value
  178     :param options: the additional request parameters
  179 
  180     :return: result of the otp check, which is
  181             the matching otpcounter or -1 if not valid
  182     """
  183 
  184     counter = token.getOtpCount()
  185     window = token.getOtpCountWindow()
  186 
  187     # ---------------------------------------------------------------------- --
  188 
  189     res = token.checkOtp(otpval, counter, window, options=options)
  190     return res
  191 
  192 
  193 def split_pin_otp(token, passw, user=None, options=None):
  194     """
  195     split the pin and the otp from the given password
  196 
  197     :param token: the corresponding token
  198     :param passw: the to be split password
  199     :param user: the token user
  200     :param options: currently not used, but might be forwarded to the
  201                     token.splitPinPass
  202     :return: tuple of (split status, pin and otpval)
  203     """
  204 
  205     otppin_mode = _get_otppin_mode(get_pin_policies(user))
  206 
  207     if 0 == otppin_mode:
  208         # old stuff: We check The fixed OTP PIN
  209         log.debug('pin policy=0: checking the PIN')
  210         (pin, otp) = token.splitPinPass(passw)
  211         return 0, pin, otp
  212 
  213     elif 1 == otppin_mode:
  214         log.debug('pin policy=1: checking the users password as pin')
  215         # split the passw into password and otp value
  216         (pin, otp) = token.splitPinPass(passw)
  217         return 1, pin, otp
  218 
  219     elif 2 == otppin_mode:
  220         # NO PIN should be entered at all
  221         log.debug('pin policy=2: checking no pin')
  222         (pin, otp) = ('', passw)
  223         token.auth_info = {'auth_info': [('pin_length', 0),
  224                                          ('otp_length', len(passw))]}
  225         return 2, pin, otp
  226 
  227     elif 3 == otppin_mode:
  228         # no pin should be checked
  229         log.debug('pin policy=3: ignoring the pin')
  230         (pin, otp) = token.splitPinPass(passw)
  231 
  232         return 3, pin, otp
  233 
  234 
  235 class ValidationHandler(object):
  236 
  237     def check_by_transactionid(self, transid, passw, options=None):
  238         """
  239         check the passw against the open transaction
  240 
  241         :param transid: the transaction id
  242         :param passw: the pass parameter
  243         :param options: the additional optional parameters
  244 
  245         :return: tuple of boolean and detail dict
  246         """
  247         reply = {}
  248 
  249         serials = []
  250         challenges = Challenges.lookup_challenges(transid=transid)
  251 
  252         for challenge in challenges:
  253             serials.append(challenge.tokenserial)
  254 
  255         if not serials:
  256             reply['value'] = False
  257             reply['failure'] = ('No challenge for transaction %r found'
  258                                 % transid)
  259 
  260             return False, reply
  261 
  262         ok = False
  263         reply['failcount'] = 0
  264         reply['value'] = False
  265         reply['token_type'] = ''
  266 
  267         token_type = options.get('token_type', None)
  268 
  269         for serial in serials:
  270 
  271             tokens = getTokens4UserOrSerial(serial=serial,
  272                                             token_type=token_type,
  273                                             read_for_update=True)
  274 
  275             if not tokens and token_type:
  276                 continue
  277 
  278             if not tokens and not token_type:
  279                 raise Exception('tokenmismatch for token serial: %s'
  280                                 % (unicode(serial)))
  281 
  282             # there could be only one
  283             token = tokens[0]
  284             owner = get_token_owner(token)
  285 
  286             (ok, opt) = self.checkTokenList(tokens, passw, user=owner,
  287                                             options=options)
  288             if opt:
  289                 reply.update(opt)
  290 
  291             reply['value'] = ok
  292             reply['token_type'] = token.getType()
  293             reply['failcount'] = token.getFailCount()
  294             reply['serial'] = token.getSerial()
  295 
  296             if ok:
  297                 break
  298 
  299         return ok, reply
  300 
  301     def checkSerialPass(self, serial, passw, options=None, user=None):
  302         """
  303         This function checks the otp for a given serial
  304 
  305         :attention: the parameter user must be set, as the pin policy==1 will
  306                     verify the user pin
  307         """
  308 
  309         token_type = options.get('token_type', None)
  310 
  311         tokenList = getTokens4UserOrSerial(None, serial, token_type=token_type,
  312                                            read_for_update=True)
  313 
  314         if passw is None:
  315             # other than zero or one token should not happen, as serial is
  316             # unique
  317             if len(tokenList) == 1:
  318                 theToken = tokenList[0]
  319                 tok = theToken.token
  320                 realms = tok.getRealmNames()
  321                 if realms is None or len(realms) == 0:
  322                     realm = getDefaultRealm()
  323                 elif len(realms) > 0:
  324                     realm = realms[0]
  325                 userInfo = getUserInfo(tok.LinOtpUserid, tok.LinOtpIdResolver,
  326                                        tok.LinOtpIdResClass)
  327                 user = User(login=userInfo.get('username'), realm=realm)
  328                 user.info = userInfo
  329 
  330                 if theToken.is_challenge_request(passw, user, options=options):
  331                     (res, opt) = Challenges.create_challenge(theToken, options)
  332                     res = False
  333                 else:
  334                     raise ParameterError('Missing parameter: pass', id=905)
  335 
  336             else:
  337                 raise Exception('No token found: '
  338                                 'unable to create challenge for %s' % serial)
  339 
  340         else:
  341             (res, opt) = self.checkTokenList(
  342                 tokenList, passw, user=user, options=options)
  343 
  344         return (res, opt)
  345 
  346     def do_request(self):
  347 
  348         return
  349 
  350     def check_status(self, transid=None, user=None, serial=None,
  351                      password=None, use_offline=False):
  352         """
  353         check for open transactions - for polling support
  354 
  355         :param transid: the transaction id where we request the status from
  356         :param user: the token owner user
  357         :param serial: or the serial we are searching for
  358         :param password: the pin/password for authorization the request
  359         :param use_offline: on success the offline info is returned
  360 
  361         :return: tuple of success and detail dict
  362         """
  363 
  364         expired, challenges = Challenges.get_challenges(token=None, transid=transid)
  365 
  366         # remove all expired challenges
  367         if expired:
  368             Challenges.delete_challenges(None, expired)
  369 
  370         if not challenges:
  371             return False, None
  372 
  373         # there is only one challenge per transaction id
  374         # if not multiple challenges, where transaction id is the parent one
  375         reply = {}
  376         involved_tokens = []
  377 
  378         transactions = {}
  379         for ch in challenges:
  380 
  381             # only look for challenges that are not compromised
  382             if not Challenges.verify_checksum(ch):
  383                 continue
  384 
  385             # is the requester authorized
  386             challenge_serial = ch.getTokenSerial()
  387             if serial and challenge_serial != serial:
  388                 continue
  389 
  390             tokens = getTokens4UserOrSerial(serial=challenge_serial)
  391             if not tokens:
  392                 continue
  393 
  394             involved_tokens.extend(tokens)
  395 
  396             # as one challenge belongs exactly to only one token,
  397             # we take this one as the token
  398             token = tokens[0]
  399             owner = get_token_owner(token)
  400 
  401             if user and user != owner:
  402                 continue
  403 
  404             involved_tokens.extend(tokens)
  405 
  406             # we only check the user password / token pin if the user
  407             # paranmeter is given
  408 
  409             if user and owner:
  410                 pin_match = check_pin(token, password, user=owner,
  411                                        options=None)
  412             else:
  413                 pin_match = token.checkPin(password)
  414 
  415             if not pin_match:
  416                 continue
  417 
  418             trans_dict = {}
  419 
  420             trans_dict['received_count'] = ch.received_count
  421             trans_dict['received_tan'] = ch.received_tan
  422             trans_dict['valid_tan'] = ch.valid_tan
  423             trans_dict['message'] = ch.challenge
  424             trans_dict['status'] = ch.getStatus()
  425 
  426             # -------------------------------------------------------------- --
  427 
  428             # extend the check status with the accept or deny of a transaction
  429 
  430             challenge_session = ch.getSession()
  431 
  432             if challenge_session:
  433 
  434                 challenge_session_dict = json.loads(challenge_session)
  435 
  436                 if 'accept' in challenge_session_dict:
  437                     trans_dict['accept'] = challenge_session_dict['accept']
  438 
  439                 if 'reject' in challenge_session_dict:
  440                     trans_dict['reject'] = challenge_session_dict['reject']
  441 
  442             # -------------------------------------------------------------- --
  443 
  444             token_dict = {'serial': token.getSerial(), 'type': token.type}
  445 
  446             # 1. check if token supports offline at all
  447             supports_offline_at_all = token.supports_offline_mode
  448 
  449             # 2. check if policy allows to use offline authentication
  450             if user is not None and user.login and user.realm:
  451                 realms = [user.realm]
  452             else:
  453                 realms = token.getRealms()
  454 
  455             offline_is_allowed = supports_offline(realms, token)
  456 
  457             if not ch.is_open() and ch.valid_tan and \
  458                supports_offline_at_all and \
  459                offline_is_allowed and \
  460                use_offline:
  461                 token_dict['offline_info'] = token.getOfflineInfo()
  462 
  463             trans_dict['token'] = token_dict
  464             transactions[ch.transid] = trans_dict
  465 
  466         if transactions:
  467             reply['transactions'] = transactions
  468 
  469         return len(reply) > 0, reply
  470 
  471     def checkUserPass(self, user, passw, options=None):
  472         """
  473         :param user: the to be identified user
  474         :param passw: the identification pass
  475         :param options: optional parameters, which are provided
  476                     to the token checkOTP / checkPass
  477 
  478         :return: tuple of True/False and optional information
  479         """
  480 
  481         # the upper layer will catch / at least should ;-)
  482 
  483         opt = None
  484         serial = None
  485         resolverClass = None
  486         uid = None
  487         audit = context['audit']
  488         user_exists = False
  489 
  490         if user is not None and not user.is_empty:
  491             # the upper layer will catch / at least should
  492             try:
  493                 (uid, _resolver, resolverClass) = getUserId(
  494                                                     user, check_existance=True)
  495                 user_exists = True
  496             except Exception as _exx:
  497                 pass_on = context.get('Config').get(
  498                     'linotp.PassOnUserNotFound', False)
  499                 if pass_on and 'true' == pass_on.lower():
  500                     audit['action_detail'] = (
  501                         'authenticated by PassOnUserNotFound')
  502                     return (True, opt)
  503                 else:
  504                     audit['action_detail'] = 'User not found'
  505                     return (False, opt)
  506 
  507         # if we have an user, check if we forward the request to another server
  508         if user_exists and get_auth_forward_on_no_token(user) is False:
  509             servers = get_auth_forward(user)
  510             if servers:
  511                 res, opt = ForwardServerPolicy.do_request(servers, env,
  512                                                           user, passw, options)
  513                 return res, opt
  514 
  515         # ------------------------------------------------------------------ --
  516 
  517         th = TokenHandler()
  518 
  519         # ------------------------------------------------------------------ --
  520 
  521         # auto asignement with otp only if user has no active token
  522 
  523         auto_assign_otp_return = th.auto_assign_otp_only(
  524                                                     otp=passw,
  525                                                     user=user,
  526                                                     options=options)
  527 
  528         if auto_assign_otp_return is True:
  529             return (True, None)
  530 
  531         # ------------------------------------------------------------------ --
  532 
  533         token_type = None
  534         if options:
  535             token_type = options.get('token_type', None)
  536 
  537         # ------------------------------------------------------------------ --
  538 
  539         # if there is a serial provided in the parameters, it overwrites the
  540         # token selection by user
  541 
  542         query_user = user
  543         if options and 'serial' in  options and options['serial']:
  544             serial = options['serial']
  545             query_user = None
  546 
  547         # ------------------------------------------------------------------ --
  548 
  549         tokenList = getTokens4UserOrSerial(
  550                                query_user,
  551                                serial,
  552                                token_type=token_type,
  553                                read_for_update=True
  554                                )
  555 
  556         if len(tokenList) == 0:
  557             audit['action_detail'] = 'User has no tokens assigned'
  558 
  559             # here we check if we should to autoassign and try to do it
  560             auto_assign_return = th.auto_assignToken(passw, user)
  561             if auto_assign_return is True:
  562                 # We can not check the token, as the OTP value is already used!
  563                 # but we will auth the user....
  564                 return (True, opt)
  565 
  566             auto_enroll_return, opt = th.auto_enrollToken(passw, user,
  567                                                           options=options)
  568             if auto_enroll_return is True:
  569                 # we always have to return a false, as
  570                 # we have a challenge tiggered
  571                 return (False, opt)
  572 
  573             pass_on = context.get('Config').get('linotp.PassOnUserNoToken',
  574                                                 False)
  575             if pass_on and 'true' == pass_on.lower():
  576                 audit['action_detail'] = 'authenticated by PassOnUserNoToken'
  577                 return (True, opt)
  578 
  579             # Check if there is an authentication policy passthru
  580 
  581             if get_auth_passthru(user):
  582                 log.debug('user %r has no token. Checking for '
  583                           'passthru in realm %r' % (user.login, user.realm))
  584                 y = getResolverObject(resolverClass)
  585                 audit['action_detail'] = 'Authenticated against Resolver'
  586                 if y.checkPass(uid, passw):
  587                     return (True, opt)
  588 
  589             # Check alternatively if there is an authentication
  590             # policy passOnNoToken
  591             elif get_auth_passOnNoToken(user):
  592                 log.info('user %r has not token. PassOnNoToken'
  593                          ' set - authenticated!')
  594                 audit['action_detail'] = (
  595                     'Authenticated by passOnNoToken policy')
  596                 return (True, opt)
  597 
  598             # if we have an user, check if we forward the request to another server
  599             elif get_auth_forward_on_no_token(user) is True:
  600                 servers = get_auth_forward(user)
  601                 if servers:
  602                     res, opt = ForwardServerPolicy.do_request(
  603                                             servers, env, user, passw, options)
  604                     return res, opt
  605 
  606             return False, opt
  607 
  608         if passw is None:
  609             raise ParameterError(u"Missing parameter:pass", id=905)
  610 
  611         (res, opt) = self.checkTokenList(
  612             tokenList, passw, user, options=options)
  613 
  614         return (res, opt)
  615 
  616     def checkTokenList(self, tokenList, passw, user=User(), options=None):
  617         """
  618         identify a matching token and test, if the token is valid, locked ..
  619         This function is called by checkSerialPass and checkUserPass to
  620 
  621         :param tokenList: list of identified tokens
  622         :param passw: the provided passw (mostly pin+otp)
  623         :param user: the identified use - as class object
  624         :param options: additional parameters, which are passed to the token
  625 
  626         :return: tuple of boolean and optional response
  627         """
  628         reply = None
  629 
  630         #  add the user to the options, so that every token could see the user
  631         if not options:
  632             options = {}
  633 
  634         options['user'] = user
  635 
  636         # if there has been one token in challenge mode, we only handle
  637         # challenges
  638 
  639         # if we got a validation against a sub_challenge, we extend this to
  640         # be a validation to all challenges of the transaction id
  641         import copy
  642         check_options = copy.deepcopy(options)
  643         state = check_options.get(
  644             'state', check_options.get('transactionid', ''))
  645         if state and '.' in state:
  646             transid = state.split('.')[0]
  647             if 'state' in check_options:
  648                 check_options['state'] = transid
  649             if 'transactionid' in check_options:
  650                 check_options['transactionid'] = transid
  651 
  652         # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  653 
  654         # transaction id optimization - part 1:
  655         #
  656         # if we have a transaction id, we check only those tokens
  657         # that belong to this transaction id:
  658 
  659         challenges = []
  660         transaction_serials = []
  661         transid = check_options.get('state',
  662                                     check_options.get('transactionid', ''))
  663         if transid:
  664             expired, challenges = Challenges.get_challenges(transid=transid,
  665                                                             filter_open=True)
  666             for challenge in challenges:
  667                 serial = challenge.tokenserial
  668                 transaction_serials.append(serial)
  669 
  670         # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  671 
  672         audit_entry = {}
  673         audit_entry['action_detail'] = "no token found!"
  674 
  675         challenge_tokens = []
  676         pin_matching_tokens = []
  677         invalid_tokens = []
  678         valid_tokens = []
  679         related_challenges = []
  680 
  681         # we have to preserve the result / reponse for token counters
  682         validation_results = {}
  683 
  684         for token in tokenList:
  685 
  686             # transaction id optimization - part 2:
  687             if transid:
  688                 if token.getSerial() not in transaction_serials:
  689                     continue
  690 
  691             audit_entry['serial'] = token.getSerial()
  692             audit_entry['token_type'] = token.getType()
  693 
  694             # preselect: the token must be in the same realm as the user
  695             if user is not None:
  696                 t_realms = token.token.getRealmNames()
  697                 u_realm = user.realm
  698                 if (len(t_realms) > 0 and len(u_realm) > 0 and
  699                         u_realm.lower() not in t_realms):
  700 
  701                     audit_entry['action_detail'] = ("Realm mismatch for "
  702                                                     "token and user")
  703 
  704                     continue
  705 
  706             # check if the token is the list of supported tokens
  707             # if not skip to the next token in list
  708             typ = token.getType()
  709             if typ.lower() not in tokenclass_registry:
  710                 log.error('token typ %r not found in tokenclasses: %r' %
  711                           (typ, tokenclass_registry.keys()))
  712                 audit_entry['action_detail'] = "Unknown Token type"
  713                 continue
  714 
  715             if not token.isActive():
  716                 audit_entry['action_detail'] = "Token inactive"
  717                 continue
  718 
  719             if token.getFailCount() >= token.getMaxFailCount():
  720                 audit_entry['action_detail'] = "Failcounter exceeded"
  721                 token.incOtpFailCounter()
  722                 continue
  723 
  724             # ---------------------------------------------------------------------- --
  725 
  726             # check for restricted path usage
  727 
  728             path = context['Path'].strip('/').partition('/')[0]
  729             token_path = token.getFromTokenInfo('scope', {}).get('path', [])
  730 
  731             if token_path and path not in token_path:
  732                 continue
  733 
  734             # -------------------------------------------------------------- --
  735 
  736             # token validity handling
  737 
  738             now = datetime.now()
  739 
  740             if (token.validity_period_start and
  741                 now < token.validity_period_start):
  742 
  743                 audit_entry['action_detail'] = ("Authentication validity "
  744                                                 "period mismatch!")
  745 
  746                 token.incOtpFailCounter()
  747 
  748                 continue
  749 
  750             token_success_excceed = (
  751                 token.count_auth_success_max > 0 and
  752                 token.count_auth_success >= token.count_auth_success_max)
  753 
  754             token_access_exceed = (
  755                 token.count_auth_max > 0 and
  756                 token.count_auth >= token.count_auth_max)
  757 
  758             token_expiry = (
  759                 token.validity_period_end and
  760                 now >= token.validity_period_end)
  761 
  762             if token_success_excceed or token_access_exceed or token_expiry:
  763 
  764                 if token_access_exceed:
  765                     msg = "Authentication counter exceeded"
  766 
  767                 if token_success_excceed:
  768                     msg = "Authentication sucess counter exceeded"
  769 
  770                 if token_expiry:
  771                     msg = "Authentication validity period exceeded!"
  772 
  773                 audit_entry['action_detail'] = msg
  774 
  775                 token.incOtpFailCounter()
  776 
  777                 # what should happen with exceeding tokens
  778 
  779                 t_realms = None
  780 
  781                 if not user.login and not user.realm:
  782                     t_realms = token.token.getRealmNames()
  783 
  784                 if disable_on_authentication_exceed(user, realms=t_realms):
  785                     token.enable(False)
  786 
  787                 if delete_on_authentication_exceed(user, realms=t_realms):
  788                     token.deleteToken()
  789 
  790                 continue
  791 
  792             # -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
  793 
  794             # start the token validation
  795 
  796             if not transid:
  797                 # if there is no transaction id given we check all token
  798                 # related challenges
  799                 (_ex_challenges,
  800                  challenges) = Challenges.get_challenges(token,
  801                                                          options=check_options,
  802                                                          filter_open=True)
  803 
  804             try:
  805                 (ret, reply) = token.check_token(
  806                     passw, user, options=check_options, challenges=challenges)
  807             except Exception as exx:
  808                 # in case of a failure during checking token, we log the error
  809                 # and continue with the next one
  810                 log.exception("checking token %r failed: %r" % (token, exx))
  811                 ret = -1
  812                 reply = "%r" % exx
  813                 audit_entry['action_detail'] = ("checking token %r "
  814                                                 "failed: %r" % (token, exx))
  815 
  816                 audit_entry['info'] = audit_entry.get('info','') + "%r" % exx
  817 
  818                 continue
  819             finally:
  820                 validation_results[token.getSerial()] = (ret, reply)
  821 
  822             (cToken, pToken, iToken, vToken) = token.get_verification_result()
  823             related_challenges.extend(token.related_challenges)
  824 
  825             challenge_tokens.extend(cToken)
  826             pin_matching_tokens.extend(pToken)
  827             invalid_tokens.extend(iToken)
  828             valid_tokens.extend(vToken)
  829 
  830         # end of token verification loop
  831         matching_challenges = []
  832         for token in valid_tokens:
  833             matching_challenges.extend(token.matching_challenges)
  834 
  835         # if there are related / sub challenges, we have to call their janitor
  836         Challenges.handle_related_challenge(matching_challenges)
  837 
  838         # now we finalize the token validation result
  839         fh = FinishTokens(valid_tokens,
  840                           challenge_tokens,
  841                           pin_matching_tokens,
  842                           invalid_tokens,
  843                           validation_results,
  844                           user, options,
  845                           audit_entry=audit_entry)
  846 
  847         (res, reply) = fh.finish_checked_tokens()
  848 
  849         # add to all tokens the last accessd time stamp
  850         add_last_accessed_info(
  851             [valid_tokens, pin_matching_tokens, challenge_tokens, valid_tokens])
  852 
  853         # now we care for all involved tokens and their challenges
  854         for token in (valid_tokens + pin_matching_tokens +
  855                       challenge_tokens + invalid_tokens):
  856             expired, _valid = Challenges.get_challenges(token)
  857             if expired:
  858                 Challenges.delete_challenges(None, expired)
  859 
  860         log.debug("Number of valid tokens found "
  861                   "(validTokenNum): %d" % len(valid_tokens))
  862 
  863         return (res, reply)
  864 
  865     def checkYubikeyPass(self, passw):
  866         """
  867         Checks the password of a yubikey in Yubico mode (44,48), where
  868         the first 12 or 16 characters are the tokenid
  869 
  870         :param passw: The password that consist of the static yubikey prefix
  871                         and the otp
  872         :type passw: string
  873 
  874         :return: True/False and the User-Object of the token owner
  875         :rtype: dict
  876         """
  877 
  878         audit = context['audit']
  879         opt = None
  880         res = False
  881 
  882         tokenList = []
  883 
  884         # strip the yubico OTP and the PIN
  885         modhex_serial = passw[:-32][-16:]
  886         try:
  887             serialnum = "UBAM" + modhex_decode(modhex_serial)
  888         except TypeError as exx:
  889             log.error("Failed to convert serialnumber: %r" % exx)
  890             return res, opt
  891 
  892         #  build list of possible yubikey tokens
  893         serials = [serialnum]
  894         for i in range(1, 3):
  895             serials.append("%s_%s" % (serialnum, i))
  896 
  897         for serial in serials:
  898             tokens = getTokens4UserOrSerial(serial=serial,
  899                                             read_for_update=True)
  900             tokenList.extend(tokens)
  901 
  902         if len(tokenList) == 0:
  903             audit['action_detail'] = (
  904                 'The serial %s could not be found!' % serialnum)
  905             return res, opt
  906 
  907         # FIXME if the Token has set a PIN and the User does not want to enter
  908         # the PIN for authentication, we need to do something different here...
  909         #  and avoid PIN checking in __checkToken.
  910         #  We could pass an "option" to __checkToken.
  911         (res, opt) = self.checkTokenList(tokenList, passw)
  912 
  913         # Now we need to get the user
  914         if res is not False and 'serial' in audit:
  915             serial = audit.get('serial', None)
  916             if serial is not None:
  917                 user = get_token_owner(tokenList[0])
  918                 audit['user'] = user.login
  919                 audit['realm'] = user.realm
  920                 opt = {'user': user.login, 'realm': user.realm}
  921 
  922         return res, opt
  923 
  924 # eof###########################################################################