Repository: privacyidea/privacyidea

Issue:
Add push require-presence answer as tag for challenge text
Add push require-presence answer as tag for challenge text


Discussion:

Patch:
diff --git a/doc/policies/authentication.rst b/doc/policies/authentication.rst
index 1ed1c8c9c9..f446b64e81 100644
--- a/doc/policies/authentication.rst
+++ b/doc/policies/authentication.rst
@@ -771,6 +771,7 @@ tag.
     * {date} the current server date in the format YYYY-MM-DD.
     * {phone} the phone number from the challenge in case of sms token.
     * {email} email address from the challenge in case of email token.
+    * {presence_answer} only for push token and only if require_presence is enabled.
 
 .. _policy_indexedsecret:
 
diff --git a/privacyidea/lib/token.py b/privacyidea/lib/token.py
index f99351d41a..2e99630418 100644
--- a/privacyidea/lib/token.py
+++ b/privacyidea/lib/token.py
@@ -2276,12 +2276,19 @@ def create_challenges_from_tokens(token_list, reply_dict, options=None):
         # Check if the max auth is succeeded
         if token_obj.check_all(message_list):
             r_chal, message, transaction_id, challenge_info = token_obj.create_challenge(
-                    transactionid=transaction_id, options=options)
+                transactionid=transaction_id, options=options)
+
             # We need to pass the info if a push token has been triggered, so that require presence can re-use the
             # challenge instead of creating a new one with a different answer
-            options["push_triggered"] = token_obj.get_type() == "push" if not options["push_triggered"] else True
+            # Also check the challenge info if the presence answer is returned to pass it on for tag replacement
+            additional_tags = {}
+            if token_obj.get_type() == "push":
+                options["push_triggered"] = True
+                if "presence_answer" in challenge_info:
+                    additional_tags["presence_answer"] = challenge_info["presence_answer"]
             # Add the reply to the response
-            message = challenge_text_replace(message, user=token_obj.user, token_obj=token_obj)
+            message = challenge_text_replace(message, user=token_obj.user, token_obj=token_obj,
+                                             additional_tags=additional_tags)
             message_list.append(message)
             if r_chal:
                 challenge_info = challenge_info or {}
@@ -2507,7 +2514,7 @@ def check_token_list(token_object_list, passw, user=None, options=None, allow_re
         further_challenge = False
         for token_object in challenge_response_token_list:
             if token_object.check_challenge_response(passw=passw,
-                                                    options=options) >= 0:
+                                                     options=options) >= 0:
                 reply_dict["serial"] = token_object.token.serial
                 matching_challenge = True
                 messages = []
@@ -2857,11 +2864,15 @@ def token_load(token_dict, tokenowner=True, overwrite=False):
     return token
 
 
-def challenge_text_replace(message, user, token_obj):
+def challenge_text_replace(message, user, token_obj, additional_tags: dict = None):
+    # TODO this function should be a token function since most of the info is from that token anyway, optionally pass
+    # TODO environment stuff into that function
     serial = token_obj.token.serial if token_obj.token.serial else None
     tokenowner = user if user else None
     tokentype = token_obj.token.tokentype if token_obj.token.tokentype else ""
     tags = create_tag_dict(serial=serial, tokenowner=tokenowner, tokentype=tokentype)
+    if additional_tags:
+        tags.update(additional_tags)
 
     if tokentype == "sms":
         if is_true(token_obj.get_tokeninfo("dynamic_phone")):
@@ -2885,5 +2896,13 @@ def challenge_text_replace(message, user, token_obj):
         if email is not None:
             tags["email"] = email
 
+    # If the message is for a pushtoken and the presence_answer is set, but there is no tag for placing that answer,
+    # Append the answer to the message
+    presence_answer = tags.get("presence_answer", None)
+    if presence_answer and tokentype == "push" and "{presence_answer}" not in message:
+        # PyBabel gettext and f-strings don't like each other
+        message += _(" Please press: {presence_answer}".format(presence_answer=presence_answer))
+
     message = message.format_map(defaultdict(str, tags))
+
     return message
diff --git a/privacyidea/lib/tokens/pushtoken.py b/privacyidea/lib/tokens/pushtoken.py
index 7546e9315a..b1507fb75e 100644
--- a/privacyidea/lib/tokens/pushtoken.py
+++ b/privacyidea/lib/tokens/pushtoken.py
@@ -251,7 +251,11 @@ def _build_smartphone_data(token_obj, challenge, registration_url, pem_privkey,
                            recipient={"givenname": user_object.info.get("givenname") if user_object else "",
                                       "surname": user_object.info.get("surname") if user_object else ""},
                            challenge=options.get("challenge"))
-    message_on_mobile = message_on_mobile.format(**tags)
+    try:
+        message_on_mobile = message_on_mobile.format(**tags)
+    except KeyError as e:
+        log.warning(f"Could not format the message: {e}. Using default message.")
+        message_on_mobile = DEFAULT_MOBILE_TEXT
     log.debug(f"Sending to mobile: {message_on_mobile}")
 
     title = get_action_values_from_options(SCOPE.AUTH, PUSH_ACTION.MOBILE_TITLE,
@@ -973,6 +977,7 @@ def create_challenge(self, transactionid=None, options=None):
                                       user_object=options.get("user")).any()
         data = None
         current_presence_options = None
+        reply_dict = {}
         if is_true(require_presence):
             if not options["push_triggered"]:
                 # Create a new challenge data
@@ -987,15 +992,19 @@ def create_challenge(self, transactionid=None, options=None):
                     selected_option = secrets.choice(available_presence_options)
                     available_presence_options.remove(selected_option)
                     current_presence_options.append(selected_option)
-                correct_option = secrets.choice(current_presence_options)
+                correct_presence_option = secrets.choice(current_presence_options)
                 # The data contains all selected options and the correct option at the end.
-                data = ",".join(current_presence_options + [correct_option])
+                data = ",".join(current_presence_options + [correct_presence_option])
             else:
                 # If the user has more than one token and more than one challenge is created, we need to ensure
                 # that the challenge data for all push token is the same.
                 challenges = get_challenges(transaction_id=transactionid)
                 data = next(c.data for c in challenges if c.serial.startswith("PIPU") and c.data) or ""
-                current_presence_options = data.split(",")[:-1]  # The correct option is the last one, so we remove it
+                # The correct option is the last one, so we remove it
+                split_presence_options = data.split(",")
+                current_presence_options = split_presence_options[:-1]
+                correct_presence_option = split_presence_options[-1]
+            reply_dict.update({"presence_answer": correct_presence_option})
         # Initially we assume there is no error from Firebase
         res = True
         fb_identifier = self.get_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG)
@@ -1052,14 +1061,7 @@ def create_challenge(self, transactionid=None, options=None):
             if is_true(options.get("exception")):
                 raise ValidateError("The token has no tokeninfo. Can not send via Firebase service.")
 
-        reply_dict = {"attributes": {"hideResponseInput": self.client_mode != CLIENTMODE.INTERACTIVE}}
-
-        if data:
-            # If the message contains {} we replace it with the data otherwise we add a new text
-            if "{}" in message:
-                message = message.format(data.split(",").pop())  # The correct presence option is the last one
-            else:
-                message += f" Please press: {data.split(',').pop()}"  # The correct presence option is the last one
+        reply_dict.update({"attributes": {"hideResponseInput": self.client_mode != CLIENTMODE.INTERACTIVE}})
         return True, message, transactionid, reply_dict
 
     @check_token_locked


Test patch:
diff --git a/tests/test_lib_tokens_push.py b/tests/test_lib_tokens_push.py
index d6384f0f13..bccc57bccd 100644
--- a/tests/test_lib_tokens_push.py
+++ b/tests/test_lib_tokens_push.py
@@ -1,7 +1,7 @@
 import json
 import time
 from base64 import b32decode, b32encode
-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, timezone
 from threading import Timer
 
 import mock
@@ -51,9 +51,8 @@
 
 
 def _create_credential_mock():
-    c = service_account.Credentials('a', 'b', 'c')
-    return mock.MagicMock(spec=c, expired=False, expiry=None,
-                          access_token='my_new_bearer_token')
+    c = service_account.Credentials("a", "b", "c")
+    return mock.MagicMock(spec=c, expired=False, expiry=None, access_token='my_new_bearer_token')
 
 
 def _check_firebase_params(request):
@@ -62,58 +61,55 @@ def _check_firebase_params(request):
     data = payload.get("message").get("data")
 
     sign_string = "{nonce}|{url}|{serial}|{question}|{title}|{sslverify}".format(**data)
-    token_obj = get_tokens(serial=data.get("serial"))[0]
-    pem_pubkey = token_obj.get_tokeninfo(PUBLIC_KEY_SERVER)
-    pubkey_obj = load_pem_public_key(to_bytes(pem_pubkey), backend=default_backend())
+    token = get_tokens(serial=data.get("serial"))[0]
+    pem_public_key = token.get_tokeninfo(PUBLIC_KEY_SERVER)
+    public_key = load_pem_public_key(to_bytes(pem_public_key), backend=default_backend())
     signature = b32decode(data.get("signature"))
     # If signature does not match it will raise InvalidSignature exception
-    pubkey_obj.verify(signature, sign_string.encode("utf8"),
-                      padding.PKCS1v15(),
-                      hashes.SHA256())
-    headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'}
+    public_key.verify(signature, sign_string.encode("utf8"), padding.PKCS1v15(), hashes.SHA256())
+    headers = {"request-id": "728d329e-0e86-11e4-a748-0c84dc037c13"}
     return 200, headers, json.dumps({})
 
 
 class PushTokenTestCase(MyTestCase):
     serial1 = "PUSH00001"
 
-    server_private_key = rsa.generate_private_key(public_exponent=65537,
-                                                  key_size=4096,
-                                                  backend=default_backend())
+    server_private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend())
     server_private_key_pem = to_unicode(server_private_key.private_bytes(
         encoding=serialization.Encoding.PEM,
         format=serialization.PrivateFormat.TraditionalOpenSSL,
-        encryption_algorithm=serialization.NoEncryption()))
+        encryption_algorithm=serialization.NoEncryption())
+    )
     server_public_key_pem = to_unicode(server_private_key.public_key().public_bytes(
         encoding=serialization.Encoding.PEM,
-        format=serialization.PublicFormat.SubjectPublicKeyInfo))
+        format=serialization.PublicFormat.SubjectPublicKeyInfo)
+    )
 
     # We now allow white spaces in the firebase config name
     firebase_config_name = "my firebase config"
 
