Exemplo n.º 1
0
def fetch_one_resource(table, **query):
    """
    Given an SQLAlchemy table and query keywords, fetch exactly one result and return it.
    If no results is found, this raises a ``ResourceNotFoundError``.
    If more than one result is found, this raises SQLAlchemy's ``MultipleResultsFound``
    """
    try:
        return table.query.filter_by(**query).one()
    except sqlalchemy.orm.exc.NoResultFound:
        raise ResourceNotFoundError(u"The requested {!s} could not be found.".format(table.__name__))
Exemplo n.º 2
0
def get_periodic_task_by_name(name):
    """
    Get a periodic task by name. Raise ParameterError if the task could not be found.
    :param name: task name, unicode
    :return: dictionary
    """
    periodic_tasks = get_periodic_tasks(name)
    if len(periodic_tasks) != 1:
        raise ResourceNotFoundError(
            u"The periodic task with unique name {!r} does not exist".format(
                name))
    return periodic_tasks[0]
Exemplo n.º 3
0
    def _api_endpoint_get(cls, g, request_data):
        """ Handle all GET requests to the api endpoint.

        Currently this is only used for polling.
        :param g: The Flask context
        :param request_data: Dictionary containing the parameters of the request
        :type request_data: dict
        :returns: Result of the polling operation, 'True' if an unanswered and
                  matching challenge exists, 'False' otherwise.
        :rtype: bool
        """
        # By default we allow polling if the policy is not set.
        allow_polling = get_action_values_from_options(
            SCOPE.AUTH, PUSH_ACTION.ALLOW_POLLING,
            options={'g': g}) or PushAllowPolling.ALLOW
        if allow_polling == PushAllowPolling.DENY:
            raise PolicyError('Polling not allowed!')
        serial = getParam(request_data, "serial", optional=False)
        timestamp = getParam(request_data, 'timestamp', optional=False)
        signature = getParam(request_data, 'signature', optional=False)
        # first check if the timestamp is in the required span
        cls._check_timestamp_in_range(timestamp, POLL_TIME_WINDOW)
        # now check the signature
        # first get the token
        try:
            tok = get_one_token(serial=serial, tokentype=cls.get_class_type())
            # If the push_allow_polling policy is set to "token" we also
            # need to check the POLLING_ALLOWED tokeninfo. If it evaluated
            # to 'False', polling is not allowed for this token. If the
            # tokeninfo value evaluates to 'True' or is not set at all,
            # polling is allowed for this token.
            if allow_polling == PushAllowPolling.TOKEN:
                if not is_true(
                        tok.get_tokeninfo(POLLING_ALLOWED, default='True')):
                    log.debug('Polling not allowed for pushtoken {0!s} due to '
                              'tokeninfo.'.format(serial))
                    raise PolicyError('Polling not allowed!')

            pubkey_obj = _build_verify_object(
                tok.get_tokeninfo(PUBLIC_KEY_SMARTPHONE))
            sign_data = u"{serial}|{timestamp}".format(**request_data)
            pubkey_obj.verify(b32decode(signature), sign_data.encode("utf8"),
                              padding.PKCS1v15(), hashes.SHA256())
            # The signature was valid now check for an open challenge
            # we need the private server key to sign the smartphone data
            pem_privkey = tok.get_tokeninfo(PRIVATE_KEY_SERVER)
            # We need the registration URL for the challenge
            registration_url = get_action_values_from_options(
                SCOPE.ENROLL, PUSH_ACTION.REGISTRATION_URL, options={'g': g})
            if not registration_url:
                raise ResourceNotFoundError(
                    'There is no registration_url defined for the '
                    ' pushtoken {0!s}. You need to define a push_registration_url '
                    'in an enrollment policy.'.format(serial))
            options = {'g': g}
            challenges = []
            challengeobject_list = get_challenges(serial=serial)
            for chal in challengeobject_list:
                # check if the challenge is active and not already answered
                _cnt, answered = chal.get_otp_status()
                if not answered and chal.is_valid():
                    # then return the necessary smartphone data to answer
                    # the challenge
                    sp_data = _build_smartphone_data(serial, chal.challenge,
                                                     registration_url,
                                                     pem_privkey, options)
                    challenges.append(sp_data)
            # return the challenges as a list in the result value
            result = challenges
        except (ResourceNotFoundError, ParameterError, InvalidSignature,
                ConfigAdminError, BinasciiError) as e:
            # to avoid disclosing information we always fail with an invalid
            # signature error even if the token with the serial could not be found
            log.debug('{0!s}'.format(traceback.format_exc()))
            log.info('The following error occurred during the signature '
                     'check: "{0!r}"'.format(e))
            raise privacyIDEAError('Could not verify signature!')

        return result
