Пример #1
0
    def _sendEmail(self):
        """
        Prepares the e-mail by gathering all relevant information and
        then sends it out.

        :return: A tuple of success and status_message
        :rtype: bool, string
        """
        otp = self._getNextOtp()
        email_address = self._email_address
        if not email_address:
            raise Exception("No e-mail address was defined for this token.")

        owner = get_token_owner(self)
        message = self._getEmailMessage(user=owner)

        if "<otp>" not in message:
            message = message + "<otp>"

        message = message.replace("<otp>", otp)
        message = message.replace("<serial>", self.getSerial())

        subject = self._getEmailSubject(user=owner)
        subject = subject.replace("<otp>", otp)
        subject = subject.replace("<serial>", self.getSerial())

        email_provider = loadProviderFromPolicy(provider_type='email',
                                                user=owner)
        status, status_message = email_provider.submitMessage(email_address,
                                                              subject=subject,
                                                              message=message)
        return status, status_message
Пример #2
0
    def checkYubikeyPass(self, passw):
        """
        Checks the password of a yubikey in Yubico mode (44,48), where
        the first 12 or 16 characters are the tokenid

        :param passw: The password that consist of the static yubikey prefix
                        and the otp
        :type passw: string

        :return: True/False and the User-Object of the token owner
        :rtype: dict
        """

        audit = context['audit']
        opt = None
        res = False

        tokenList = []

        # strip the yubico OTP and the PIN
        modhex_serial = passw[:-32][-16:]
        try:
            serialnum = "UBAM" + modhex_decode(modhex_serial)
        except TypeError as exx:
            log.error("Failed to convert serialnumber: %r" % exx)
            return res, opt

        #  build list of possible yubikey tokens
        serials = [serialnum]
        for i in range(1, 3):
            serials.append("%s_%s" % (serialnum, i))

        for serial in serials:
            tokens = getTokens4UserOrSerial(serial=serial,
                                            read_for_update=True)
            tokenList.extend(tokens)

        if len(tokenList) == 0:
            audit['action_detail'] = (
                'The serial %s could not be found!' % serialnum)
            return res, opt

        # FIXME if the Token has set a PIN and the User does not want to enter
        # the PIN for authentication, we need to do something different here...
        #  and avoid PIN checking in __checkToken.
        #  We could pass an "option" to __checkToken.
        (res, opt) = self.checkTokenList(tokenList, passw)

        # Now we need to get the user
        if res is not False and 'serial' in audit:
            serial = audit.get('serial', None)
            if serial is not None:
                user = get_token_owner(tokenList[0])
                audit['user'] = user.login
                audit['realm'] = user.realm
                opt = {'user': user.login, 'realm': user.realm}

        return res, opt
Пример #3
0
    def createChallenge(self, transaction_id, options):
        """
        """
        _ = context['translate']

        valid_states = ['pairing_response_received', 'pairing_complete']
        self.ensure_state_is_in(valid_states)

        if self.current_state == 'pairing_response_received':
            content_type = CONTENT_TYPE_PAIRING
            reset_url = True
        else:
            content_type = options.get('content_type')
            reset_url = False

        message = options.get('data')

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

        owner = get_token_owner(self)
        if owner and owner.login and owner.realm:
            realms = [owner.realm]
        else:
            realms = self.getRealms()

        callback_policies = ['qrtoken_challenge_callback_url',
                             'qrtoken_challenge_callback_sms']
        callback_url = get_single_auth_policy(callback_policies[0],
                                              user=owner, realms=realms)
        callback_sms = get_single_auth_policy(callback_policies[1],
                                              user=owner, realms=realms)

        if not callback_url and not callback_sms:
            raise Exception(_('Policy %s must have a value') %
                            _(" or ").join(callback_policies))

        # TODO: get from policy/config
        compression = False

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

        challenge_url, user_sig = self.create_challenge_url(transaction_id,
                                                            content_type,
                                                            message,
                                                            callback_url,
                                                            callback_sms,
                                                            compression,
                                                            reset_url)

        data = {'message': message, 'user_sig': user_sig}

        if self.current_state == 'pairing_response_received':
            self.change_state('pairing_challenge_sent')

        return (True, challenge_url, data, {})
Пример #4
0
    def _sendEmail(self):
        """
        Prepares the e-mail by gathering all relevant information and
        then sends it out.

        :return: A tuple of success and status_message
        :rtype: bool, string
        """
        otp = self._getNextOtp()
        email_address = self._email_address
        if not email_address:
            raise Exception("No e-mail address was defined for this token.")

        owner = get_token_owner(self)
        message = self._getEmailMessage(user=owner)

        if "<otp>" not in message:
            message = message + "<otp>"

        message = message.replace("<otp>", otp)
        message = message.replace("<serial>", self.getSerial())

        subject = self._getEmailSubject(user=owner)
        subject = subject.replace("<otp>", otp)
        subject = subject.replace("<serial>", self.getSerial())

        try:
            email_provider_class = self._getEmailProviderClass()
            email_provider = email_provider_class()
        except Exception as exc:
            LOG.exception("[sendEmail] Failed to load EmailProvider: %r" % exc)
            raise exc

        ## now we need the config from the env
        LOG.debug("[sendEmail] loading e-mail configuration for class %s"
                  % email_provider)
        config = self._getEmailProviderConfig()
        LOG.debug("[sendEmail] config: %r" % config)
        email_provider.loadConfig(config)
        status, status_message = email_provider.submitMessage(email_address,
                                                              subject=subject,
                                                              message=message)
        return status, status_message
Пример #5
0
def do_forward_failcounter(token):
    '''
    this function checks the for the policy

        scope=authentication,
        action=forwardtoken:no_failcounter_forwarding

    defining if the target token failcounter should be incremented / reseted

    :param serial: the token serial number, which allows to derive the
                   realm(s) and owner from
    :return: boolean
    '''
    boolean = True

    owner = get_token_owner(token)
    if owner and owner.realm:
        realms = [owner.realm]
    else:
        realms = getTokenRealms(token.getSerial())

    if not realms:
        realms = ['*']

    for realm in realms:
        params = {'scope': 'authentication',
                  'realm': realm,
                  'action': "forwardtoken:no_failcounter_forwarding"
                  }

        if owner and owner.login:
            params['user'] = owner.login

        pol = getPolicy(params)

        if pol:
            boolean = False
            break

    return boolean