-    smartphone_private_key = rsa.generate_private_key(public_exponent=65537,
-                                                      key_size=4096,
-                                                      backend=default_backend())
+    smartphone_private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend())
     smartphone_public_key = smartphone_private_key.public_key()
     smartphone_public_key_pem = to_unicode(smartphone_public_key.public_bytes(
         encoding=serialization.Encoding.PEM,
-        format=serialization.PublicFormat.SubjectPublicKeyInfo))
+        format=serialization.PublicFormat.SubjectPublicKeyInfo)
+    )
     # The smartphone sends the public key in URLsafe and without the ----BEGIN header
     smartphone_public_key_pem_urlsafe = strip_key(smartphone_public_key_pem).replace("+", "-").replace("/", "_")
 
     def _create_push_token(self):
-        tparams = {'type': 'push', 'genkey': 1}
-        tparams.update(FB_CONFIG_VALS)
-        tok = init_token(param=tparams)
-        tok.add_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG, self.firebase_config_name)
-        tok.add_tokeninfo(PUBLIC_KEY_SMARTPHONE, self.smartphone_public_key_pem_urlsafe)
-        tok.add_tokeninfo('firebase_token', 'firebaseT')
-        tok.add_tokeninfo(PUBLIC_KEY_SERVER, self.server_public_key_pem)
-        tok.add_tokeninfo(PRIVATE_KEY_SERVER, self.server_private_key_pem, 'password')
-        tok.del_tokeninfo("enrollment_credential")
-        tok.token.rollout_state = "enrolled"
-        tok.token.active = True
-        return tok
+        token_param = {"type": "push", "genkey": 1}
+        token_param.update(FB_CONFIG_VALS)
+        token = init_token(param=token_param)
+        token.add_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG, self.firebase_config_name)
+        token.add_tokeninfo(PUBLIC_KEY_SMARTPHONE, self.smartphone_public_key_pem_urlsafe)
+        token.add_tokeninfo("firebase_token", "firebaseT")
+        token.add_tokeninfo(PUBLIC_KEY_SERVER, self.server_public_key_pem)
+        token.add_tokeninfo(PRIVATE_KEY_SERVER, self.server_private_key_pem, "password")
+        token.del_tokeninfo("enrollment_credential")
+        token.token.rollout_state = "enrolled"
+        token.token.active = True
+        return token
 
     def test_01_create_token(self):
         db_token = Token(self.serial1, tokentype="push")
@@ -186,8 +182,7 @@ def test_01_create_token(self):
 
         detail = token.get_init_detail()
         self.assertEqual(detail.get("rollout_state"), "enrolled")
-        augmented_pubkey = "-----BEGIN RSA PUBLIC KEY-----\n{}\n-----END RSA PUBLIC KEY-----\n".format(
-            detail.get("public_key"))
+        augmented_pubkey = f"-----BEGIN RSA PUBLIC KEY-----\n{detail.get('public_key')}\n-----END RSA PUBLIC KEY-----\n"
         parsed_stripped_server_pubkey = serialization.load_pem_public_key(
             to_bytes(augmented_pubkey),
             default_backend())
@@ -195,25 +190,24 @@ def test_01_create_token(self):
         remove_token(self.serial1)
 
     def test_01a_enroll_with_app_pin(self):
-        tparams = {'type': 'push', 'genkey': 1}
-        tparams.update(FB_CONFIG_VALS)
-        tok = init_token(param=tparams)
-        detail = tok.get_init_detail(params={PUSH_ACTION.FIREBASE_CONFIG: POLL_ONLY,
-                                             PUSH_ACTION.REGISTRATION_URL: "https://privacyidea.com/enroll",
-                                             ACTION.FORCE_APP_PIN: True})
-        self.assertIn('pin=True', detail['pushurl']['value'])
-        remove_token(tok.get_serial())
+        token_param = {"type": "push", "genkey": 1}
+        token_param.update(FB_CONFIG_VALS)
+        token = init_token(param=token_param)
+        detail = token.get_init_detail(params={PUSH_ACTION.FIREBASE_CONFIG: POLL_ONLY,
+                                               PUSH_ACTION.REGISTRATION_URL: "https://privacyidea.com/enroll",
+                                               ACTION.FORCE_APP_PIN: True})
+        self.assertIn("pin=True", detail["pushurl"]["value"])
+        remove_token(token.get_serial())
 
     def test_02a_lib_enroll(self):
         r = set_smsgateway(self.firebase_config_name,
-                           'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider',
+                           "privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider",
                            "myFB", FB_CONFIG_VALS)
         self.assertTrue(r > 0)
         set_policy("push1", scope=SCOPE.ENROLL,
-                   action="{0!s}={1!s}".format(PUSH_ACTION.FIREBASE_CONFIG,
-                                               self.firebase_config_name))
-        token_obj = self._create_push_token()
-        remove_token(token_obj.get_serial())
+                   action=f"{PUSH_ACTION.FIREBASE_CONFIG}={self.firebase_config_name}")
+        token = self._create_push_token()
+        remove_token(token.get_serial())
 
     @responses.activate
     def test_03a_api_authenticate_fail(self):
@@ -221,37 +215,35 @@ def test_03a_api_authenticate_fail(self):
         self.setUp_user_realms()
         # create FireBase Service and policies
         set_smsgateway(self.firebase_config_name,
-                       'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider',
+                       "privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider",
                        "myFB", FB_CONFIG_VALS)
         set_policy("push1", scope=SCOPE.ENROLL,
-                   action="{0!s}={1!s},{2!s}={3!s},{4!s}={5!s}".format(
-                       PUSH_ACTION.FIREBASE_CONFIG,
-                       self.firebase_config_name,
-                       PUSH_ACTION.REGISTRATION_URL, REGISTRATION_URL,
-                       PUSH_ACTION.TTL, TTL))
+                   action=f"{PUSH_ACTION.FIREBASE_CONFIG}={self.firebase_config_name},"
+                          f"{PUSH_ACTION.REGISTRATION_URL}={REGISTRATION_URL},"
+                          f"{PUSH_ACTION.TTL}={TTL}")
         # create push token
-        tokenobj = self._create_push_token()
-        serial = tokenobj.get_serial()
+        token = self._create_push_token()
+        serial = token.get_serial()
         # set PIN
-        tokenobj.set_pin("pushpin")
-        tokenobj.add_user(User("cornelius", self.realm1))
+        token.set_pin("pushpin")
+        token.add_user(User("cornelius", self.realm1))
 
         # We mock the ServiceAccountCredentials, since we can not directly contact the Google API
-        with mock.patch('privacyidea.lib.smsprovider.FirebaseProvider.service_account.Credentials'
-                        '.from_service_account_file') as mySA:
+        with mock.patch("privacyidea.lib.smsprovider.FirebaseProvider.service_account.Credentials"
+                        ".from_service_account_file") as mock_service_account:
             # alternative: side_effect instead of return_value
-            mySA.return_value = _create_credential_mock()
+            mock_service_account.return_value = _create_credential_mock()
 
             # add responses, to simulate the failing communication (status 500)