Exemplo n.º 4
0
    def _api_endpoint_post(cls, request_data):
        """ Handle all POST requests to the api endpoint

        :param request_data: Dictionary containing the parameters of the request
        :type request_data: dict
        :returns: The result of handling the request and a dictionary containing
                  the details of the request handling
        :rtype: (bool, dict)
        """
        details = {}
        result = False

        serial = getParam(request_data, "serial", optional=False)
        if all(k in request_data for k in ("fbtoken", "pubkey")):
            log.debug("Do the 2nd step of the enrollment.")
            try:
                token_obj = get_one_token(
                    serial=serial,
                    tokentype="push",
                    rollout_state=ROLLOUTSTATE.CLIENTWAIT)
                token_obj.update(request_data)
            except ResourceNotFoundError:
                raise ResourceNotFoundError(
                    "No token with this serial number "
                    "in the rollout state 'clientwait'.")
            init_detail_dict = request_data

            details = token_obj.get_init_detail(init_detail_dict)
            result = True
        elif all(k in request_data for k in ("nonce", "signature")):
            log.debug(
                "Handling the authentication response from the smartphone.")
            challenge = getParam(request_data, "nonce")
            signature = getParam(request_data, "signature")

            # get the token_obj for the given serial:
            token_obj = get_one_token(serial=serial, tokentype="push")
            pubkey_obj = _build_verify_object(
                token_obj.get_tokeninfo(PUBLIC_KEY_SMARTPHONE))
            # Do the 2nd step of the authentication
            # Find valid challenges
            challengeobject_list = get_challenges(serial=serial,
                                                  challenge=challenge)

            if challengeobject_list:
                # There are valid challenges, so we check this signature
                for chal in challengeobject_list:
                    # verify the signature of the nonce
                    sign_data = u"{0!s}|{1!s}".format(challenge, serial)
                    try:
                        pubkey_obj.verify(b32decode(signature),
                                          sign_data.encode("utf8"),
                                          padding.PKCS1v15(), hashes.SHA256())
                        # The signature was valid
                        log.debug(
                            "Found matching challenge {0!s}.".format(chal))
                        chal.set_otp_status(True)
                        chal.save()
                        result = True
                    except InvalidSignature as _e:
                        pass
        elif all(k in request_data
                 for k in ('new_fb_token', 'timestamp', 'signature')):
            timestamp = getParam(request_data, 'timestamp', optional=False)
            signature = getParam(request_data, 'signature', optional=False)
            # first check if the timestamp is in the required span
            cls._check_timestamp_in_range(timestamp, UPDATE_FB_TOKEN_WINDOW)
            try:
                tok = get_one_token(serial=serial,
                                    tokentype=cls.get_class_type())
                pubkey_obj = _build_verify_object(
                    tok.get_tokeninfo(PUBLIC_KEY_SMARTPHONE))
                sign_data = u"{new_fb_token}|{serial}|{timestamp}".format(
                    **request_data)
                pubkey_obj.verify(b32decode(signature),
                                  sign_data.encode("utf8"), padding.PKCS1v15(),
                                  hashes.SHA256())
                # If the timestamp and signature are valid we update the token
                tok.add_tokeninfo('firebase_token',
                                  request_data['new_fb_token'])
                result = True
            except (ResourceNotFoundError, ParameterError, TypeError,
                    InvalidSignature, ConfigAdminError, BinasciiError) as e:
                # to avoid disclosing information we always fail with an invalid
                # signature error even if the token with the serial could not be found
                log.debug('{0!s}'.format(traceback.format_exc()))
                log.info('The following error occurred during the signature '
                         'check: "{0!r}"'.format(e))
                raise privacyIDEAError('Could not verify signature!')
        else:
            raise ParameterError("Missing parameters!")

        return result, details