Пример #6
0
    def update(self, params):

        param_keys = set(params.keys())
        init_rollout_state_keys = set(['type', 'hashlib', 'serial', '::scope::',
                                   'key_size', 'user.login', 'description',
                                   'user.realm', 'session', 'otplen', 'resConf',
                                   'user', 'realm', 'qr', 'pin'])

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

        if not param_keys.issubset(init_rollout_state_keys):

            # make sure the call aborts, if request
            # type wasn't recognized

            raise Exception('Unknown request type for token type qr')

        # if param keys are in {'type', 'hashlib'} the token is
        # initialized for the first time. this is e.g. done on the
        # manage web ui. since the token doesn't exist in the database
        # yet, its rollout state must be None (that is: they data for
        # the rollout state doesn't exist yet)

        self.ensure_state(None)

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

        # we check if callback policies are set. this must be done here
        # because the token gets saved directly after the update method
        # in the TokenHandler

        _ = context['translate']

        owner = get_token_owner(self)
        if owner and owner.login and owner.realm:
            realms = [owner.realm]
        else:
            realms = self.getRealms()

        pairing_policies = ['qrtoken_pairing_callback_url',
                            'qrtoken_pairing_callback_sms']

        cb_url = get_single_auth_policy(pairing_policies[0],
                                        user=owner, realms=realms)
        cb_sms = get_single_auth_policy(pairing_policies[1],
                                        user=owner, realms=realms)


        if not cb_url and not cb_sms:
            raise Exception(_('Policy %s must have a value') %
                            _(" or ").join(pairing_policies))

        challenge_policies = ['qrtoken_challenge_callback_url',
                              'qrtoken_challenge_callback_sms']

        cb_url = get_single_auth_policy(challenge_policies[0],
                                        user=owner, realms=realms)
        cb_sms = get_single_auth_policy(challenge_policies[1],
                                        user=owner, realms=realms)

        if not cb_url and not cb_sms:
            raise Exception(_('Policy %s must have a value') %
                            _(" or ").join(challenge_policies))

        partition = get_partition(realms, owner)
        self.addToTokenInfo('partition', partition)

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

        # we set the the active state of the token to False, because
        # it should not be allowed to use it for validation before the
        # pairing process is done

        self.token.LinOtpIsactive = False

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

        if 'otplen' not in params:
            params['otplen'] = getFromConfig("QRTokenOtpLen", 8)

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

        TokenClass.update(self, params, reset_failcount=True)
Пример #7
0
    def sendSMS(self, message=None, transactionid=None):
        '''
        send sms

        :param message: the sms submit message - could contain placeholders
         like <otp> or <serial>
        :type message: string

        :return: submitted message
        :rtype: string

        '''

        success = None

        if not message:
            message = "<otp>"

        if not SMSPROVIDER_IMPORTED:
            raise Exception("The SMSProvider could not be imported. Maybe you "
                            "didn't install the package (Debian "
                            "linotp-smsprovider or PyPI SMSProvider)")

        # we require the token owner to get the phone number and the provider
        owner = get_token_owner(self)

        phone = self.get_mobile_number(owner)

        otp = self.getNextOtp()
        serial = self.getSerial()

        if '<otp>' not in message:
            log.error('Message unconfigured: prepending <otp> to message')
            if isinstance(message, str):
                message = "<otp> %s" % message
            else:
                message = "<otp> %r" % message

        message = message.replace("<otp>", otp)
        message = message.replace("<serial>", serial)

        if transactionid:
            message = message.replace("<transactionid>", transactionid)

        log.debug("[sendSMS] sending SMS to phone number %s " % phone)

        realm = None
        realms = self.getRealms()
        if realms:
            realm = realms[0]

        # we require the token owner to get the phone number and the provider
        owner = get_token_owner(self)
        if not owner or not owner.login:
            log.warning("[sendSMS] Missing required token owner")

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

        # load providers for the user

        providers = get_provider_from_policy('sms', realm=realm, user=owner)

        # remember if at least one provider could be accessed
        available = False

        res_scheduler = ResourceScheduler(tries=1, uri_list=providers)
        for provider_name in next(res_scheduler):

            sms_provider = loadProvider('sms', provider_name=provider_name)

            if not sms_provider:
                raise Exception('unable to load provider')

            try:

                success = sms_provider.submitMessage(phone, message)

                available = True
                break

            except ProviderNotAvailable as exx:
                log.error('Provider not available %r', provider_name)
                res_scheduler.block(provider_name, delay=30)

        if not available:
            raise AllResourcesUnavailable("unable to connect to any "
                                          "SMSProvider %r" % providers)

        log.debug("[sendSMS] message submitted")

        # # after submit set validity time
        self.setValidUntil()

        return success, message
Пример #8
0
    def submitChallenge(self, options=None):
        '''
        submit the sms message - former method name was checkPin

        :param options: the request options context

        :return: tuple of success and message
        '''

        _ = context['translate']

        res = 0

        fallback_realm = (self.getRealms() or [None])[0]
        login, realm = get_user_from_options(
            options,
            fallback_user=get_token_owner(self),
            fallback_realm=fallback_realm)

        message = options.get('challenge', "<otp>")
        result = _("sending sms failed")

        # it is configurable, if sms should be triggered by a valid pin
        send_by_PIN = getFromConfig("sms.sendByPin") or True

        # if the token is not active or there is no pin triggered sending, we leave
        if not (self.isActive() is True and send_by_PIN is True):
            return res, result

        counter = self.getOtpCount()
        log.debug("[submitChallenge] counter=%r" % counter)

        # At this point we MUST NOT bail out in case of an
        # Gateway error, since checkPIN is successful, as the bail
        # out would cancel the checking of the other tokens
        try:
            sms_ret = False
            new_message = None

            sms_ret, new_message = get_auth_smstext(user=login, realm=realm)
            if sms_ret:
                message = new_message

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

            # if there is a data or message part in the request, it might
            # overrule the given smstext

            if 'data' in options or 'message' in options:

                # if there is an enforce policy
                # we do not allow the owerwrite

                enforce = enforce_smstext(user=login, realm=realm)

                if not enforce:
                    message = options.get('data',
                                          options.get('message', '<otp>'))

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

            # fallback if no message is defined

            if not message:
                message = "<otp>"

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

            # submit the sms message

            transactionid = options.get('transactionid', None)
            res, result = self.sendSMS(message=message,
                                       transactionid=transactionid)

            self.info['info'] = "SMS sent: %r" % res
            log.debug('SMS sent: %s', result)

            return res, result

        except Exception as e:
            # The PIN was correct, but the SMS could not be sent.
            self.info['info'] = str(e)
            info = ("The SMS could not be sent: %r" % e)
            log.warning("[submitChallenge] %s", info)
            return False, info

        finally:
            # we increment the otp in any case, independend if sending
            # of the sms was sucsessful
            self.incOtpCounter(counter, reset=False)
