def send_result(obj, rid=1, details=None): ''' sendResult - return an json result document :param obj: simple result object like dict, sting or list :type obj: dict or list or string/unicode :param rid: id value, for future versions :type rid: int :param details: optional parameter, which allows to provide more detail :type details: None or simple type like dict, list or string/unicode :return: json rendered sting result :rtype: string ''' return jsonify(prepare_result(obj, rid, details))
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 are accepted: .. sourcecode:: http POST /ttype/push HTTP/1.1 Host: https://yourprivacyideaserver serial=<token serial> nonce=<the actual challenge> signature=<the signed nonce> - In some cases the Firebase service changes the token of a device. This needs to be communicated to privacyIDEA through this endpoint (https://github.com/privacyidea/privacyidea/wiki/concept%3A-pushtoken-poll#update -firebase-token): .. sourcecode:: http POST /ttype/push HTTP/1.1 Host: https://yourprivacyideaserver new_fb_token=<new Firebase token> serial=<token serial> timestamp=<timestamp> signature=SIGNATURE(<new_fb_token>|<serial>|<timestamp>) - 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 = {} if request.method == 'POST': result, details = cls._api_endpoint_post(request.all_data) elif request.method == 'GET': result = cls._api_endpoint_get(g, request.all_data) else: raise privacyIDEAError( 'Method {0!s} not allowed in \'api_endpoint\' ' 'for push token.'.format(request.method)) return "json", prepare_result(result, details=details)
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)
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)
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: 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_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 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!") return "json", prepare_result(result, details=details)