Ejemplo n.º 1
0
    def test_supports_offline(
        self,
        mocked__get_client,
        mocked__get_policies,
        mocked_get_policy_definitions,
    ):
        """verify that client in the policy is honored for supports_offline"""

        m_policy = {
            "general": {
                "realm": "*",
                "active": "True",
                "client": "*",
                "user": "******",
                "time": "* * * * * *;",
                "action": "support_offline=qr",
                "scope": "authentication",
            }
        }
        mocked_get_policy_definitions.return_value = {
            "authentication": {
                "support_offline": {
                    "type": "set",
                    "value": ["qr", "u2f"],  # TODO: currently hardcoded
                    "desc": "The token types that should support offline "
                    "authentication",
                },
            }
        }

        mocked__get_policies.return_value = m_policy

        qr_token = Token("qr")

        mocked__get_client.return_value = "127.0.0.1"
        assert supports_offline(realms=["defaultrealm"], token=qr_token)

        mocked__get_client.return_value = "128.0.0.1"
        assert supports_offline(realms=["defaultrealm"], token=qr_token)

        m_policy["general"]["client"] = "127.0.0.1/24"

        mocked__get_client.return_value = "127.0.0.1"
        assert supports_offline(realms=["defaultrealm"], token=qr_token)

        mocked__get_client.return_value = "128.0.0.1"
        assert not supports_offline(realms=["defaultrealm"], token=qr_token)
Ejemplo n.º 2
0
    def test_supports_offline(self, mocked__get_client, mocked__get_policies,
                              mocked_get_policy_definitions):
        """verify that client in the policy is honored for supports_offline"""

        m_policy = {
            'general': {
                'realm': '*',
                'active': 'True',
                'client': '*',
                'user': '******',
                'time': '* * * * * *;',
                'action': 'support_offline=qr',
                'scope': 'authentication',
            }
        }
        mocked_get_policy_definitions.return_value = {
            'authentication': {
                'support_offline': {
                    'type':
                    'set',
                    'value': ['qr', 'u2f'],  # TODO: currently hardcoded
                    'desc':
                    'The token types that should support offline '
                    'authentication'
                },
            }
        }

        mocked__get_policies.return_value = m_policy

        qr_token = Token('qr')

        mocked__get_client.return_value = '127.0.0.1'
        assert supports_offline(realms=['defaultrealm'], token=qr_token)

        mocked__get_client.return_value = '128.0.0.1'
        assert supports_offline(realms=['defaultrealm'], token=qr_token)

        m_policy['general']['client'] = '127.0.0.1/24'

        mocked__get_client.return_value = '127.0.0.1'
        assert supports_offline(realms=['defaultrealm'], token=qr_token)

        mocked__get_client.return_value = '128.0.0.1'
        assert not supports_offline(realms=['defaultrealm'], token=qr_token)
Ejemplo n.º 3
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.getChallenge()
            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