Пример #9
0
    def createChallenge(self, transaction_id, options):
        """
        """
        _ = context['translate']

        valid_states = ['pairing_response_received', 'pairing_complete']
        self.ensure_state_is_in(valid_states)

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

        if self.current_state == 'pairing_response_received':
            content_type = CONTENT_TYPE_PAIRING
            reset_url = True
        else:

            content_type_as_str = options.get('content_type')
            reset_url = False

            if content_type_as_str is None:
                content_type = None
            else:
                try:
                    # pylons silently converts all ints in json
                    # to unicode :(
                    content_type = int(content_type_as_str)
                except:
                    raise ValueError('Unrecognized content type: %s' %
                                     content_type_as_str)

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

        message = options.get('data')

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

        owner = get_token_owner(self)
        if owner and owner.login and owner.realm:
            realms = [owner.realm]
        else:
            realms = self.getRealms()

        callback_policies = [
            'qrtoken_challenge_callback_url', 'qrtoken_challenge_callback_sms'
        ]
        callback_url = get_single_auth_policy(callback_policies[0],
                                              user=owner,
                                              realms=realms)
        callback_sms = get_single_auth_policy(callback_policies[1],
                                              user=owner,
                                              realms=realms)

        if not callback_url and not callback_sms:
            raise Exception(
                _('Policy %s must have a value') %
                _(" or ").join(callback_policies))

        # TODO: get from policy/config
        compression = False

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

        challenge_url, user_sig = self.create_challenge_url(
            transaction_id, content_type, message, callback_url, callback_sms,
            compression, reset_url)

        data = {'message': message, 'user_sig': user_sig}

        if self.current_state == 'pairing_response_received':
            self.change_state('pairing_challenge_sent')

        return (True, challenge_url, data, {})
Пример #10
0
    def getInitDetail(self, params, user=None):

        _ = context['translate']
        response_detail = {}

        param_keys = set(params.keys())
        init_rollout_state_keys = set([
            'type', 'hashlib', 'serial', '::scope::', 'key_size', 'user.login',
            'description', 'user.realm', 'session', 'otplen', 'pin', 'resConf',
            'user', 'realm', 'qr'
        ])

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

        if param_keys.issubset(init_rollout_state_keys):

            # collect data used for generating the pairing url

            serial = self.getSerial()
            # for qrtoken hashlib is ignored
            hash_algorithm = None
            otp_pin_length = int(self.getOtpLen())

            owner = get_token_owner(self)
            if owner and owner.login and owner.realm:
                realms = [owner.realm]
                user = owner
            else:
                realms = self.getRealms()

            pairing_policies = [
                'qrtoken_pairing_callback_url', 'qrtoken_pairing_callback_sms'
            ]

            # it is guaranteed, that either cb_url or cb_sms has a value
            # because we checked it in the update method

            cb_url = get_single_auth_policy(pairing_policies[0],
                                            user=owner,
                                            realms=realms)
            cb_sms = get_single_auth_policy(pairing_policies[1],
                                            user=owner,
                                            realms=realms)

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

            partition = self.getFromTokenInfo('partition')

            # FIXME: certificate usage

            pairing_url = generate_pairing_url(token_type='qr',
                                               partition=partition,
                                               serial=serial,
                                               callback_url=cb_url,
                                               callback_sms_number=cb_sms,
                                               otp_pin_length=otp_pin_length,
                                               hash_algorithm=hash_algorithm,
                                               use_cert=False)

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

            self.addToInfo('pairing_url', pairing_url)
            response_detail['pairing_url'] = pairing_url

            # create response tabs
            response_detail['lse_qr_url'] = {
                'description': _('QRToken Pairing Url'),
                'img': create_img(pairing_url, width=250),
                'order': 0,
                'value': pairing_url
            }
            response_detail['lse_qr_cert'] = {
                'description': _('QRToken Certificate'),
                'img': create_img(pairing_url, width=250),
                'order': 1,
                'value': pairing_url
            }

            response_detail['serial'] = self.getSerial()

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

        else:

            # make sure the call aborts, if request
            # type wasn't recognized

            raise Exception('Unknown request type for token type qr')

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

        self.change_state('pairing_url_sent')

        return response_detail
Пример #11
0
    def setPin(self):
        """
        method:
            api/helpdesk/setPin

        description:
            This function sets the PIN of the token

        arguments:
            * serial     - required
            * pin        - optional - uses random pin instead

        returns:
            an array with the list of affected serial numbers

        """
        res = {}

        try:
            params = self.request_params

            serial = params.get("serial")
            if not serial:
                raise ParameterError("Missing parameter: 'serial'")

            tokens = getTokens4UserOrSerial(serial=serial)

            result = []

            for token in tokens:
                owner = get_token_owner(token)
                current_serial = token.getSerial()

                pin = params.get(
                    'pin', createRandomPin(owner, min_pin_length=6))

                # as the parameter pin in the params is evaluated by
                # the checkPolicyPre and checkPolicyPost we need to put the
                # parameter pin and current_serial into the params
                params['pin'] = pin
                params['serial'] = current_serial

                # set pin is done by the admin/set with the parameter pin
                checkPolicyPre(
                    'admin', method='set', param=params, user=owner)

                token.setPin(pin)

                # while in the pre checks for method='set' the post checks
                # for 'setPin' which is used to determin if a new pin has to
                # be generated

                res = checkPolicyPost(
                    'admin', 'setPin', param=params, user=owner)
                pin = res.get('new_pin', pin)

                info = {
                    'message': ('A new pin ${Pin} has been set for your '
                                'token: ${serial}'),
                    'Subject': 'new pin set for token ${serial}',
                    'Pin': pin,
                    'serial': current_serial,
                }

                notify_user(owner, 'setPin', info, required=True)

                result.append(serial)

            c.audit['success'] = True
            c.audit['info'] = result

            Session.commit()
            return sendResult(response, result)

        except PolicyException as pex:
            log.exception('[setPin] policy failed %r')
            Session.rollback()
            return sendError(response, pex, 1)

        except Exception as exx:
            log.exception('[setPin] error while setting pin')
            Session.rollback()
            return sendError(response, exx, 0)

        finally:
            Session.close()