-            responses.add(responses.POST, 'https://fcm.googleapis.com/v1/projects/test-123456/messages:send',
+            responses.add(responses.POST, "https://fcm.googleapis.com/v1/projects/test-123456/messages:send",
                           body="""{}""",
                           status=500,
                           content_type="application/json")
 
             # Send the first authentication request to trigger the challenge
             with mock.patch("logging.Logger.warning") as mock_log:
-                with self.app.test_request_context('/validate/check',
-                                                   method='POST',
+                with self.app.test_request_context("/validate/check",
+                                                   method="POST",
                                                    data={"user": "cornelius",
                                                          "realm": self.realm1,
                                                          "pass": "pushpin"}):
@@ -262,8 +254,7 @@ def test_03a_api_authenticate_fail(self):
                     self.assertFalse(result.get("value"))
                     self.assertEqual("CHALLENGE", result.get("authentication"))
                     # Check that the warning was written to the log file.
-                    mock_log.assert_called_with("Failed to submit message to Firebase service for token {0!s}."
-                                                .format(serial))
+                    mock_log.assert_called_with(f"Failed to submit message to Firebase service for token {serial}.")
                     # Check that the user was informed about the need to poll
                     detail = res.json.get("detail")
                     self.assertEqual("Please confirm the authentication on your mobile device! "
@@ -272,19 +263,19 @@ def test_03a_api_authenticate_fail(self):
 
             # Our ServiceAccountCredentials mock has been called once, because
             # no access token has been fetched before
-            mySA.assert_called_once()
+            mock_service_account.assert_called_once()
             self.assertIn(FIREBASE_FILE, get_app_local_store()["firebase_token"])
 
             # By default, polling is allowed for push tokens so the corresponding
             # challenge should be available in the challenge table, even though
             # the request to firebase failed.
-            chals = get_challenges(serial=tokenobj.token.serial)
-            self.assertEqual(len(chals), 1, chals)
-            chals[0].delete()
+            challenges = get_challenges(serial=token.token.serial)
+            self.assertEqual(len(challenges), 1, challenges)
+            challenges[0].delete()
 
             # Do the same with the parameter "exception", so that we receive an Error on HTTP
-            with self.app.test_request_context('/validate/check',
-                                               method='POST',
+            with self.app.test_request_context("/validate/check",
+                                               method="POST",
                                                data={"user": "cornelius",
                                                      "realm": self.realm1,
                                                      "exception": 1,
@@ -298,19 +289,18 @@ def test_03a_api_authenticate_fail(self):
                 self.assertEqual("ERR401: Failed to submit message to Firebase service.", error.get("message"))
 
             # Remove the created challenge
-            chals = get_challenges(serial=tokenobj.token.serial)
-            self.assertEqual(len(chals), 1, chals)
-            chals[0].delete()
+            challenges = get_challenges(serial=token.token.serial)
+            self.assertEqual(len(challenges), 1, challenges)
+            challenges[0].delete()
 
             # Now disable polling and check that no challenge is created
             # disallow polling through a policy
-            set_policy('push_poll', SCOPE.AUTH,
-                       action='{0!s}={1!s}'.format(PUSH_ACTION.ALLOW_POLLING,
-                                                   PushAllowPolling.DENY))
+            set_policy("push_poll", SCOPE.AUTH,
+                       action=f"{PUSH_ACTION.ALLOW_POLLING}={PushAllowPolling.DENY}")
 
             with mock.patch("logging.Logger.warning") as mock_log:
-                with self.app.test_request_context('/validate/check',
-                                                   method='POST',
+                with self.app.test_request_context("/validate/check",
+                                                   method="POST",
                                                    data={"user": "cornelius",
                                                          "realm": self.realm1,
                                                          "pass": "pushpin"}):
@@ -321,34 +311,31 @@ def test_03a_api_authenticate_fail(self):
                     self.assertFalse(result.get("value"))
                     self.assertEqual("CHALLENGE", result.get("authentication"))
                     # Check that the warning was written to the log file.
-                    mock_log.assert_called_with("Failed to submit message to Firebase service for token {0!s}."
-                                                .format(serial))
-            self.assertEqual(len(get_challenges(serial=tokenobj.token.serial)), 0)
+                    mock_log.assert_called_with(f"Failed to submit message to Firebase service for token {serial}.")
+            self.assertEqual(len(get_challenges(serial=token.token.serial)), 0)
             # disallow polling the specific token through a policy
-            set_policy('push_poll', SCOPE.AUTH,
-                       action='{0!s}={1!s}'.format(PUSH_ACTION.ALLOW_POLLING,
-                                                   PushAllowPolling.TOKEN))
-            tokenobj.add_tokeninfo(POLLING_ALLOWED, False)
+            set_policy("push_poll", SCOPE.AUTH,
+                       action=f"{PUSH_ACTION.ALLOW_POLLING}={PushAllowPolling.TOKEN}")
+            token.add_tokeninfo(POLLING_ALLOWED, False)
             with mock.patch("logging.Logger.warning") as mock_log:
-                with self.app.test_request_context('/validate/check',
-                                                   method='POST',
+                with self.app.test_request_context("/validate/check",
+                                                   method="POST",
                                                    data={"user": "cornelius",
                                                          "realm": self.realm1,
                                                          "pass": "pushpin"}):
                     res = self.app.full_dispatch_request()
-                    self.assertTrue(res.status_code == 200, res)
+                    self.assertEqual(200, res.status_code)
                     result = res.json.get("result")
                     self.assertTrue(result.get("status"))
                     self.assertFalse(result.get("value"))
                     self.assertEqual("CHALLENGE", result.get("authentication"))
                     # Check that the warning was written to the log file.
-                    mock_log.assert_called_with("Failed to submit message to Firebase service for token {0!s}."
-                                                .format(serial))
-            self.assertEqual(len(get_challenges(serial=tokenobj.token.serial)), 0)
+                    mock_log.assert_called_with(f"Failed to submit message to Firebase service for token {serial}.")
+            self.assertEqual(len(get_challenges(serial=token.token.serial)), 0)
 
             # Do the same with the parameter "exception", so that we receive an Error on HTTP
-            with self.app.test_request_context('/validate/check',
-                                               method='POST',
+            with self.app.test_request_context("/validate/check",
+                                               method="POST",
                                                data={"user": "cornelius",
                                                      "realm": self.realm1,
                                                      "exception": 1,
@@ -365,10 +352,10 @@ def test_03a_api_authenticate_fail(self):
             # succeeded even though polling is disabled
             # add responses, to simulate the successful communication to firebase
             # We also add the presence_required policy.
-            set_policy('push_presence', SCOPE.AUTH,
-                       action='{0!s}=1'.format(PUSH_ACTION.REQUIRE_PRESENCE))
+            set_policy("push_presence", SCOPE.AUTH,
+                       action=f"{PUSH_ACTION.REQUIRE_PRESENCE}=1")
             responses.replace(responses.POST,
-                              'https://fcm.googleapis.com/v1/projects/test-123456/messages:send',
+                              "https://fcm.googleapis.com/v1/projects/test-123456/messages:send",
                               body="""{}""",
                               content_type="application/json")
             with self.app.test_request_context('/validate/check',
@@ -378,19 +365,18 @@ def test_03a_api_authenticate_fail(self):
                                                      "pass": "pushpin"}):
                 res = self.app.full_dispatch_request()
                 self.assertTrue(res.status_code == 200, res)
-                jsonresp = res.json
-                self.assertTrue(jsonresp.get("result").get("status"))
-            self.assertEqual(len(get_challenges(serial=tokenobj.token.serial)), 1)
-            chal = get_challenges(serial=tokenobj.token.serial)[0]
+                self.assertTrue(res.json.get("result").get("status"))
+            self.assertEqual(len(get_challenges(serial=token.token.serial)), 1)
+            challenges = get_challenges(serial=token.token.serial)[0]
             # Check in the challenge for a require_presence value, this indicates, that the challenges was created
-            self.assertIn(chal.data.split(',').pop(), AVAILABLE_PRESENCE_OPTIONS_ALPHABETIC)
-            chal.delete()
+            self.assertIn(challenges.data.split(',').pop(), AVAILABLE_PRESENCE_OPTIONS_ALPHABETIC)
+            challenges.delete()
 
         remove_token(serial=serial)
         delete_smsgateway(self.firebase_config_name)
-        delete_policy('push_poll')
-        delete_policy('push1')
-        delete_policy('push_presence')
+        delete_policy("push_poll")
+        delete_policy("push1")
+        delete_policy("push_presence")
 
     @responses.activate
     def test_03b_api_authenticate_client(self):
@@ -398,178 +384,175 @@ def test_03b_api_authenticate_client(self):
         self.setUp_user_realms()
         # create FireBase Service and policies
         set_smsgateway(self.firebase_config_name,
-                       'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider',
+                       "privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider",
                        "myFB", FB_CONFIG_VALS)
         set_policy("push_config", scope=SCOPE.ENROLL,
-                   action="{0!s}={1!s}".format(PUSH_ACTION.FIREBASE_CONFIG,
-                                               self.firebase_config_name))
+                   action=f"{PUSH_ACTION.FIREBASE_CONFIG}={self.firebase_config_name}")
         # create push token
-        tokenobj = self._create_push_token()
-        serial = tokenobj.get_serial()
+        token = self._create_push_token()
+        serial = token.get_serial()
         # set PIN
-        tokenobj.set_pin("pushpin")
-        tokenobj.add_user(User("cornelius", self.realm1))
+        token.set_pin("pushpin")
+        token.add_user(User("cornelius", self.realm1))
 
         cached_fbtoken = {
-            'firebase_token': {
-                FB_CONFIG_VALS[FIREBASE_CONFIG.JSON_CONFIG]: _create_credential_mock()}}
-        self.app.config.setdefault('_app_local_store', {}).update(cached_fbtoken)
+            "firebase_token": {FB_CONFIG_VALS[FIREBASE_CONFIG.JSON_CONFIG]: _create_credential_mock()}}
+        self.app.config.setdefault("_app_local_store", {}).update(cached_fbtoken)
         # We mock the ServiceAccountCredentials, since we can not directly contact the Google API
-        with mock.patch('privacyidea.lib.smsprovider.FirebaseProvider.service_account'
-                        '.Credentials.from_service_account_file') as mySA:
+        with mock.patch("privacyidea.lib.smsprovider.FirebaseProvider.service_account"
+                        ".Credentials.from_service_account_file") as mock_service_account:
             # add responses, to simulate the communication to firebase
-            responses.add(responses.POST, 'https://fcm.googleapis.com/v1/projects'
-                                          '/test-123456/messages:send',
+            responses.add(responses.POST, "https://fcm.googleapis.com/v1/projects"
+                                          "/test-123456/messages:send",
                           body="""{}""",
                           content_type="application/json")
 
             # Send the first authentication request to trigger the challenge
-            with self.app.test_request_context('/validate/check',
-                                               method='POST',
+            with self.app.test_request_context("/validate/check",
+                                               method="POST",
                                                data={"user": "cornelius",
                                                      "realm": self.realm1,
                                                      "pass": "pushpin"}):
                 res = self.app.full_dispatch_request()
-                self.assertTrue(res.status_code == 200, res)
-                jsonresp = res.json
-                self.assertFalse(jsonresp.get("result").get("value"))
-                self.assertTrue(jsonresp.get("result").get("status"))
-                self.assertEqual(jsonresp.get("detail").get("serial"), tokenobj.token.serial)
-                self.assertTrue("transaction_id" in jsonresp.get("detail"))
-                transaction_id = jsonresp.get("detail").get("transaction_id")
-                self.assertEqual(jsonresp.get("detail").get("message"), DEFAULT_CHALLENGE_TEXT)
+                self.assertEqual(200, res.status_code)
+                json_response = res.json
+                self.assertFalse(json_response.get("result").get("value"))
+                self.assertTrue(json_response.get("result").get("status"))
+                self.assertEqual(json_response.get("detail").get("serial"), token.token.serial)
+                self.assertTrue("transaction_id" in json_response.get("detail"))
+                transaction_id = json_response.get("detail").get("transaction_id")
+                self.assertEqual(json_response.get("detail").get("message"), DEFAULT_CHALLENGE_TEXT)
 
             # Our ServiceAccountCredentials mock has not been called because we use a cached token
-            mySA.assert_not_called()
+            mock_service_account.assert_not_called()
             self.assertIn(FIREBASE_FILE, get_app_local_store()["firebase_token"])
             # remove cached Credentials
             get_app_local_store().pop("firebase_token")
 
         # The mobile device has not communicated with the backend, yet.
         # The user is not authenticated!
-        with self.app.test_request_context('/validate/check',
-                                           method='POST',
+        with self.app.test_request_context("/validate/check",
+                                           method="POST",
                                            data={"user": "cornelius",
                                                  "realm": self.realm1,
                                                  "pass": "",
                                                  "transaction_id": transaction_id}):
             res = self.app.full_dispatch_request()
-            self.assertTrue(res.status_code == 200, res)
-            jsonresp = res.json
+            self.assertEqual(200, res.status_code)
+            json_response = res.json
             # Result-Value is false, the user has not answered the challenge, yet
-            self.assertFalse(jsonresp.get("result").get("value"))
+            self.assertFalse(json_response.get("result").get("value"))
 
         # As the challenge has not been answered yet, the /validate/polltransaction endpoint returns false
-        with self.app.test_request_context('/validate/polltransaction', method='GET',
+        with self.app.test_request_context("/validate/polltransaction", method="GET",
                                            query_string={'transaction_id': transaction_id}):
             res = self.app.full_dispatch_request()
-            self.assertEqual(res.status_code, 200)
+            self.assertEqual(200, res.status_code)
             self.assertTrue(res.json["result"]["status"])
             self.assertFalse(res.json["result"]["value"])
 
         # Now the smartphone communicates with the backend and the challenge in the database table
         # is marked as answered successfully.
-        challengeobject_list = get_challenges(serial=tokenobj.token.serial,
-                                              transaction_id=transaction_id)
-        challengeobject_list[0].set_otp_status(True)
+        challenges = get_challenges(serial=token.token.serial, transaction_id=transaction_id)
+        challenges[0].set_otp_status(True)
 
         # As the challenge has been answered, the /validate/polltransaction endpoint returns true
-        with self.app.test_request_context('/validate/polltransaction', method='GET',
-                                           query_string={'transaction_id': transaction_id}):
+        with self.app.test_request_context("/validate/polltransaction", method="GET",
+                                           query_string={"transaction_id": transaction_id}):
             res = self.app.full_dispatch_request()
-            self.assertEqual(res.status_code, 200)
+            self.assertEqual(200, res.status_code)
             self.assertTrue(res.json["result"]["status"])
             self.assertTrue(res.json["result"]["value"])
 
-        with self.app.test_request_context('/validate/check',
-                                           method='POST',
+        with self.app.test_request_context("/validate/check",
+                                           method="POST",
                                            data={"user": "cornelius",
                                                  "realm": self.realm1,
                                                  "pass": "",
                                                  "state": transaction_id}):
             res = self.app.full_dispatch_request()
-            self.assertTrue(res.status_code == 200, res)
-            jsonresp = res.json
+            self.assertEqual(200, res.status_code)
+            json_response = res.json
             # Result-Value is True, since the challenge is marked resolved in the DB
-        self.assertTrue(jsonresp.get("result").get("value"))
+        self.assertTrue(json_response.get("result").get("value"))
 
         # As the challenge does not exist anymore, the /validate/polltransaction endpoint returns false
-        with self.app.test_request_context('/validate/polltransaction', method='GET',
-                                           query_string={'transaction_id': transaction_id}):
+        with self.app.test_request_context("/validate/polltransaction", method="GET",
+                                           query_string={"transaction_id": transaction_id}):
             res = self.app.full_dispatch_request()
-            self.assertEqual(res.status_code, 200)
+            self.assertEqual(200, res.status_code)
             self.assertTrue(res.json["result"]["status"])
             self.assertFalse(res.json["result"]["value"])
-        self.assertEqual(get_challenges(serial=tokenobj.token.serial), [])
+        self.assertEqual(get_challenges(serial=token.token.serial), [])
 
         # We mock the ServiceAccountCredentials, since we can not directly contact the Google API
         # Do single shot auth with waiting
         # Also mock time.time to be 4000 seconds in the future (exceeding the validity of myAccessTokenInfo),
         # so that we fetch a new auth token
-        with mock.patch('privacyidea.lib.smsprovider.FirebaseProvider.time') as mock_time:
+        with mock.patch("privacyidea.lib.smsprovider.FirebaseProvider.time") as mock_time:
             mock_time.time.return_value = time.time() + 4000
 
             with mock.patch(
-                    'privacyidea.lib.smsprovider.FirebaseProvider.service_account.Credentials'
-                    '.from_service_account_file') as mySA:
+                    "privacyidea.lib.smsprovider.FirebaseProvider.service_account.Credentials"
+                    ".from_service_account_file") as mock_service_account:
                 # alternative: side_effect instead of return_value
-                mySA.return_value = _create_credential_mock()
+                mock_service_account.return_value = _create_credential_mock()
 
                 # add responses, to simulate the communication to firebase
-                responses.add(responses.POST, 'https://fcm.googleapis.com/v1/projects/test-123456/messages:send',
+                responses.add(responses.POST, "https://fcm.googleapis.com/v1/projects/test-123456/messages:send",
                               body="""{}""",
                               content_type="application/json")
 
                 # In two seconds we need to run an update on the challenge table.
                 Timer(2, self._mark_challenge_as_accepted).start()
 
-                set_policy("push1", scope=SCOPE.AUTH, action="{0!s}=20".format(PUSH_ACTION.WAIT))
+                set_policy("push1", scope=SCOPE.AUTH, action=f"{PUSH_ACTION.WAIT}=20")
                 # Send the first authentication request to trigger the challenge
-                with self.app.test_request_context('/validate/check',
-                                                   method='POST',
+                with self.app.test_request_context("/validate/check",
+                                                   method="POST",
                                                    data={"user": "cornelius",
                                                          "realm": self.realm1,
                                                          "pass": "pushpin"}):
                     res = self.app.full_dispatch_request()
-                    self.assertTrue(res.status_code == 200, res)
-                    jsonresp = res.json
+                    self.assertEqual(200, res.status_code)
+                    json_response = res.json
                     # We successfully authenticated! YEAH!
-                    self.assertTrue(jsonresp.get("result").get("value"))
-                    self.assertTrue(jsonresp.get("result").get("status"))
-                    self.assertEqual(jsonresp.get("detail").get("serial"), tokenobj.token.serial)
+                    self.assertTrue(json_response.get("result").get("value"))
+                    self.assertTrue(json_response.get("result").get("status"))
+                    self.assertEqual(json_response.get("detail").get("serial"), token.token.serial)
                 delete_policy("push1")
 
             # Our ServiceAccountCredentials mock has been called once because we fetched a new token
-            mySA.assert_called_once()
+            mock_service_account.assert_called_once()
             self.assertIn(FIREBASE_FILE, get_app_local_store()["firebase_token"])
             self.assertEqual(get_app_local_store()["firebase_token"][FIREBASE_FILE].access_token,
                              "my_new_bearer_token")
 
         # Authentication fails, if the push notification is not accepted within the configured time
-        with mock.patch('privacyidea.lib.smsprovider.FirebaseProvider.service_account.Credentials'
-                        '.from_service_account_file') as mySA:
+        with mock.patch("privacyidea.lib.smsprovider.FirebaseProvider.service_account.Credentials"
+                        ".from_service_account_file") as mock_service_account:
             # alternative: side_effect instead of return_value
-            mySA.return_value = _create_credential_mock()
+            mock_service_account.return_value = _create_credential_mock()
 
             # add responses, to simulate the communication to firebase
-            responses.add(responses.POST, 'https://fcm.googleapis.com/v1/projects/test-123456/messages:send',
+            responses.add(responses.POST, "https://fcm.googleapis.com/v1/projects/test-123456/messages:send",
                           body="""{}""",
                           content_type="application/json")
 
-            set_policy("push1", scope=SCOPE.AUTH, action="{0!s}=1".format(PUSH_ACTION.WAIT))
+            set_policy("push1", scope=SCOPE.AUTH, action=f"{PUSH_ACTION.WAIT}=1")
             # Send the first authentication request to trigger the challenge
-            with self.app.test_request_context('/validate/check',
-                                               method='POST',
+            with self.app.test_request_context("/validate/check",
+                                               method="POST",
                                                data={"user": "cornelius",
                                                      "realm": self.realm1,
                                                      "pass": "pushpin"}):
                 res = self.app.full_dispatch_request()
-                self.assertTrue(res.status_code == 200, res)
-                jsonresp = res.json
+                self.assertEqual(200, res.status_code)
+                json_response = res.json
                 # We fail to authenticate! Oh No!
-                self.assertFalse(jsonresp.get("result").get("value"))
-                self.assertTrue(jsonresp.get("result").get("status"))
-                self.assertEqual(jsonresp.get("detail").get("serial"), tokenobj.token.serial)
+                self.assertFalse(json_response.get("result").get("value"))
+                self.assertTrue(json_response.get("result").get("status"))
+                self.assertEqual(json_response.get("detail").get("serial"), token.token.serial)
             delete_policy("push1")
         delete_policy('push_config')
         remove_token(serial=serial)
@@ -578,9 +561,9 @@ def _mark_challenge_as_accepted(self):
         # We simply mark all challenges as successfully answered!
         with self.app.test_request_context():
             challenges = get_challenges()
-            for chal in challenges:
-                chal.set_otp_status(True)
-                chal.save()
+            for challenge in challenges:
+                challenge.set_otp_status(True)
+                challenge.save()
 
     @responses.activate
     def test_04_api_authenticate_smartphone(self):
@@ -1025,7 +1008,7 @@ def test_06b_api_auth_presence(self):
                                                      PUSH_ACTION.WAIT: "10",
                                                      "password": "pushpin"}):
                 res = self.app.full_dispatch_request()
-                self.assertEqual(res.status_code, 200)
+                self.assertEqual(200, res.status_code)
                 response_json = res.json
                 self.assertTrue(response_json.get("result").get("value"))
                 self.assertTrue(response_json.get("result").get("status"))
@@ -1214,31 +1197,27 @@ def test_06d_api_auth_presence_custom(self):
                        'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider',
                        "myFB", FB_CONFIG_VALS)
         set_policy("push_config", scope=SCOPE.ENROLL,
-                   action="{0!s}={1!s}".format(PUSH_ACTION.FIREBASE_CONFIG,
-                                               self.firebase_config_name))
+                   action=f"{PUSH_ACTION.FIREBASE_CONFIG}={self.firebase_config_name}")
         set_policy("push_custom", scope=SCOPE.AUTH,
                    action=f"{PUSH_ACTION.PRESENCE_OPTIONS}=CUSTOM")
         set_policy("push_custom_options", scope=SCOPE.AUTH,
                    action=f"{PUSH_ACTION.PRESENCE_CUSTOM_OPTIONS}={custom_presence_options}")
         # create push token
-        tokenobj = self._create_push_token()
-        serial = tokenobj.get_serial()
+        token = self._create_push_token()
 
         # set PIN
-        tokenobj.set_pin("pushpin")
-        tokenobj.add_user(User("cornelius", self.realm1))
+        token.set_pin("pushpin")
+        token.add_user(User("cornelius", self.realm1))
 
         # Set a loginmode policy
         set_policy("webui", scope=SCOPE.WEBUI,
-                   action="{}={}".format(ACTION.LOGINMODE, LOGINMODE.PRIVACYIDEA))
+                   action=f"{ACTION.LOGINMODE}={LOGINMODE.PRIVACYIDEA}")
         # Set a policy to require presence
-        set_policy("push_require_presence", scope=SCOPE.AUTH, action="{0!s}=1".format(PUSH_ACTION.REQUIRE_PRESENCE))
-        presence_answer = None
-        challenge = None
+        set_policy("push_require_presence", scope=SCOPE.AUTH, action=f"{PUSH_ACTION.REQUIRE_PRESENCE}=1")
         with mock.patch('privacyidea.lib.smsprovider.FirebaseProvider.service_account.Credentials'
-                        '.from_service_account_file') as mySA:
+                        '.from_service_account_file') as mock_service_account:
             # alternative: side_effect instead of return_value
-            mySA.from_json_keyfile_name.return_value = _create_credential_mock()
+            mock_service_account.from_json_keyfile_name.return_value = _create_credential_mock()
 
             # add responses, to simulate the communication to firebase
             responses.add(responses.POST, 'https://fcm.googleapis.com/v1/projects/test-123456/messages:send',
@@ -1249,37 +1228,34 @@ def test_06d_api_auth_presence_custom(self):
                                                method='POST',
                                                data={"username": "cornelius",
                                                      "realm": self.realm1,
-                                                     # this will be overwritted by pushtoken_disable_wait
+                                                     # this will be overwritten by pushtoken_disable_wait
                                                      PUSH_ACTION.WAIT: "10",
                                                      "password": "pushpin"}):
                 res = self.app.full_dispatch_request()
                 self.assertEqual(res.status_code, 200)
                 # Get the challenge from the database
 
-                jsonresp = res.json
-                self.assertTrue(jsonresp.get("result").get("value"))
-                self.assertTrue(jsonresp.get("result").get("status"))
-                self.assertEqual(jsonresp.get("detail").get("serial"), tokenobj.token.serial)
-                self.assertIn("transaction_id", jsonresp.get("detail"))
-                transaction_id = jsonresp.get("detail").get("transaction_id")
-                challengeobject_list = get_challenges(serial=tokenobj.token.serial,
-                                                      transaction_id=transaction_id)
-                chal = challengeobject_list[0]  # TODO: Why is challenge a string?
-                challenge = chal.challenge
-                presence_answer = chal.get_data().split(
-                    ',').pop()  # The correct answer is always appended to the available options
+                json_response = res.json
+                self.assertTrue(json_response.get("result").get("value"))
+                self.assertTrue(json_response.get("result").get("status"))
+                self.assertEqual(json_response.get("detail").get("serial"), token.token.serial)
+                self.assertIn("transaction_id", json_response.get("detail"))
+                transaction_id = json_response.get("detail").get("transaction_id")
+                challenges = get_challenges(serial=token.token.serial, transaction_id=transaction_id)
+                challenge = challenges[0]
+                nonce = challenge.challenge
+                # The correct answer is always appended to the available options
+                presence_answer = challenge.get_data().split(',').pop()
                 challenge_text = DEFAULT_CHALLENGE_TEXT + f" Please press: {presence_answer}"
-                self.assertEqual(jsonresp.get("detail").get("message"), challenge_text)
+                self.assertEqual(json_response.get("detail").get("message"), challenge_text)
 
-        self.assertTrue(presence_answer != None)
-        self.assertTrue(presence_answer in custom_options_list)
+        self.assertIsNotNone(presence_answer)
+        self.assertIn(presence_answer, custom_options_list)
         # This is what the smartphone answers.
         # create the signature:
-        sign_data = f"{challenge}|{tokenobj.token.serial}|{presence_answer}"
+        sign_data = f"{nonce}|{token.token.serial}|{presence_answer}"
         signature = b32encode_and_unicode(
-            self.smartphone_private_key.sign(sign_data.encode("utf-8"),
-                                             padding.PKCS1v15(),
-                                             hashes.SHA256()))
+            self.smartphone_private_key.sign(sign_data.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256()))
 
         # We still cannot log in
         with self.app.test_request_context('/auth',
@@ -1289,18 +1265,18 @@ def test_06d_api_auth_presence_custom(self):
                                                  "password": "",
                                                  "transaction_id": transaction_id}):
             res = self.app.full_dispatch_request()
-            self.assertEqual(res.status_code, 401)
+            self.assertEqual(401, res.status_code, res)
             self.assertFalse(res.json['result']['status'])
 
         # Answer the challenge
         with self.app.test_request_context('/ttype/push',
                                            method='POST',
-                                           data={"serial": tokenobj.token.serial,
+                                           data={"serial": token.token.serial,
                                                  "nonce": challenge,
                                                  "signature": signature,
                                                  "presence_answer": presence_answer}):
             res = self.app.full_dispatch_request()
-            self.assertTrue(res.status_code == 200, res)
+            self.assertEqual(200, res.status_code, res)
             self.assertTrue(res.json['result']['status'])
             self.assertTrue(res.json['result']['value'])
 
@@ -1312,13 +1288,48 @@ def test_06d_api_auth_presence_custom(self):
                                                  "password": "",
                                                  "transaction_id": transaction_id}):
             res = self.app.full_dispatch_request()
-            self.assertEqual(res.status_code, 200)
+            self.assertEqual(200, res.status_code, res)
             self.assertTrue(res.json['result']['status'])
 
-        remove_token(tokenobj.get_serial())
+        remove_token(token.get_serial())
         delete_policy("webui")
         delete_policy("push_require_presence")
 
+    def test_06e_require_presence_text_replace(self):
+        # Set a loginmode policy
+        set_policy("webui", scope=SCOPE.WEBUI, action=f"{ACTION.LOGINMODE}={LOGINMODE.PRIVACYIDEA}")
+        # Set a policy to require presence
+        set_policy("push_require_presence", scope=SCOPE.AUTH, action=f"{PUSH_ACTION.REQUIRE_PRESENCE}=1")
+        set_policy("text", scope=SCOPE.AUTH, action="challenge_text=the answer is {presence_answer}")
+
+        self.setUp_user_realms()
+        token = self._create_push_token()
+        token.add_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG, POLL_ONLY)
+        token.set_pin("pushpin")
+        token.add_user(User("cornelius", self.realm1))
+        with self.app.test_request_context('/auth',
+                                           method='POST',
+                                           data={"username": "cornelius",
+                                                 "realm": self.realm1,
+                                                 "password": "pushpin"}):
+            res = self.app.full_dispatch_request()
+            self.assertEqual(200, res.status_code)
+            self.assertTrue(res.json.get("result").get("value"))
+            self.assertTrue(res.json.get("result").get("status"))
+            self.assertEqual(res.json.get("detail").get("serial"), token.token.serial)
+            self.assertIn("transaction_id", res.json.get("detail"))
+            transaction_id = res.json.get("detail").get("transaction_id")
+            challenges = get_challenges(serial=token.token.serial, transaction_id=transaction_id)
+            challenge = challenges[0]
+            # The correct answer is always appended to the available options
+            presence_answer = challenge.get_data().split(",").pop()
+            challenge_text = f"the answer is {presence_answer}"
+            self.assertEqual(challenge_text, res.json.get("detail").get("message"))
+        delete_policy("text")
+        delete_policy("push_require_presence")
+        delete_policy("webui")
+        remove_token(token.get_serial())
+
     def test_07_check_timestamp(self):
         timestamp_fmt = 'broken_timestamp_010203'
         self.assertRaisesRegex(privacyIDEAError,
@@ -1351,104 +1362,97 @@ def test_10_api_endpoint(self):
         # first check for unused request methods
         g = FakeFlaskG()
         g.policy_object = PolicyClass()
-        builder = EnvironBuilder(method='PUT',
-                                 headers={})
+        builder = EnvironBuilder(method='PUT', headers={})
 
-        req = Request(builder.get_environ())
-        req.all_data = {'serial': 'SPASS01'}
+        request = Request(builder.get_environ())
+        request.all_data = {'serial': 'SPASS01'}
         self.assertRaisesRegex(privacyIDEAError,
                                'Method PUT not allowed in \'api_endpoint\' '
                                'for push token.',
-                               PushTokenClass.api_endpoint, req, g)
+                               PushTokenClass.api_endpoint, request, g)
 
         # check for parameter error in POST request
-        builder = EnvironBuilder(method='POST',
-                                 headers={})
+        builder = EnvironBuilder(method='POST', headers={})
 
-        req = Request(builder.get_environ())
-        req.all_data = {'serial': 'SPASS01'}
-        self.assertRaisesRegex(ParameterError, 'Missing parameters!',
-                               PushTokenClass.api_endpoint, req, g)
+        request = Request(builder.get_environ())
+        request.all_data = {'serial': 'SPASS01'}
+        self.assertRaisesRegex(ParameterError, 'Missing parameters!', PushTokenClass.api_endpoint, request, g)
 
         # check for missing parameter in GET request
-        builder = EnvironBuilder(method='GET',
-                                 headers={})
+        builder = EnvironBuilder(method='GET', headers={})
 
-        req = Request(builder.get_environ())
-        req.all_data = {'serial': 'SPASS01', 'timestamp': '2019-10-05T22:13:23+0100'}
+        request = Request(builder.get_environ())
+        request.all_data = {'serial': 'SPASS01', 'timestamp': '2019-10-05T22:13:23+0100'}
         self.assertRaisesRegex(ParameterError, 'Missing parameter: \'signature\'',
-                               PushTokenClass.api_endpoint, req, g)
+                               PushTokenClass.api_endpoint, request, g)
 
         # check for invalid timestamp (very old)
-        req = Request(builder.get_environ())
-        req.all_data = {'serial': 'SPASS01',
-                        'timestamp': '2019-10-05T22:13:23+0100',
-                        'signature': 'unknown'}
+        request = Request(builder.get_environ())
+        request.all_data = {'serial': 'SPASS01',
+                            'timestamp': '2019-10-05T22:13:23+0100',
+                            'signature': 'unknown'}
         self.assertRaisesRegex(privacyIDEAError,
                                r'Timestamp 2019-10-05T22:13:23\+0100 not in valid range.',
-                               PushTokenClass.api_endpoint, req, g)
+                               PushTokenClass.api_endpoint, request, g)
 
         # check for invalid timestamp (recent but too early)
-        req = Request(builder.get_environ())
-        req.all_data = {'serial': 'SPASS01',
-                        'timestamp': (datetime.now(utc)
-                                      - timedelta(minutes=2)).isoformat(),
-                        'signature': 'unknown'}
+        request = Request(builder.get_environ())
+        request.all_data = {'serial': 'SPASS01',
+                            'timestamp': (datetime.now(utc) - timedelta(minutes=2)).isoformat(),
+                            'signature': 'unknown'}
         self.assertRaisesRegex(privacyIDEAError,
                                r'Timestamp .* not in valid range.',
-                               PushTokenClass.api_endpoint, req, g)
+                               PushTokenClass.api_endpoint, request, g)
 
         # check for invalid timestamp (recent but too late)
-        req = Request(builder.get_environ())
-        req.all_data = {'serial': 'SPASS01',
-                        'timestamp': (datetime.now(utc)
-                                      + timedelta(minutes=2)).isoformat(),
-                        'signature': 'unknown'}
+        request = Request(builder.get_environ())
+        request.all_data = {'serial': 'SPASS01',
+                            'timestamp': (datetime.now(utc) + timedelta(minutes=2)).isoformat(),
+                            'signature': 'unknown'}
         self.assertRaisesRegex(privacyIDEAError,
                                r'Timestamp .* not in valid range.',
-                               PushTokenClass.api_endpoint, req, g)
+                               PushTokenClass.api_endpoint, request, g)
 
         # check for broken timestamp
-        req = Request(builder.get_environ())
-        req.all_data = {'serial': 'SPASS01',
-                        'timestamp': '2019-broken-timestamp',
-                        'signature': 'unknown'}
+        request = Request(builder.get_environ())
+        request.all_data = {'serial': 'SPASS01',
+                            'timestamp': '2019-broken-timestamp',
+                            'signature': 'unknown'}
         self.assertRaisesRegex(privacyIDEAError,
                                r'Could not parse timestamp .*\. ISO-Format required.',
-                               PushTokenClass.api_endpoint, req, g)
+                               PushTokenClass.api_endpoint, request, g)
 
         # check for timestamp of wrong type
-        req = Request(builder.get_environ())
-        req.all_data = {'serial': 'SPASS01',
-                        'timestamp': datetime.utcnow(),
-                        'signature': 'unknown'}
+        request = Request(builder.get_environ())
+        request.all_data = {'serial': 'SPASS01',
+                            'timestamp': datetime.now(timezone.utc),
+                            'signature': 'unknown'}
         self.assertRaisesRegex(privacyIDEAError,
                                r'Could not parse timestamp .*\. ISO-Format required.',
-                               PushTokenClass.api_endpoint, req, g)
+                               PushTokenClass.api_endpoint, request, g)
 
         # check for timezone unaware timestamp (we assume UTC then)