Exemplo n.º 5
0
    def api_endpoint(cls, request, g):
        """
        This provides a function which is called by the API endpoint
        ``/ttype/push`` which is defined in :doc:`../../api/ttype`

        The method returns a tuple ``("json", {})``

        This endpoint provides several functionalities:

        - It is used for the 2nd enrollment step of the smartphone.
          It accepts the following parameters:

            .. sourcecode:: http

              POST /ttype/push HTTP/1.1
              Host: https://yourprivacyideaserver

              serial=<token serial>
              fbtoken=<firebase token>
              pubkey=<public key>

        - It is also used when the smartphone sends the signed response
          to the challenge during authentication. The following parameters ar accepted:

            .. sourcecode:: http

              POST /ttype/push HTTP/1.1
              Host: https://yourprivacyideaserver

              serial=<token serial>
              nonce=<the actual challenge>
              signature=<the signed nonce>

        - And it also acts as an endpoint for polling challenges:

            .. sourcecode:: http

              GET /ttype/push HTTP/1.1
              Host: https://yourprivacyideaserver

              serial=<tokenserial>
              timestamp=<timestamp>
              signature=SIGNATURE(<tokenserial>|<timestamp>)

          More on polling can be found here: https://github.com/privacyidea/privacyidea/wiki/concept%3A-pushtoken-poll

        :param request: The Flask request
        :param g: The Flask global object g
        :return: The json string representing the result dictionary
        :rtype: tuple("json", str)
        """
        details = {}
        result = False

        if request.method == 'POST':
            serial = getParam(request.all_data, "serial", optional=False)
            if serial and "fbtoken" in request.all_data and "pubkey" in request.all_data:
                log.debug("Do the 2nd step of the enrollment.")
                try:
                    token_obj = get_one_token(serial=serial,
                                              tokentype="push",
                                              rollout_state="clientwait")
                    token_obj.update(request.all_data)
                except ResourceNotFoundError:
                    raise ResourceNotFoundError(
                        "No token with this serial number "
                        "in the rollout state 'clientwait'.")
                init_detail_dict = request.all_data

                details = token_obj.get_init_detail(init_detail_dict)
                result = True
            elif serial and "nonce" in request.all_data and "signature" in request.all_data:
                log.debug(
                    "Handling the authentication response from the smartphone."
                )
                challenge = getParam(request.all_data, "nonce")
                serial = getParam(request.all_data, "serial")
                signature = getParam(request.all_data, "signature")

                # get the token_obj for the given serial:
                token_obj = get_one_token(serial=serial, tokentype="push")
                pubkey_obj = _build_verify_object(
                    token_obj.get_tokeninfo(PUBLIC_KEY_SMARTPHONE))
                # Do the 2nd step of the authentication
                # Find valid challenges
                challengeobject_list = get_challenges(serial=serial,
                                                      challenge=challenge)

                if challengeobject_list:
                    # There are valid challenges, so we check this signature
                    for chal in challengeobject_list:
                        # verify the signature of the nonce
                        sign_data = u"{0!s}|{1!s}".format(challenge, serial)
                        try:
                            pubkey_obj.verify(b32decode(signature),
                                              sign_data.encode("utf8"),
                                              padding.PKCS1v15(),
                                              hashes.SHA256())
                            # The signature was valid
                            log.debug(
                                "Found matching challenge {0!s}.".format(chal))
                            chal.set_otp_status(True)
                            chal.save()
                            result = True
                        except InvalidSignature as _e:
                            pass

            else:
                raise ParameterError("Missing parameters!")
        elif request.method == 'GET':
            # This is only used for polling
            # By default we allow polling if the policy is not set.
            allow_polling = get_action_values_from_options(
                SCOPE.AUTH, PUSH_ACTION.ALLOW_POLLING,
                options={'g': g}) or PushAllowPolling.ALLOW
            if allow_polling == PushAllowPolling.DENY:
                raise PolicyError('Polling not allowed!')
            serial = getParam(request.all_data, "serial", optional=False)
            timestamp = getParam(request.all_data, 'timestamp', optional=False)
            signature = getParam(request.all_data, 'signature', optional=False)
            # first check if the timestamp is in the required span
            try:
                ts = isoparse(timestamp)
            except (ValueError, TypeError) as _e:
                log.debug('{0!s}'.format(traceback.format_exc()))
                raise privacyIDEAError(
                    'Could not parse timestamp {0!s}. '
                    'ISO-Format required.'.format(timestamp))
            # TODO: make time delta configurable
            td = timedelta(minutes=POLL_TIME_WINDOW)
            # We don't know if the passed timestamp is timezone aware. If no
            # timezone is passed, we assume UTC
            if ts.tzinfo:
                now = datetime.now(utc)
            else:
                now = datetime.utcnow()
            if not (now - td <= ts <= now + td):
                raise privacyIDEAError(
                    'Timestamp {0!s} not in valid range.'.format(timestamp))
            # now check the signature
            # first get the token
            try:
                tok = get_one_token(serial=serial,
                                    tokentype=cls.get_class_type())
                # If the push_allow_polling policy is set to "token" we also
                # need to check the POLLING_ALLOWED tokeninfo. If it evaluated
                # to 'False', polling is not allowed for this token. If the
                # tokeninfo value evaluates to 'True' or is not set at all,
                # polling is allowed for this token.
                if allow_polling == PushAllowPolling.TOKEN:
                    if not is_true(
                            tok.get_tokeninfo(POLLING_ALLOWED,
                                              default='True')):
                        log.debug(
                            'Polling not allowed for pushtoken {0!s} due to '
                            'tokeninfo.'.format(serial))
                        raise PolicyError('Polling not allowed!')

                pubkey_obj = _build_verify_object(
                    tok.get_tokeninfo(PUBLIC_KEY_SMARTPHONE))
                sign_data = u"{serial}|{timestamp}".format(**request.all_data)
                pubkey_obj.verify(b32decode(signature),
                                  sign_data.encode("utf8"), padding.PKCS1v15(),
                                  hashes.SHA256())
                # The signature was valid now check for an open challenge
                # we need the private server key to sign the smartphone data
                pem_privkey = tok.get_tokeninfo(PRIVATE_KEY_SERVER)
                # we also need the FirebaseGateway for this token
                fb_identifier = tok.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG)
                if not fb_identifier:
                    raise ResourceNotFoundError(
                        'The pushtoken {0!s} has no Firebase configuration '
                        'assigned.'.format(serial))
                fb_gateway = create_sms_instance(fb_identifier)
                options = {'g': g}
                challenges = []
                challengeobject_list = get_challenges(serial=serial)
                for chal in challengeobject_list:
                    # check if the challenge is active and not already answered
                    _cnt, answered = chal.get_otp_status()
                    if not answered and chal.is_valid():
                        # then return the necessary smartphone data to answer
                        # the challenge
                        sp_data = _build_smartphone_data(
                            serial, chal.challenge, fb_gateway, pem_privkey,
                            options)
                        challenges.append(sp_data)
                # return the challenges as a list in the result value
                result = challenges
            except (ResourceNotFoundError, ParameterError, InvalidSignature,
                    ConfigAdminError, BinasciiError) as e:
                # to avoid disclosing information we always fail with an invalid
                # signature error even if the token with the serial could not be found
                log.debug('{0!s}'.format(traceback.format_exc()))
                log.info('The following error occurred during the signature '
                         'check: "{0!r}"'.format(e))
                raise privacyIDEAError('Could not verify signature!')

        else:
            raise privacyIDEAError(
                'Method {0!s} not allowed in \'api_endpoint\' '
                'for push token.'.format(request.method))

        return "json", prepare_result(result, details=details)