Пример #12
0
    def getInitDetail(self, params, user=None):

        _ = context['translate']
        response_detail = {}

        param_keys = set(params.keys())
        init_rollout_state_keys = {'type', 'hashlib', 'serial', '::scope::',
                                   'key_size', 'user.login', 'description',
                                   'user.realm', 'session', 'otplen', 'pin',
                                   'resConf', 'user', 'realm', 'qr'}

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

        if param_keys.issubset(init_rollout_state_keys):

            # collect data used for generating the pairing url

            serial = self.getSerial()
            # for qrtoken hashlib is ignored
            hash_algorithm = None
            pub_key = get_qrtoken_public_key()
            otp_pin_length = int(self.getOtpLen())

            owner = get_token_owner(self)
            if owner and owner.login and owner.realm:
                realms = [owner.realm]
                user = owner
            else:
                realms = self.getRealms()

            pairing_policies = ['qrtoken_pairing_callback_url',
                                'qrtoken_pairing_callback_sms']

            # it is guaranteed, that either cb_url or cb_sms has a value
            # because we checked it in the update method

            cb_url = get_single_auth_policy(pairing_policies[0],
                                            user=owner, realms=realms)
            cb_sms = get_single_auth_policy(pairing_policies[1],
                                            user=owner, realms=realms)

            cert_id = get_pairing_certificate_id(realms=realms, user=user)

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

            pairing_url = generate_pairing_url('qrtoken',
                                               server_public_key=pub_key,
                                               serial=serial,
                                               callback_url=cb_url,
                                               callback_sms_number=cb_sms,
                                               otp_pin_length=otp_pin_length,
                                               hash_algorithm=hash_algorithm,
                                               cert_id=cert_id)

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

            self.addToInfo('pairing_url', pairing_url)
            response_detail['pairing_url'] = pairing_url

            # create response tabs
            response_detail['lse_qr_url'] = {
                'description': _('QRToken Pairing Url'),
                'img': create_img(pairing_url, width=250),
                'order': 0,
                'value': pairing_url}
            response_detail['lse_qr_cert'] = {
                'description': _('QRToken Certificate'),
                'img': create_img(pairing_url, width=250),
                'order': 1,
                'value': pairing_url}

            response_detail['serial'] = self.getSerial()

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

        else:

            # make sure the call aborts, if request
            # type wasn't recognized

            raise Exception('Unknown request type for token type qr')

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

        self.change_state('pairing_url_sent')

        return response_detail
Пример #13
0
    def createChallenge(self, transaction_id, options):
        """
        create a challenge - either for pairing or challenges when the token is
        activated.

        we support re-activation by the means that if we are in the state
        'pairing_challenge_sent' the activation challenge could be triggered
        again

        :param transaction_id: scope of the challenge, will become part of the
                               challenge url code
        :param options: the request optional parameters
        :return: tuple with (True, challenge_url, data, {})
                 whereby the data is a dict with
                   {'message': message, 'user_sig': user_sig}
        """

        _ = context['translate']

        valid_states = [
            'pairing_response_received', 'pairing_challenge_sent',
            'pairing_complete'
        ]
        self.ensure_state_is_in(valid_states)

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

        if self.current_state in [
                'pairing_challenge_sent', 'pairing_response_received'
        ]:

            content_type = CONTENT_TYPE_PAIRING
            reset_url = True

        elif self.current_state == 'pairing_complete':

            content_type_as_str = options.get('content_type')
            reset_url = False

            if content_type_as_str is None:
                content_type = None
            else:
                try:
                    # pylons silently converts all ints in json
                    # to unicode :(
                    content_type = int(content_type_as_str)
                except:
                    raise ValueError('Unrecognized content type: %s' %
                                     content_type_as_str)

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

        message = options.get('data')

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

        owner = get_token_owner(self)
        if owner and owner.login and owner.realm:
            realms = [owner.realm]
        else:
            realms = self.getRealms()

        callback_policies = [
            'qrtoken_challenge_callback_url', 'qrtoken_challenge_callback_sms'
        ]
        callback_url = get_single_auth_policy(callback_policies[0],
                                              user=owner,
                                              realms=realms)
        callback_sms = get_single_auth_policy(callback_policies[1],
                                              user=owner,
                                              realms=realms)

        if not callback_url and not callback_sms:
            raise Exception(
                _('Policy %s must have a value') %
                _(" or ").join(callback_policies))

        # TODO: get from policy/config
        compression = False

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

        challenge_url, user_sig = self.create_challenge_url(
            transaction_id, content_type, message, callback_url, callback_sms,
            compression, reset_url)

        data = {'message': message, 'user_sig': user_sig}

        if self.current_state == 'pairing_response_received':
            self.change_state('pairing_challenge_sent')

        return (True, challenge_url, data, {})