-        req = Request(builder.get_environ())
-        req.all_data = {'serial': 'SPASS01',
-                        'timestamp': datetime.utcnow().isoformat(),
-                        'signature': 'unknown'}
+        request = Request(builder.get_environ())
+        request.all_data = {'serial': 'SPASS01',
+                            'timestamp': datetime.now(timezone.utc).isoformat(),
+                            'signature': 'unknown'}
         self.assertRaisesRegex(privacyIDEAError,
                                r'Could not verify signature!',
-                               PushTokenClass.api_endpoint, req, g)
+                               PushTokenClass.api_endpoint, request, g)
 
         # create a push token
-        tparams = {'type': 'push', 'genkey': 1}
-        tparams.update(FB_CONFIG_VALS)
-        tok = init_token(param=tparams)
-        serial = tok.get_serial()
+        token_param = {'type': 'push', 'genkey': 1}
+        token_param.update(FB_CONFIG_VALS)
+        token = init_token(param=token_param)
+        serial = token.get_serial()
         # now we need to perform the second rollout step
-        builder = EnvironBuilder(method='POST',
-                                 headers={})
-        req = Request(builder.get_environ())
-        req.all_data = {"enrollment_credential": tok.get_tokeninfo("enrollment_credential"),
-                        "serial": serial,
-                        "pubkey": self.smartphone_public_key_pem_urlsafe,
-                        "fbtoken": "firebaseT"}
-        res = PushTokenClass.api_endpoint(req, g)
+        builder = EnvironBuilder(method='POST', headers={})
+        request = Request(builder.get_environ())
+        request.all_data = {"enrollment_credential": token.get_tokeninfo("enrollment_credential"),
+                            "serial": serial,
+                            "pubkey": self.smartphone_public_key_pem_urlsafe,
+                            "fbtoken": "firebaseT"}
+        res = PushTokenClass.api_endpoint(request, g)
         self.assertEqual(res[0], 'json', res)
         self.assertTrue(res[1]['result']['value'], res)
         self.assertTrue(res[1]['result']['status'], res)