Exemplo n.º 6
0
    def api_endpoint(cls, request, g):
        """
        This provides a function which is called by the API endpoint
        /ttype/push which is defined in api/ttype.py

        The method returns
            return "json", {}

        This endpoint is used for the 2nd enrollment step of the smartphone.
        Parameters sent:
            * serial
            * fbtoken
            * pubkey

        This endpoint is also used, if the smartphone sends the signed response
        to the challenge during authentication
        Parameters sent:
            * serial
            * nonce (which is the challenge)
            * signature (which is the signed nonce)


        :param request: The Flask request
        :param g: The Flask global object g
        :return: dictionary
        """
        details = {}
        result = False
        serial = getParam(request.all_data, "serial", optional=False)

        if serial and "fbtoken" in request.all_data and "pubkey" in request.all_data:
            # Do the 2nd step of the enrollment
            try:
                token_obj = get_one_token(serial=serial,
                                          tokentype="push",
                                          rollout_state="clientwait")
                token_obj.update(request.all_data)
            except ResourceNotFoundError:
                raise ResourceNotFoundError(
                    "No token with this serial number in the rollout state 'clientwait'."
                )
            init_detail_dict = request.all_data

            details = token_obj.get_init_detail(init_detail_dict)
            result = True
        elif serial and "nonce" in request.all_data and "signature" in request.all_data:
            challenge = getParam(request.all_data, "nonce")
            serial = getParam(request.all_data, "serial")
            signature = getParam(request.all_data, "signature")

            # get the token_obj for the given serial:
            token_obj = get_one_token(serial=serial, tokentype="push")
            pubkey_pem = token_obj.get_tokeninfo(PUBLIC_KEY_SMARTPHONE)
            # The public key of the smartphone was probably sent as urlsafe:
            pubkey_pem = pubkey_pem.replace("-", "+").replace("_", "/")
            # The public key was sent without any header
            pubkey_pem = "-----BEGIN PUBLIC KEY-----\n{0!s}\n-----END PUBLIC KEY-----".format(
                pubkey_pem.strip().replace(" ", "+"))
            # Do the 2nd step of the authentication
            # Find valid challenges
            challengeobject_list = get_challenges(serial=serial,
                                                  challenge=challenge)

            if challengeobject_list:
                # There are valid challenges, so we check this signature
                for chal in challengeobject_list:
                    # verify the signature of the nonce
                    pubkey_obj = serialization.load_pem_public_key(
                        to_bytes(pubkey_pem), default_backend())
                    sign_data = u"{0!s}|{1!s}".format(challenge, serial)
                    try:
                        pubkey_obj.verify(b32decode(signature),
                                          sign_data.encode("utf8"),
                                          padding.PKCS1v15(), hashes.SHA256())
                        # The signature was valid
                        chal.set_otp_status(True)
                        result = True
                    except InvalidSignature as e:
                        pass

        else:
            raise ParameterError("Missing parameters!")

        return "json", prepare_result(result, details=details)