Пример #14
0
def janitor_to_remove_enrollment_token(valid_tokens):
    """
    remove all enrollment only tokens
    """
    # ------------------------------------------------------------------ --

    # get all owners for the valid tokens

    all_owners = set()

    for token in valid_tokens:

        # if the authenticated token is a rollout token, we dont count him

        path = token.getFromTokenInfo('scope', {}).get('path', [])
        if len(path) == 1 and path[0] == 'userservice':
            continue

        # TODO: get owner sadly throws a genric exception in case of
        # no intersection beteen token realms and user realms :(
        try:
            owner = get_token_owner(token)
        except Exception:
            continue

        if owner:
            all_owners.add(owner)

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

    # get all rollout only tokens per owner

    to_be_removed_tokens = []

    for owner in all_owners:

        # should be purge the tokens of the user? <- defined by policy

        if not purge_enrollment_token(user=owner):
            continue

        user_tokens = getTokens4UserOrSerial(user=owner)

        # if there is only one token either
        # - the user has still the rollout: do not delete
        # - or the user has a new one: we dont delete either

        if len(user_tokens) < 2:
            continue

        for token in user_tokens:
            path = token.getFromTokenInfo('scope', {}).get('path', [])
            if len(path) == 1 and path[0] == 'userservice':
                to_be_removed_tokens.append(token)

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

    # delete all rollout tokens

    serials = []
    for token in to_be_removed_tokens:
        serials.append(token.getSerial())
        remove_token(token)

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

    # add info about the purging

    audit = context['audit']

    if serials:
        audit['info'] += 'purged rollout tokens:' + ', '.join(serials)
Пример #15
0
def janitor_to_remove_enrollment_token(valid_tokens):
    """
    remove all enrollment only tokens
    """
    # ------------------------------------------------------------------ --

    # get all owners for the valid tokens

    all_owners = set()

    for token in valid_tokens:

        # if the authenticated token is a rollout token, we dont count him

        path = token.getFromTokenInfo('scope', {}).get('path', [])
        if len(path) == 1 and path[0] == 'userservice':
            continue

        # TODO: get owner sadly throws a genric exception in case of
        # no intersection beteen token realms and user realms :(
        try:
            owner = get_token_owner(token)
        except Exception:
            continue

        if owner:
            all_owners.add(owner)

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

    # get all rollout only tokens per owner

    to_be_removed_tokens = []

    for owner in all_owners:

        # should be purge the tokens of the user? <- defined by policy

        if not purge_enrollment_token(user=owner):
            continue

        user_tokens = getTokens4UserOrSerial(user=owner)

        # if there is only one token either
        # - the user has still the rollout: do not delete
        # - or the user has a new one: we dont delete either

        if len(user_tokens) < 2:
            continue

        for token in user_tokens:
            path = token.getFromTokenInfo('scope', {}).get('path', [])
            if len(path) == 1 and path[0] == 'userservice':
                to_be_removed_tokens.append(token)

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

    # delete all rollout tokens

    serials = []
    for token in to_be_removed_tokens:
        serials.append(token.getSerial())
        remove_token(token)

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

    # add info about the purging

    audit = context['audit']

    if serials:
        audit['info'] += 'purged rollout tokens:' + ', '.join(serials)
Пример #16
0
    def sendSMS(self, message=None, transactionid=None):
        '''
        send sms

        :param message: the sms submit message - could contain placeholders
         like <otp> or <serial>
        :type message: string

        :return: submitted message
        :rtype: string

        '''

        ret = None

        if not message:
            message = "<otp>"

        if not SMSPROVIDER_IMPORTED:
            raise Exception("The SMSProvider could not be imported. Maybe you "
                            "didn't install the package (Debian "
                            "linotp-smsprovider or PyPI SMSProvider)")

        # we require the token owner to get the phone number and the provider
        owner = get_token_owner(self)

        phone = self.get_mobile_number(owner)

        otp = self.getNextOtp()
        serial = self.getSerial()

        if '<otp>' not in message:
            log.error('Message unconfigured: prepending <otp> to message')
            if isinstance(message, basestring):
                message = "<otp> %s" % message
            else:
                message = "<otp> %r" % message

        message = message.replace("<otp>", otp)
        message = message.replace("<serial>", serial)

        if transactionid:
            message = message.replace("<transactionid>", transactionid)

        log.debug("[sendSMS] sending SMS to phone number %s " % phone)

        realm = None
        realms = self.getRealms()
        if realms:
            realm = realms[0]

        # we require the token owner to get the phone number and the provider
        owner = get_token_owner(self)
        if not owner or not owner.login:
            log.warning("[sendSMS] Missing required token owner")

        sms_provider = loadProviderFromPolicy(provider_type='sms',
                                              realm=realm,
                                              user=owner)

        if not sms_provider:
            raise Exception('unable to load provider')

        ret = sms_provider.submitMessage(phone, message)

        if not ret:
            raise Exception("Failed to submit message")
        log.debug("[sendSMS] message submitted")

        # # after submit set validity time
        self.setValidUntil()

        # return OTP for selftest purposes
        return ret, message
Пример #17
0
    def check_by_transactionid(self, transid, passw, options=None):
        """
        check the passw against the open transaction

        :param transid: the transaction id
        :param passw: the pass parameter
        :param options: the additional optional parameters

        :return: tuple of boolean and detail dict
        """
        reply = {}

        serials = []
        challenges = Challenges.lookup_challenges(transid=transid,
                                                  read_for_update=True)

        for challenge in challenges:
            serials.append(challenge.tokenserial)

        if not serials:
            reply['value'] = False
            reply['failure'] = ('No challenge for transaction %r found' %
                                transid)

            return False, reply

        ok = False
        reply['failcount'] = 0
        reply['value'] = False
        reply['token_type'] = ''

        token_type = options.get('token_type', None)

        for serial in serials:

            tokens = getTokens4UserOrSerial(serial=serial,
                                            token_type=token_type,
                                            read_for_update=True)

            if not tokens and token_type:
                continue

            if not tokens and not token_type:
                raise Exception('tokenmismatch for token serial: %s' %
                                (unicode(serial)))

            # there could be only one
            token = tokens[0]
            owner = get_token_owner(token)

            (ok, opt) = self.checkTokenList(tokens,
                                            passw,
                                            user=owner,
                                            options=options)
            if opt:
                reply.update(opt)

            reply['token_type'] = token.getType()
            reply['failcount'] = token.getFailCount()
            reply['value'] = ok

            if ok:
                break

        return ok, reply