@@ -1459,18 +1463,18 @@ def test_10_api_endpoint(self):
     def test_11_api_endpoint_update_fbtoken(self):
         g = FakeFlaskG()
         # create a push token
-        tparams = {'type': 'push', 'genkey': 1}
-        tparams.update(FB_CONFIG_VALS)
-        tok = init_token(param=tparams)
-        serial = tok.get_serial()
+        token_param = {'type': 'push', 'genkey': 1}
+        token_param.update(FB_CONFIG_VALS)
+        token = init_token(param=token_param)
+        serial = token.get_serial()
 
         # Run enrollment step 2
-        tok.update({"enrollment_credential": tok.get_tokeninfo("enrollment_credential"),
-                    "serial": serial,
-                    "fbtoken": "firebasetoken1",
-                    "pubkey": self.smartphone_public_key_pem_urlsafe})
-        self.assertEqual(tok.token.get('rollout_state'), 'enrolled', tok)
-        self.assertEqual(tok.get_tokeninfo('firebase_token'), 'firebasetoken1', tok)
+        token.update({"enrollment_credential": token.get_tokeninfo("enrollment_credential"),
+                      "serial": serial,
+                      "fbtoken": "firebasetoken1",
+                      "pubkey": self.smartphone_public_key_pem_urlsafe})
+        self.assertEqual(token.token.get('rollout_state'), 'enrolled', token)
+        self.assertEqual(token.get_tokeninfo('firebase_token'), 'firebasetoken1', token)
 
         req_data = {'new_fb_token': 'firebasetoken2',
                     'serial': serial,
@@ -1487,9 +1491,7 @@ def test_11_api_endpoint_update_fbtoken(self):
 
         # Create a correct signature
         sign_string = "{new_fb_token}|{serial}|{timestamp}".format(**req_data)
-        sig = self.smartphone_private_key.sign(sign_string.encode('utf8'),
-                                               padding.PKCS1v15(),
-                                               hashes.SHA256())
+        sig = self.smartphone_private_key.sign(sign_string.encode('utf8'), padding.PKCS1v15(), hashes.SHA256())
         req_data.update({'signature': b32encode(sig)})
 
         # and perform the firebase token update
@@ -1502,18 +1504,16 @@ def test_11_api_endpoint_update_fbtoken(self):
         self.assertTrue(res[1]['result']['value'], res)
         self.assertTrue(res[1]['result']['status'], res)
 
-        self.assertEqual(tok.token.get('rollout_state'), 'enrolled', tok)
-        self.assertEqual(tok.get_tokeninfo('firebase_token'), req_data['new_fb_token'], tok)
-        tok.delete_token()
+        self.assertEqual(token.token.get('rollout_state'), 'enrolled', token)
+        self.assertEqual(token.get_tokeninfo('firebase_token'), req_data['new_fb_token'], token)
+        token.delete_token()
 
     def test_15_poll_endpoint(self):
         g = FakeFlaskG()
         set_policy("push1", scope=SCOPE.ENROLL,
-                   action="{0!s}={1!s},{2!s}={3!s},{4!s}={5!s}".format(
-                       PUSH_ACTION.FIREBASE_CONFIG,
-                       self.firebase_config_name,
-                       PUSH_ACTION.REGISTRATION_URL, REGISTRATION_URL,
-                       PUSH_ACTION.TTL, TTL))
+                   action=f"{PUSH_ACTION.FIREBASE_CONFIG}={self.firebase_config_name},"
+                          f"{PUSH_ACTION.REGISTRATION_URL}={REGISTRATION_URL},"
+                          f"{PUSH_ACTION.TTL}={TTL}")
         g.policy_object = PolicyClass()
         # set up the Firebase Gateway
         r = set_smsgateway(self.firebase_config_name,
@@ -1522,32 +1522,29 @@ def test_15_poll_endpoint(self):
         self.assertGreater(r, 0)
 
         # create a new push token
-        tok = self._create_push_token()
-        serial = tok.get_serial()
+        token = self._create_push_token()
+        serial = token.get_serial()
 
         # this is the default timestamp for polling in this test
         timestamp = datetime(2020, 6, 19, 13, 27, tzinfo=utc)
 
         # create a poll request
         # first create a signature
-        ts = timestamp.isoformat()
-        sign_string = "{serial}|{timestamp}".format(serial=serial, timestamp=ts)
-        sig = self.smartphone_private_key.sign(sign_string.encode('utf8'),
-                                               padding.PKCS1v15(),
-                                               hashes.SHA256())
-
-        builder = EnvironBuilder(method='GET',
-                                 headers={})
-        req = Request(builder.get_environ())
-        req.all_data = {'serial': serial,
-                        'timestamp': ts,
-                        'signature': b32encode(sig)}
+        timestamp_str = timestamp.isoformat()
+        sign_string = f"{serial}|{timestamp_str}"
+        signature = self.smartphone_private_key.sign(sign_string.encode('utf8'), padding.PKCS1v15(), hashes.SHA256())
+
+        builder = EnvironBuilder(method='GET', headers={})
+        request = Request(builder.get_environ())
+        request.all_data = {'serial': serial,
+                            'timestamp': timestamp_str,
+                            'signature': b32encode(signature)}
         # poll for challenges
-        with mock.patch('privacyidea.models.datetime') as mock_dt1, \
-                mock.patch('privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
+        with mock.patch('privacyidea.models.datetime') as mock_dt1, mock.patch(
+                'privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
             mock_dt1.utcnow.return_value = timestamp.replace(tzinfo=None) + timedelta(seconds=15)
             mock_dt2.now.return_value = timestamp + timedelta(seconds=15)
-            res = PushTokenClass.api_endpoint(req, g)
+            res = PushTokenClass.api_endpoint(request, g)
         self.assertTrue(res[1]['result']['status'], res)
         # No challenge created yet
         self.assertEqual(res[1]['result']['value'], [], res[1]['result'])
@@ -1560,8 +1557,8 @@ def test_15_poll_endpoint(self):
             challenge = b32encode_and_unicode(geturandom())
             db_challenge = Challenge(serial, challenge=challenge)
             db_challenge.save()
-        tid = db_challenge.get_transaction_id()
-        self.assertGreater(len(get_challenges(transaction_id=tid)), 0)
+        transaction_id = db_challenge.get_transaction_id()
+        self.assertGreater(len(get_challenges(transaction_id=transaction_id)), 0)
 
         # now check that we receive the challenge when polling
         # since we mock the time we can use the same request data
@@ -1569,17 +1566,17 @@ def test_15_poll_endpoint(self):
                 mock.patch('privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
             mock_dt1.utcnow.return_value = timestamp.replace(tzinfo=None) + timedelta(seconds=15)
             mock_dt2.now.return_value = timestamp + timedelta(seconds=15)
-            res = PushTokenClass.api_endpoint(req, g)
+            res = PushTokenClass.api_endpoint(request, g)
         self.assertTrue(res[1]['result']['status'], res)
-        chall = res[1]['result']['value'][0]
-        self.assertEqual(chall['nonce'], challenge, chall)
-        self.assertIn('signature', chall, chall)
+        server_challenge = res[1]['result']['value'][0]
+        self.assertEqual(server_challenge['nonce'], challenge, server_challenge)
+        self.assertIn('signature', server_challenge, server_challenge)
         # check that the signature matches
-        sign_string = "{nonce}|{url}|{serial}|{question}|{title}|{sslverify}".format(**chall)
+        sign_string = "{nonce}|{url}|{serial}|{question}|{title}|{sslverify}".format(**server_challenge)
         parsed_stripped_server_pubkey = serialization.load_pem_public_key(
             to_bytes(self.server_public_key_pem),
             default_backend())
-        parsed_stripped_server_pubkey.verify(b32decode(chall['signature']),
+        parsed_stripped_server_pubkey.verify(b32decode(server_challenge['signature']),
                                              sign_string.encode('utf8'),
                                              padding.PKCS1v15(),
                                              hashes.SHA256())
@@ -1587,175 +1584,167 @@ def test_15_poll_endpoint(self):
 
         # Now mark the challenge as answered so we receive an empty list
         db_challenge.set_otp_status(True)
-        with mock.patch('privacyidea.models.datetime') as mock_dt1, \
-                mock.patch('privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
+        with mock.patch('privacyidea.models.datetime') as mock_dt1, mock.patch(
+                'privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
             mock_dt1.utcnow.return_value = timestamp.replace(tzinfo=None) + timedelta(seconds=15)
             mock_dt2.now.return_value = timestamp + timedelta(seconds=15)
-            res = PushTokenClass.api_endpoint(req, g)
+            res = PushTokenClass.api_endpoint(request, g)
         self.assertTrue(res[1]['result']['status'], res)
         self.assertEqual(res[1]['result']['value'], [], res[1]['result']['value'])
 
         # disallow polling through a policy
-        set_policy('push_poll', SCOPE.AUTH,
-                   action='{0!s}={1!s}'.format(PUSH_ACTION.ALLOW_POLLING,
-                                               PushAllowPolling.DENY))
-        with mock.patch('privacyidea.models.datetime') as mock_dt1, \
-                mock.patch('privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
+        set_policy('push_poll', SCOPE.AUTH, action=f'{PUSH_ACTION.ALLOW_POLLING}={PushAllowPolling.DENY}')
+        with mock.patch('privacyidea.models.datetime') as mock_dt1, mock.patch(
+                'privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
             mock_dt1.utcnow.return_value = timestamp.replace(tzinfo=None) + timedelta(seconds=15)
             mock_dt2.now.return_value = timestamp + timedelta(seconds=15)
             self.assertRaisesRegex(PolicyError,
                                    r'Polling not allowed!',
-                                   PushTokenClass.api_endpoint, req, g)
+                                   PushTokenClass.api_endpoint, request, g)
 
         # disallow polling based on a per token configuration
-        set_policy('push_poll', SCOPE.AUTH,
-                   action='{0!s}={1!s}'.format(PUSH_ACTION.ALLOW_POLLING,
-                                               PushAllowPolling.TOKEN))
+        set_policy('push_poll', SCOPE.AUTH, action=f'{PUSH_ACTION.ALLOW_POLLING}={PushAllowPolling.TOKEN}')
         # If no tokeninfo is set, allow polling
-        with mock.patch('privacyidea.models.datetime') as mock_dt1, \
-                mock.patch('privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
+        with mock.patch('privacyidea.models.datetime') as mock_dt1, mock.patch(
+                'privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
             mock_dt1.utcnow.return_value = timestamp.replace(tzinfo=None) + timedelta(seconds=15)
             mock_dt2.now.return_value = timestamp + timedelta(seconds=15)
-            res = PushTokenClass.api_endpoint(req, g)
+            res = PushTokenClass.api_endpoint(request, g)
         self.assertTrue(res[1]['result']['status'], res)
         self.assertEqual(res[1]['result']['value'], [], res[1]['result']['value'])
 
         # now set the tokeninfo POLLING_ALLOWED to 'False'
-        tok.add_tokeninfo(POLLING_ALLOWED, 'False')
-        with mock.patch('privacyidea.models.datetime') as mock_dt1, \
-                mock.patch('privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
+        token.add_tokeninfo(POLLING_ALLOWED, 'False')
+        with mock.patch('privacyidea.models.datetime') as mock_dt1, mock.patch(
+                'privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
             mock_dt1.utcnow.return_value = timestamp.replace(tzinfo=None) + timedelta(seconds=15)
             mock_dt2.now.return_value = timestamp + timedelta(seconds=15)
-            self.assertRaisesRegex(PolicyError,
-                                   r'Polling not allowed!',
-                                   PushTokenClass.api_endpoint, req, g)
+            self.assertRaisesRegex(PolicyError, r'Polling not allowed!', PushTokenClass.api_endpoint, request, g)
 
         # Explicitly allow polling for this token
-        tok.add_tokeninfo(POLLING_ALLOWED, 'True')
-        with mock.patch('privacyidea.models.datetime') as mock_dt1, \
-                mock.patch('privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
+        token.add_tokeninfo(POLLING_ALLOWED, 'True')
+        with mock.patch('privacyidea.models.datetime') as mock_dt1, mock.patch(
+                'privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
             mock_dt1.utcnow.return_value = timestamp.replace(tzinfo=None) + timedelta(seconds=15)
             mock_dt2.now.return_value = timestamp + timedelta(seconds=15)
-            res = PushTokenClass.api_endpoint(req, g)
+            res = PushTokenClass.api_endpoint(request, g)
         self.assertTrue(res[1]['result']['status'], res)
         self.assertEqual(res[1]['result']['value'], [], res[1]['result']['value'])
 
         # If polling for this token is denied but the overall configuration
         # allows polling, the tokeninfo is ignored
-        tok.add_tokeninfo(POLLING_ALLOWED, 'False')
+        token.add_tokeninfo(POLLING_ALLOWED, 'False')
         set_policy('push_poll', SCOPE.AUTH,
-                   action='{0!s}={1!s}'.format(PUSH_ACTION.ALLOW_POLLING,
-                                               PushAllowPolling.ALLOW))
-        with mock.patch('privacyidea.models.datetime') as mock_dt1, \
-                mock.patch('privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
+                   action=f'{PUSH_ACTION.ALLOW_POLLING}={PushAllowPolling.ALLOW}')
+        with mock.patch('privacyidea.models.datetime') as mock_dt1, mock.patch(
+                'privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
             mock_dt1.utcnow.return_value = timestamp.replace(tzinfo=None) + timedelta(seconds=15)
             mock_dt2.now.return_value = timestamp + timedelta(seconds=15)
-            res = PushTokenClass.api_endpoint(req, g)
+            res = PushTokenClass.api_endpoint(request, g)
         self.assertTrue(res[1]['result']['status'], res)
         self.assertEqual(res[1]['result']['value'], [], res[1]['result']['value'])
 
         # this should also work if there is no ALLOW_POLLING policy
         delete_policy('push_poll')
-        with mock.patch('privacyidea.models.datetime') as mock_dt1, \
-                mock.patch('privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
+        with mock.patch('privacyidea.models.datetime') as mock_dt1, mock.patch(
+                'privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
             mock_dt1.utcnow.return_value = timestamp.replace(tzinfo=None) + timedelta(seconds=15)
             mock_dt2.now.return_value = timestamp + timedelta(seconds=15)
-            res = PushTokenClass.api_endpoint(req, g)
+            res = PushTokenClass.api_endpoint(request, g)
         self.assertTrue(res[1]['result']['status'], res)
         self.assertEqual(res[1]['result']['value'], [], res[1]['result']['value'])
 
         # check for a non-existing serial
         unknown_serial = 'unknown_serial_01'
         # we shouldn't run into a signature check so we don't create one
-        req.all_data = {'serial': unknown_serial,
-                        'timestamp': ts,
-                        'signature': b32encode(b'no signature check')}
+        request.all_data = {'serial': unknown_serial,
+                            'timestamp': timestamp_str,
+                            'signature': b32encode(b'no signature check')}
         # poll for challenges
-        with mock.patch('privacyidea.models.datetime') as mock_dt1, \
-                mock.patch('privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
+        with mock.patch('privacyidea.models.datetime') as mock_dt1, mock.patch(
+                'privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
             mock_dt1.utcnow.return_value = timestamp.replace(tzinfo=None) + timedelta(seconds=15)
             mock_dt2.now.return_value = timestamp + timedelta(seconds=15)
             self.assertRaisesRegex(privacyIDEAError,
                                    r'Could not verify signature!',
-                                   PushTokenClass.api_endpoint, req, g)
+                                   PushTokenClass.api_endpoint, request, g)
 
         # serial exists but signature is wrong
-        sig_fail = bytearray(sig)
-        sig_fail[0] += 1
-        req.all_data = {'serial': serial,
-                        'timestamp': ts,
-                        'signature': b32encode(sig_fail)}
+        wrong_signature = bytearray(signature)
+        wrong_signature[0] += 1
+        request.all_data = {'serial': serial,
+                            'timestamp': timestamp_str,
+                            'signature': b32encode(wrong_signature)}
         # poll for challenges
-        with mock.patch('privacyidea.models.datetime') as mock_dt1, \
-                mock.patch('privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
+        with mock.patch('privacyidea.models.datetime') as mock_dt1, mock.patch(
+                'privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
             mock_dt1.utcnow.return_value = timestamp.replace(tzinfo=None) + timedelta(seconds=15)
             mock_dt2.now.return_value = timestamp + timedelta(seconds=15)
             self.assertRaisesRegex(privacyIDEAError,
                                    r'Could not verify signature!',
-                                   PushTokenClass.api_endpoint, req, g)
+                                   PushTokenClass.api_endpoint, request, g)
 
         # check for a wrongly created signature (inverted timestamp, serial)
-        sign_string2 = "{timestamp}|{serial}".format(serial=serial, timestamp=ts)
-        sig_fail2 = self.smartphone_private_key.sign(sign_string2.encode('utf8'),
-                                                     padding.PKCS1v15(),
-                                                     hashes.SHA256())
-        req.all_data = {'serial': serial,
-                        'timestamp': ts,
-                        'signature': b32encode(sig_fail2)}
+        sign_string2 = f"{timestamp_str}|{serial}"
+        wrong_signature2 = self.smartphone_private_key.sign(sign_string2.encode('utf8'), padding.PKCS1v15(),
+                                                            hashes.SHA256())
+        request.all_data = {'serial': serial,
+                            'timestamp': timestamp_str,
+                            'signature': b32encode(wrong_signature2)}
         # poll for challenges
-        with mock.patch('privacyidea.models.datetime') as mock_dt1, \
-                mock.patch('privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
+        with mock.patch('privacyidea.models.datetime') as mock_dt1, mock.patch(
+                'privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
             mock_dt1.utcnow.return_value = timestamp.replace(tzinfo=None) + timedelta(seconds=15)
             mock_dt2.now.return_value = timestamp + timedelta(seconds=15)
             self.assertRaisesRegex(privacyIDEAError,
                                    r'Could not verify signature!',
-                                   PushTokenClass.api_endpoint, req, g)
+                                   PushTokenClass.api_endpoint, request, g)
 
         # the serial exists but does not belong to a push token
-        tok2 = init_token(param={'type': 'hotp', 'genkey': 1})
-        serial2 = tok2.get_serial()
+        token2 = init_token(param={'type': 'hotp', 'genkey': 1})
+        serial2 = token2.get_serial()
         # we shouldn't run into the signature check here
-        req.all_data = {'serial': serial2,
-                        'timestamp': datetime.utcnow().isoformat(),
-                        'signature': b32encode(b"signature not needed")}
+        request.all_data = {'serial': serial2,
+                            'timestamp': datetime.now(tz=utc).isoformat(),
+                            'signature': b32encode(b"signature not needed")}
         # poll for challenges
         self.assertRaisesRegex(privacyIDEAError,
                                r'Could not verify signature!',
-                               PushTokenClass.api_endpoint, req, g)
+                               PushTokenClass.api_endpoint, request, g)
 
         # wrongly configured push token (no firebase config)
-        tok.del_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG)
+        token.del_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG)
         # We are missing a registration URL, thus polling of challenges fails
         delete_policy("push1")
-        req.all_data = {'serial': serial,
-                        'timestamp': ts,
-                        'signature': b32encode(sig)}
+        request.all_data = {'serial': serial,
+                            'timestamp': timestamp_str,
+                            'signature': b32encode(signature)}
         # poll for challenges
-        with mock.patch('privacyidea.models.datetime') as mock_dt1, \
-                mock.patch('privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
+        with mock.patch('privacyidea.models.datetime') as mock_dt1, mock.patch(
+                'privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
             mock_dt1.utcnow.return_value = timestamp.replace(tzinfo=None) + timedelta(seconds=15)
             mock_dt2.now.return_value = timestamp + timedelta(seconds=15)
             self.assertRaisesRegex(privacyIDEAError,
                                    r'Could not verify signature!',
-                                   PushTokenClass.api_endpoint, req, g)
+                                   PushTokenClass.api_endpoint, request, g)
 
         # unknown firebase configuration
-        tok.add_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG, 'my unknown firebase config')
-        req.all_data = {'serial': serial,
-                        'timestamp': ts,
-                        'signature': b32encode(sig)}
+        token.add_tokeninfo(PUSH_ACTION.FIREBASE_CONFIG, 'my unknown firebase config')
+        request.all_data = {'serial': serial,
+                            'timestamp': timestamp_str,
+                            'signature': b32encode(signature)}
         # poll for challenges
-        with mock.patch('privacyidea.models.datetime') as mock_dt1, \
-                mock.patch('privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
+        with mock.patch('privacyidea.models.datetime') as mock_dt1, mock.patch(
+                'privacyidea.lib.tokens.pushtoken.datetime') as mock_dt2:
             mock_dt1.utcnow.return_value = timestamp.replace(tzinfo=None) + timedelta(seconds=15)
             mock_dt2.now.return_value = timestamp + timedelta(seconds=15)
             self.assertRaisesRegex(privacyIDEAError,
                                    r'Could not verify signature!',
-                                   PushTokenClass.api_endpoint, req, g)
+                                   PushTokenClass.api_endpoint, request, g)
 
         # cleanup
-        tok.delete_token()
-        tok2.delete_token()
+        token.delete_token()
+        token2.delete_token()
         delete_smsgateway(self.firebase_config_name)
 
     @responses.activate
@@ -1767,24 +1756,22 @@ def test_20_api_authenticate_two_tokens(self):
                        'privacyidea.lib.smsprovider.FirebaseProvider.FirebaseProvider',
                        "myFB", FB_CONFIG_VALS)
         set_policy("push_config", scope=SCOPE.ENROLL,
-                   action="{0!s}={1!s}".format(PUSH_ACTION.FIREBASE_CONFIG,
-                                               self.firebase_config_name))
+                   action=f"{PUSH_ACTION.FIREBASE_CONFIG}={self.firebase_config_name}")
         # create push token
-        tokenobj1 = self._create_push_token()
-        serial1 = tokenobj1.get_serial()
+        token1 = self._create_push_token()
+        serial1 = token1.get_serial()
         # create 2nd push token
-        tokenobj2 = self._create_push_token()
-        serial2 = tokenobj2.get_serial()
+        token2 = self._create_push_token()
+        serial2 = token2.get_serial()
         self.assertNotEqual(serial1, serial2)
         # set PIN
-        tokenobj1.set_pin("pushpin")
-        tokenobj1.add_user(User("cornelius", self.realm1))
-        tokenobj2.set_pin("pushpin")
-        tokenobj2.add_user(User("cornelius", self.realm1))
+        token1.set_pin("pushpin")
+        token1.add_user(User("cornelius", self.realm1))
+        token2.set_pin("pushpin")
+        token2.add_user(User("cornelius", self.realm1))
 
         # set the policy for require presence
-        set_policy('push_presence', SCOPE.AUTH,
-                   action='{0!s}=1'.format(PUSH_ACTION.REQUIRE_PRESENCE))
+        set_policy('push_presence', SCOPE.AUTH, action=f'{PUSH_ACTION.REQUIRE_PRESENCE}=1')
 
         cached_fbtoken = {
             'firebase_token': {
@@ -1792,7 +1779,7 @@ def test_20_api_authenticate_two_tokens(self):
         self.app.config.setdefault('_app_local_store', {}).update(cached_fbtoken)
         # We mock the ServiceAccountCredentials, since we can not directly contact the Google API
         with mock.patch('privacyidea.lib.smsprovider.FirebaseProvider.service_account'
-                        '.Credentials.from_service_account_file') as mySA:
+                        '.Credentials.from_service_account_file') as mock_service_account:
             # add responses, to simulate the communication to firebase
             responses.add(responses.POST, 'https://fcm.googleapis.com/v1/projects'
                                           '/test-123456/messages:send',
@@ -1806,20 +1793,19 @@ def test_20_api_authenticate_two_tokens(self):
                                                      "realm": self.realm1,
                                                      "pass": "pushpin"}):
                 res = self.app.full_dispatch_request()
-                self.assertTrue(res.status_code == 200, res)
-                jsonresp = res.json
-                self.assertFalse(jsonresp.get("result").get("value"))
-                self.assertTrue(jsonresp.get("result").get("status"))
-                multi_challenge = jsonresp.get("detail").get("multi_challenge")
+                self.assertEqual(200, res.status_code, res)
+                json_response = res.json
+                self.assertFalse(json_response.get("result").get("value"))
+                self.assertTrue(json_response.get("result").get("status"))
+                multi_challenge = json_response.get("detail").get("multi_challenge")
                 # Ensure, that both messages are the same, i.e. the user is requested to press the same button
-                self.assertEqual(multi_challenge[0].get("message"),
-                                 multi_challenge[1].get("message"),
+                self.assertEqual(multi_challenge[0].get("message"), multi_challenge[1].get("message"),
                                  multi_challenge)
                 self.assertIn("Please press:", multi_challenge[0].get("message"))
-                transaction_id = jsonresp.get("detail").get("transaction_id")
+                transaction_id = json_response.get("detail").get("transaction_id")
 
             # Our ServiceAccountCredentials mock has not been called because we use a cached token
-            mySA.assert_not_called()
+            mock_service_account.assert_not_called()
             self.assertIn(FIREBASE_FILE, get_app_local_store()["firebase_token"])
             # remove cached Credentials
             get_app_local_store().pop("firebase_token")
@@ -1833,30 +1819,29 @@ def test_20_api_authenticate_two_tokens(self):
                                                  "pass": "",
                                                  "transaction_id": transaction_id}):
             res = self.app.full_dispatch_request()
-            self.assertTrue(res.status_code == 200, res)
-            jsonresp = res.json
+            self.assertEqual(200, res.status_code, res)
+            json_response = res.json
             # Result-Value is false, the user has not answered the challenge, yet
-            self.assertFalse(jsonresp.get("result").get("value"))
+            self.assertFalse(json_response.get("result").get("value"))
 
         # As the challenge has not been answered yet, the /validate/polltransaction endpoint returns false
         with self.app.test_request_context('/validate/polltransaction', method='GET',
                                            query_string={'transaction_id': transaction_id}):
             res = self.app.full_dispatch_request()
-            self.assertEqual(res.status_code, 200)
+            self.assertEqual(200, res.status_code)
             self.assertTrue(res.json["result"]["status"])
             self.assertFalse(res.json["result"]["value"])
 
         # Now the smartphone communicates with the backend and the challenge in the database table
         # is marked as answered successfully.
-        challengeobject_list = get_challenges(serial=tokenobj1.token.serial,
-                                              transaction_id=transaction_id)
-        challengeobject_list[0].set_otp_status(True)
+        challenges = get_challenges(serial=token1.token.serial, transaction_id=transaction_id)
+        challenges[0].set_otp_status(True)
 
         # As the challenge has been answered, the /validate/polltransaction endpoint returns true
         with self.app.test_request_context('/validate/polltransaction', method='GET',
                                            query_string={'transaction_id': transaction_id}):
             res = self.app.full_dispatch_request()
-            self.assertEqual(res.status_code, 200)
+            self.assertEqual(200, res.status_code)
             self.assertTrue(res.json["result"]["status"])
             self.assertTrue(res.json["result"]["value"])
 
@@ -1867,19 +1852,19 @@ def test_20_api_authenticate_two_tokens(self):
                                                  "pass": "",
                                                  "state": transaction_id}):
             res = self.app.full_dispatch_request()
-            self.assertTrue(res.status_code == 200, res)
-            jsonresp = res.json
+            self.assertEqual(200, res.status_code)
+            json_response = res.json
             # Result-Value is True, since the challenge is marked resolved in the DB
-        self.assertTrue(jsonresp.get("result").get("value"))
+        self.assertTrue(json_response.get("result").get("value"))
 
         # As the challenge does not exist anymore, the /validate/polltransaction endpoint returns false
         with self.app.test_request_context('/validate/polltransaction', method='GET',
                                            query_string={'transaction_id': transaction_id}):
             res = self.app.full_dispatch_request()
-            self.assertEqual(res.status_code, 200)
+            self.assertEqual(200, res.status_code)
             self.assertTrue(res.json["result"]["status"])
             self.assertFalse(res.json["result"]["value"])
-        self.assertEqual(get_challenges(serial=tokenobj1.token.serial), [])
+        self.assertEqual(get_challenges(serial=token1.token.serial), [])
 
         delete_policy("push_config")
         delete_policy("push_presence")