Ejemplo n.º 4
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(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 = {}

        pin_policies = get_pin_policies(user)
        if 1 in pin_policies:
            pin_match = check_pin(None, password, user=user, options=None)
            if not pin_match:
                return False, None

        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
            serial = ch.getTokenSerial()
            tokens = getTokens4UserOrSerial(serial=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]

            if 1 not in pin_policies:
                pin_match = check_pin(token, password, user=user, options=None)
                if not pin_match:
                    ret = False
                    continue

            ret = True

            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()

            token_dict = {'serial': serial, '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 ret, reply
Ejemplo n.º 5
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(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 = {}

        pin_policies = linotp.lib.policy.get_pin_policies(user)
        if 1 in pin_policies:
            pin_match = check_pin(None, password, user=user, options=None)
            if not pin_match:
                return False, None

        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
            serial = ch.getTokenSerial()
            tokens = getTokens4UserOrSerial(serial=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]

            if 1 not in pin_policies:
                pin_match = check_pin(token, password, user=user,
                                      options=None)
                if not pin_match:
                    ret = False
                    continue

            ret = True

            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()

            token_dict = {'serial': serial, '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 ret, reply
Ejemplo n.º 6
0
    def finish_valid_tokens(self):
        """
        processing of the valid tokens
        """
        valid_tokens = self.valid_tokens
        validation_results = self.validation_results
        user = self.user

        if len(valid_tokens) == 1:
            token = valid_tokens[0]
            if user:
                action_detail = ("user %r@%r successfully authenticated." %
                                 (user.login, user.realm))
            else:
                action_detail = ("serial %r successfully authenticated." %
                                 token.getSerial())

            log.info(action_detail)

            # there could be a match in the window ahead,
            # so we need the last valid counter here
            (counter, _reply) = validation_results[token.getSerial()]
            token.setOtpCount(counter + 1)
            token.statusValidationSuccess()
            # finish as well related open challenges
            Challenges.finish_challenges(token, success=True)

            if token.getFromTokenInfo('count_auth_success_max', default=None):
                auth_count = token.get_count_auth_success()
                token.set_count_auth_success(auth_count + 1)

            detail = None
            auth_info = self.options.get('auth_info', 'False')
            if auth_info.lower() == "true":
                detail = token.getAuthDetail()

            # 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)

            # 3. check if parameter 'use_offline' is provided
            use_offline_param = self.options.get('use_offline', 'False')
            use_offline = use_offline_param.lower() == 'true'

            if supports_offline_at_all and \
               offline_is_allowed and \
               use_offline:

                offline_info = token.getOfflineInfo()
                if detail is None:
                    detail = {}

                offline = {'serial': token.getSerial(), 'type': token.type}
                offline['offline_info'] = offline_info

                detail.update({'offline': offline})

            return (True, detail, action_detail)

        else:
            # we have to set the matching counter to prevent replay one one
            # single token
            for token in valid_tokens:
                (res, _reply) = validation_results[token.getSerial()]
                token.setOtpCount(res)

            context['audit']['action_detail'] = "Multiple valid tokens found!"
            if user:
                log.error(
                    "[__checkTokenList] multiple token match error: "
                    "Several Tokens matching with the same OTP PIN "
                    "and OTP for user %r. Not sure how to auth", user.login)
            raise UserError("multiple token match error", id=-33)
Ejemplo n.º 7
0
    def finish_valid_tokens(self):
        """
        processing of the valid tokens
        """
        valid_tokens = self.valid_tokens
        validation_results = self.validation_results
        user = self.user

        if len(valid_tokens) == 1:
            token = valid_tokens[0]
            if user:
                action_detail = ("user %r@%r successfully authenticated."
                                 % (user.login, user.realm))
            else:
                action_detail = ("serial %r successfully authenticated."
                                 % token.getSerial())

            log.info(action_detail)

            # there could be a match in the window ahead,
            # so we need the last valid counter here

            (counter, _reply) = validation_results[token.getSerial()]
            token.setOtpCount(counter + 1)
            token.statusValidationSuccess()

            # finish as well related open challenges
            Challenges.finish_challenges(token, success=True)

            if token.count_auth_success_max > 0:
                token.inc_count_auth_success()

            if token.count_auth_max > 0:
                token.inc_count_auth()

            detail = None
            auth_info = self.options.get('auth_info', 'False')
            if auth_info.lower() == "true":
                detail = token.getAuthDetail()

            # 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)

            # 3. check if parameter 'use_offline' is provided
            use_offline_param = self.options.get('use_offline', 'False')
            use_offline = use_offline_param.lower() == 'true'

            if supports_offline_at_all and \
               offline_is_allowed and \
               use_offline:

                offline_info = token.getOfflineInfo()
                if detail is None:
                    detail = {}

                offline = {'serial': token.getSerial(), 'type': token.type}
                offline['offline_info'] = offline_info

                detail.update({'offline': offline})

            janitor_to_remove_enrollment_token(valid_tokens=[token])

            return (True, detail, action_detail)

        else:
            # we have to set the matching counter to prevent replay one one
            # single token

            for token in valid_tokens:

                (res, _reply) = validation_results[token.getSerial()]

                token.setOtpCount(res)

                # in case of multiple matches the tokens were accessed
                # so we count them as well
                if token.count_auth_max > 0:
                    token.inc_count_auth()

            janitor_to_remove_enrollment_token(valid_tokens=valid_tokens)

            context['audit']['action_detail'] = "Multiple valid tokens found!"
            if user:
                log.error("multiple token match error: "
                          "Several Tokens matching with the same OTP PIN "
                          "and OTP for user %r. Not sure how to auth",
                          user.login)

            raise UserError("multiple token match error", id=-33)
Ejemplo n.º 8
0
    def finish_valid_tokens(self):
        """
        processing of the valid tokens
        """
        valid_tokens = self.valid_tokens
        validation_results = self.validation_results
        user = self.user

        if len(valid_tokens) == 1:
            token = valid_tokens[0]
            if user:
                action_detail = "user %r@%r successfully authenticated." % (
                    user.login,
                    user.realm,
                )
            else:
                action_detail = ("serial %r successfully authenticated." %
                                 token.getSerial())

            log.info(action_detail)

            # there could be a match in the window ahead,
            # so we need the last valid counter here

            (counter, _reply) = validation_results[token.getSerial()]
            token.setOtpCount(counter + 1)
            token.statusValidationSuccess()

            # finish as well related open challenges
            Challenges.finish_challenges(token, success=True)

            if token.count_auth_success_max > 0:
                token.inc_count_auth_success()

            if token.count_auth_max > 0:
                token.inc_count_auth()

            detail = None
            auth_info = self.options.get("auth_info", "False")
            if auth_info.lower() == "true":
                detail = token.getAuthDetail()

            # 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)

            # 3. check if parameter 'use_offline' is provided
            use_offline_param = self.options.get("use_offline", "False")
            use_offline = use_offline_param.lower() == "true"

            if supports_offline_at_all and offline_is_allowed and use_offline:

                offline_info = token.getOfflineInfo()
                if detail is None:
                    detail = {}

                offline = {"serial": token.getSerial(), "type": token.type}
                offline["offline_info"] = offline_info

                detail.update({"offline": offline})

            janitor_to_remove_enrollment_token(valid_tokens=[token])

            return (True, detail, action_detail)

        else:
            # we have to set the matching counter to prevent replay one one
            # single token

            for token in valid_tokens:

                (res, _reply) = validation_results[token.getSerial()]

                token.setOtpCount(res)

                # in case of multiple matches the tokens were accessed
                # so we count them as well
                if token.count_auth_max > 0:
                    token.inc_count_auth()

            janitor_to_remove_enrollment_token(valid_tokens=valid_tokens)

            g.audit["action_detail"] = "Multiple valid tokens found!"
            if user:
                log.error(
                    "multiple token match error: "
                    "Several Tokens matching with the same OTP PIN "
                    "and OTP for user %r. Not sure how to auth",
                    user.login,
                )

            raise UserError("multiple token match error", id=-33)
Ejemplo n.º 9
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