Пример #18
0
    def sendSMS(self, message=None, transactionid=None):
        '''
        send sms

        :param message: the sms submit message - could contain placeholders
         like <otp> or <serial>
        :type message: string

        :return: submitted message
        :rtype: string

        '''
        log.debug("[sendSMS] begin. process the submitting of "
                                              "the sms message %r" % (message))

        ret = None

        if not message:
            message = "<otp>"

        if not SMSPROVIDER_IMPORTED:
            raise Exception("The SMSProvider could not be imported. Maybe you "
                            "didn't install the package (Debian "
                            "linotp-smsprovider or PyPI SMSProvider)")

        phone = self.getPhone()
        otp = self.getNextOtp()
        serial = self.getSerial()

        if '<otp>' not in message:
            log.error('Message unconfigured: prepending <otp> to message')
            if isinstance(message, basestring):
                message = "<otp> %s" % message
            else:
                message = "<otp> %r" % message

        message = message.replace("<otp>", otp)
        message = message.replace("<serial>", serial)

        if transactionid:
            message = message.replace("<transactionid>", transactionid)

        log.debug("[sendSMS] sending SMS to phone number %s " % phone)

        owner = get_token_owner(self)

        sms_provider = loadProviderFromPolicy(provider_type='sms', user=owner)

        if not sms_provider:
            raise Exception('unable to load provider')

        log.debug("[sendSMS] submitMessage: %r, to phone %r", message, phone)
        ret = sms_provider.submitMessage(phone, message)

        if not ret:
            raise Exception("Failed to submit message")
        log.debug("[sendSMS] message submitted")

        # # after submit set validity time
        self.setValidUntil()

        # return OTP for selftest purposes
        log.debug("[sendSMS] end. sms message submitted: message %r"
                                                                % (message))
        return ret, message
Пример #19
0
    def check_status(self, transid=None, user=None, serial=None,
                     password=None, use_offline=False):
        """
        check for open transactions - for polling support

        :param transid: the transaction id where we request the status from
        :param user: the token owner user
        :param serial: or the serial we are searching for
        :param password: the pin/password for authorization the request
        :param use_offline: on success the offline info is returned

        :return: tuple of success and detail dict
        """

        expired, challenges = Challenges.get_challenges(token=None, transid=transid)

        # remove all expired challenges
        if expired:
            Challenges.delete_challenges(None, expired)

        if not challenges:
            return False, None

        # there is only one challenge per transaction id
        # if not multiple challenges, where transaction id is the parent one
        reply = {}
        involved_tokens = []

        transactions = {}
        for ch in challenges:

            # only look for challenges that are not compromised
            if not Challenges.verify_checksum(ch):
                continue

            # is the requester authorized
            challenge_serial = ch.getTokenSerial()
            if serial and challenge_serial != serial:
                continue

            tokens = getTokens4UserOrSerial(serial=challenge_serial)
            if not tokens:
                continue

            involved_tokens.extend(tokens)

            # as one challenge belongs exactly to only one token,
            # we take this one as the token
            token = tokens[0]
            owner = get_token_owner(token)

            if user and user != owner:
                continue

            involved_tokens.extend(tokens)

            # we only check the user password / token pin if the user
            # paranmeter is given

            if user and owner:
                pin_match = check_pin(token, password, user=owner,
                                       options=None)
            else:
                pin_match = token.checkPin(password)

            if not pin_match:
                continue

            trans_dict = {}

            trans_dict['received_count'] = ch.received_count
            trans_dict['received_tan'] = ch.received_tan
            trans_dict['valid_tan'] = ch.valid_tan
            trans_dict['message'] = ch.challenge
            trans_dict['status'] = ch.getStatus()

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

            # extend the check status with the accept or deny of a transaction

            challenge_session = ch.getSession()

            if challenge_session:

                challenge_session_dict = json.loads(challenge_session)

                if 'accept' in challenge_session_dict:
                    trans_dict['accept'] = challenge_session_dict['accept']

                if 'reject' in challenge_session_dict:
                    trans_dict['reject'] = challenge_session_dict['reject']

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

            token_dict = {'serial': token.getSerial(), 'type': token.type}

            # 1. check if token supports offline at all
            supports_offline_at_all = token.supports_offline_mode

            # 2. check if policy allows to use offline authentication
            if user is not None and user.login and user.realm:
                realms = [user.realm]
            else:
                realms = token.getRealms()

            offline_is_allowed = supports_offline(realms, token)

            if not ch.is_open() and ch.valid_tan and \
               supports_offline_at_all and \
               offline_is_allowed and \
               use_offline:
                token_dict['offline_info'] = token.getOfflineInfo()

            trans_dict['token'] = token_dict
            transactions[ch.transid] = trans_dict

        if transactions:
            reply['transactions'] = transactions

        return len(reply) > 0, reply
Пример #20
0
    def update(self, params):

        param_keys = set(params.keys())
        init_rollout_state_keys = {'type', 'hashlib', 'serial', '::scope::',
                                   'key_size', 'user.login', 'description',
                                   'user.realm', 'session', 'otplen', 'resConf',
                                   'user', 'realm', 'qr', 'pin'}

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

        if not param_keys.issubset(init_rollout_state_keys):

            # make sure the call aborts, if request
            # type wasn't recognized

            raise Exception('Unknown request type for token type qr')

        # if param keys are in {'type', 'hashlib'} the token is
        # initialized for the first time. this is e.g. done on the
        # manage web ui. since the token doesn't exist in the database
        # yet, its rollout state must be None (that is: they data for
        # the rollout state doesn't exist yet)

        self.ensure_state(None)

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

        # we check if callback policies are set. this must be done here
        # because the token gets saved directly after the update method
        # in the TokenHandler

        _ = context['translate']

        owner = get_token_owner(self)
        if owner and owner.login and owner.realm:
            realms = [owner.realm]
        else:
            realms = self.getRealms()

        pairing_policies = ['qrtoken_pairing_callback_url',
                            'qrtoken_pairing_callback_sms']

        cb_url = get_single_auth_policy(pairing_policies[0],
                                        user=owner, realms=realms)
        cb_sms = get_single_auth_policy(pairing_policies[1],
                                        user=owner, realms=realms)


        if not cb_url and not cb_sms:
            raise Exception(_('Policy %s must have a value') %
                            _(" or ").join(pairing_policies))

        challenge_policies = ['qrtoken_challenge_callback_url',
                              'qrtoken_challenge_callback_sms']

        cb_url = get_single_auth_policy(challenge_policies[0],
                                        user=owner, realms=realms)
        cb_sms = get_single_auth_policy(challenge_policies[1],
                                        user=owner, realms=realms)

        if not cb_url and not cb_sms:
            raise Exception(_('Policy %s must have a value') %
                            _(" or ").join(pairing_policies))

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

        # we set the the active state of the token to False, because
        # it should not be allowed to use it for validation before the
        # pairing process is done

        self.token.LinOtpIsactive = False

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

        if 'otplen' not in params:
            params['otplen'] = getFromConfig("QRTokenOtpLen", 8)

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

        TokenClass.update(self, params, reset_failcount=True)
Пример #21
0
    def getInitDetail(self, params, user=None):

        """
        returns initialization details in the enrollment process
        (gets called after update method). used here to pass the
        pairing url to the user

        :param params: parameters provided by the client

        :param user: (unused)

        :raises TokenStateError: If token state is not 'initialized'

        :returns: a dict consisting of a 'pairing_url' entry, containing
            the pairing url and a 'pushtoken_pairing_url' entry containing
            a data structure used in the manage frontend in the enrollment
            process
        """

        _ = context['translate']
        response_detail = {}

        self.ensure_state('initialized')

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

        # collect data used for generating the pairing url

        serial = self.getSerial()

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

        owner = get_token_owner(self)
        if owner and owner.login and owner.realm:
            realms = [owner.realm]
        else:
            realms = self.getRealms()

        # it is guaranteed, that cb_url has a value
        # because we checked it in the update method

        cb_url = get_single_auth_policy('pushtoken_pairing_callback_url',
                                        user=owner, realms=realms)

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

        partition = self.getFromTokenInfo('partition')

        # FIXME: certificate usage

        pairing_url = generate_pairing_url(token_type='push',
                                           partition=partition,
                                           serial=serial,
                                           callback_url=cb_url,
                                           use_cert=False)

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

        self.addToInfo('pairing_url', pairing_url)
        response_detail['pairing_url'] = pairing_url

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

        # add response tabs (used in the manage view on enrollment)

        response_detail['lse_qr_url'] = {
            'description': _('Pairing URL'),
            'img': create_img(pairing_url, width=250),
            'order': 0,
            'value': pairing_url}

        response_detail['serial'] = self.getSerial()

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

        self.change_state('unpaired')

        return response_detail
Пример #22
0
    def createChallenge(self, transaction_id, options):

        """
        entry hook for the challenge logic. when this function is called
        a challenge with an transaction was created.

        :param transaction_id: A unique transaction id used to identity
            the challenge object

        :param options: additional options as a dictionary

        :raises TokenStateError: If token state is not 'active' or
            'pairing_response_received'

        :returns: A tuple (success, message, data, attributes)
            with success being a boolean indicating if the call
            to this method was successful, message being a string
            that is passed to the user, attributes being additional
            output data (unused in here)
        """
        _ = context['translate']

        valid_states = ['active',
                        'pairing_response_received',

                        # we support re activation
                        # as long as the token is not active
                        'pairing_challenge_sent',
                        ]

        self.ensure_state_is_in(valid_states)

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

        # inside the challenge url we sent a callback url for the client
        # which is defined by an authentication policy

        owner = get_token_owner(self)
        if owner and owner.login and owner.realm:
            realms = [owner.realm]
        else:
            realms = self.getRealms()

        callback_policy_name = 'pushtoken_challenge_callback_url'
        callback_url = get_single_auth_policy(callback_policy_name,
                                              user=owner, realms=realms)

        if not callback_url:
            raise Exception(_('Policy pushtoken_challenge_callback_url must '
                              'have a value'))

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

        # load and configure provider

        # the realm logic was taken from the
        # provider loading in the smstoken class
        # TODO: refactor & centralize logic

        realm = None
        if realms:
            realm = realms[0]

        push_provider = loadProviderFromPolicy(provider_type='push',
                                               realm=realm,
                                               user=owner)

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

        if self.current_state in ['pairing_response_received',
                                  'pairing_challenge_sent']:

            content_type = CONTENT_TYPE_PAIRING

            message = ''
            challenge_url, sig_base = self.create_challenge_url(transaction_id,
                                                                content_type,
                                                                callback_url)

        elif self.current_state in ['active']:

            content_type_as_str = options.get(
                'content_type', CONTENT_TYPE_SIGNREQ)

            try:

                # pylons silently converts all ints in json
                # to unicode :(

                content_type = int(content_type_as_str)

            except:

                raise ValueError('Unrecognized content type: %s'
                                 % content_type_as_str)

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

            if content_type == CONTENT_TYPE_SIGNREQ:

                message = options.get('data')
                challenge_url, sig_base = self.create_challenge_url(
                                                transaction_id, content_type,
                                                callback_url, message=message)

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

            elif content_type == CONTENT_TYPE_LOGIN:

                message = options.get('data')
                login, __, host = message.partition('@')

                challenge_url, sig_base = self.create_challenge_url(
                                                transaction_id, content_type,
                                                callback_url, login=login,
                                                host=host)

            else:

                raise ValueError('Unrecognized content type: %s' % content_type)

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

        # send the challenge_url to the push notification proxy

        token_info = self.getTokenInfo()
        gda = token_info['gda']

        log.debug("pushing notification: %r : %r", challenge_url, gda)

        success, response = push_provider.push_notification(
                                                challenge_url,
                                                gda,
                                                transaction_id)

        if not success:
            raise Exception('push mechanism failed. response was %r'
                            % response)

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

        # we save sig_base in the challenge data, because we need it in
        # checkOtp to verify the signature

        b64_sig_base = b64encode(sig_base)
        data = {'sig_base': b64_sig_base}

        if self.current_state == 'pairing_response_received':
            self.change_state('pairing_challenge_sent')

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

        # don't pass the challenge_url as message to the user

        return (True, '', data, {})
Пример #23
0
    def check_status(self,
                     transid=None,
                     user=None,
                     serial=None,
                     password=None,
                     use_offline=False):
        """
        check for open transactions - for polling support

        :param transid: the transaction id where we request the status from
        :param user: the token owner user
        :param serial: or the serial we are searching for
        :param password: the pin/password for authorization the request
        :param use_offline: on success the offline info is returned

        :return: tuple of success and detail dict
        """

        expired, challenges = Challenges.get_challenges(token=None,
                                                        transid=transid)

        # remove all expired challenges
        if expired:
            Challenges.delete_challenges(None, expired)

        if not challenges:
            return False, None

        # there is only one challenge per transaction id
        # if not multiple challenges, where transaction id is the parent one
        reply = {}
        involved_tokens = []

        transactions = {}
        for ch in challenges:

            # only look for challenges that are not compromised
            if not Challenges.verify_checksum(ch):
                continue

            # is the requester authorized
            challenge_serial = ch.getTokenSerial()
            if serial and challenge_serial != serial:
                continue

            tokens = getTokens4UserOrSerial(serial=challenge_serial)
            if not tokens:
                continue

            involved_tokens.extend(tokens)

            # as one challenge belongs exactly to only one token,
            # we take this one as the token
            token = tokens[0]
            owner = get_token_owner(token)

            if user and user != owner:
                continue

            involved_tokens.extend(tokens)

            # we only check the user password / token pin if the user
            # paranmeter is given

            if user and owner:
                pin_match = check_pin(token,
                                      password,
                                      user=owner,
                                      options=None)
            else:
                pin_match = token.checkPin(password)

            if not pin_match:
                continue

            trans_dict = {}

            trans_dict['received_count'] = ch.received_count
            trans_dict['received_tan'] = ch.received_tan
            trans_dict['valid_tan'] = ch.valid_tan
            trans_dict['message'] = ch.challenge
            trans_dict['status'] = ch.getStatus()

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

            # extend the check status with the accept or deny of a transaction

            challenge_session = ch.getSession()

            if challenge_session:

                challenge_session_dict = json.loads(challenge_session)

                if 'accept' in challenge_session_dict:
                    trans_dict['accept'] = challenge_session_dict['accept']

                if 'reject' in challenge_session_dict:
                    trans_dict['reject'] = challenge_session_dict['reject']

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

            token_dict = {'serial': token.getSerial(), 'type': token.type}

            # 1. check if token supports offline at all
            supports_offline_at_all = token.supports_offline_mode

            # 2. check if policy allows to use offline authentication
            if user is not None and user.login and user.realm:
                realms = [user.realm]
            else:
                realms = token.getRealms()

            offline_is_allowed = supports_offline(realms, token)

            if not ch.is_open() and ch.valid_tan and \
               supports_offline_at_all and \
               offline_is_allowed and \
               use_offline:
                token_dict['offline_info'] = token.getOfflineInfo()

            trans_dict['token'] = token_dict
            transactions[ch.transid] = trans_dict

        if transactions:
            reply['transactions'] = transactions

        return len(reply) > 0, reply
Пример #24
0
    def update(self, params):

        """
        initialization entry hook for the enrollment process.

        :param params: parameters provided by the client

        :raises Exception: If the client supplied unrecognized
            configuration parameters for this token type

        :raises Exception: If the policy 'pushtoken_pairing_callback_url'
            was not set.

        :raises TokenStateError: If token state is not None
            (default pre-enrollment state)
        """

        param_keys = set(params.keys())
        init_rollout_state_keys = set(['type', 'serial', '::scope::',
                                       'user.login', 'description',
                                       'user.realm', 'session', 'key_size',
                                       'resConf', 'user', 'realm',
                                       'pin'])

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

        if not param_keys.issubset(init_rollout_state_keys):

            # make sure the call aborts, if request
            # type wasn't recognized

            raise Exception('Unknown request type for token type pushtoken')

        # if param keys are in above set, the token is
        # initialized for the first time. this is e.g. done on the
        # manage web ui. since the token doesn't exist in the database
        # yet, its rollout state must be None (that is: the data for
        # the rollout state doesn't exist yet)

        self.ensure_state(None)

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

        # we check if callback policies are set. this must be done here
        # because the token gets saved directly after the update method
        # in the TokenHandler

        _ = context['translate']

        owner = get_token_owner(self)
        if owner and owner.login and owner.realm:
            realms = [owner.realm]
        else:
            realms = self.getRealms()

        cb_url = get_single_auth_policy('pushtoken_pairing_callback_url',
                                        user=owner, realms=realms)

        if not cb_url:
            raise Exception(_('Policy pushtoken_pairing_callback_url must '
                              'have a value'))

        partition = get_partition(realms, owner)
        self.addToTokenInfo('partition', partition)

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

        # we set the the active state of the token to False, because
        # it should not be allowed to use it for validation before the
        # pairing process is done

        self.token.LinOtpIsactive = False

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

        TokenClass.update(self, params, reset_failcount=True)

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

        self.change_state('initialized')
Пример #25
0
    def check_by_transactionid(self, transid, passw, options=None):
        """
        check the passw against the open transaction

        :param transid: the transaction id
        :param passw: the pass parameter
        :param options: the additional optional parameters

        :return: tuple of boolean and detail dict
        """
        reply = {}

        serials = []
        challenges = Challenges.lookup_challenges(transid=transid)

        for challenge in challenges:
            serials.append(challenge.tokenserial)

        if not serials:
            reply['value'] = False
            reply['failure'] = ('No challenge for transaction %r found'
                                % transid)

            return False, reply

        ok = False
        reply['failcount'] = 0
        reply['value'] = False
        reply['token_type'] = ''

        token_type = options.get('token_type', None)

        for serial in serials:

            tokens = getTokens4UserOrSerial(serial=serial,
                                            token_type=token_type,
                                            read_for_update=True)

            if not tokens and token_type:
                continue

            if not tokens and not token_type:
                raise Exception('tokenmismatch for token serial: %s'
                                % (unicode(serial)))

            # there could be only one
            token = tokens[0]
            owner = get_token_owner(token)

            (ok, opt) = self.checkTokenList(tokens, passw, user=owner,
                                            options=options)
            if opt:
                reply.update(opt)

            reply['value'] = ok
            reply['token_type'] = token.getType()
            reply['failcount'] = token.getFailCount()
            reply['serial'] = token.getSerial()

            if ok:
                break

        return ok, reply