def test_02_app_local_store(self): store1 = get_app_local_store() store1["hello"] = "world" g.test_flag = True # We get the same store even if we push another app context for the same app with self.app.app_context(): store2 = get_app_local_store() self.assertEqual(store1, store2) self.assertEqual(store2["hello"], "world") self.assertNotIn("test_flag", g) g.test_flag = False self.assertEqual(g.test_flag, True) g.pop("test_flag") # We get a different store if we push a context for another app new_app = create_app("testing", "") with new_app.app_context(): store3 = get_app_local_store() self.assertNotIn("hello", store3) self.assertNotEqual(store3, store1) store3["hello"] = "no!" store4 = get_app_local_store() store3["something"] = "else" self.assertEqual(store4["hello"], "no!") self.assertEqual(store4["something"], "else") self.assertEqual(store1, store2) self.assertEqual(store2["hello"], "world")
def test_02_app_local_store(self): store1 = get_app_local_store() store1["hello"] = "world" g.test_flag = True # We get the same store even if we push another app context for the same app with self.app.app_context(): store2 = get_app_local_store() self.assertEqual(store1, store2) self.assertEqual(store2["hello"], "world") self.assertNotIn("test_flag", g) g.test_flag = False self.assertEquals(g.test_flag, True) g.pop("test_flag") # We get a different store if we push a context for another app new_app = create_app("testing", "") with new_app.app_context(): store3 = get_app_local_store() self.assertNotIn("hello", store3) self.assertNotEqual(store3, store1) store3["hello"] = "no!" store4 = get_app_local_store() store3["something"] = "else" self.assertEqual(store4["hello"], "no!") self.assertEqual(store4["something"], "else") self.assertEqual(store1, store2) self.assertEqual(store2["hello"], "world")
def register_app(self, app): """ Create an instance of a ``BaseQueue`` subclass according to the app config's ``PI_JOB_QUEUE_CLASS`` option and store it in the ``job_queue`` config. Register all collected jobs with this application. This instance is shared between threads! This function should only be called once per process. :param app: privacyIDEA app """ with app.app_context(): store = get_app_local_store() if "job_queue" in store: raise RuntimeError("App already has a job queue: {!r}".format( store["job_queue"])) try: package_name, class_name = app.config[JOB_QUEUE_CLASS].rsplit( ".", 1) queue_class = get_module_class(package_name, class_name) except (ImportError, ValueError) as exx: log.warning(u"Could not import job queue class {!r}: {!r}".format( app.config[JOB_QUEUE_CLASS], exx)) return # Extract configuration from app config: All options starting with PI_JOB_QUEUE_ options = {} for k, v in app.config.items(): if k.startswith(JOB_QUEUE_OPTION_PREFIX) and k != JOB_QUEUE_CLASS: options[k[len(JOB_QUEUE_OPTION_PREFIX):].lower()] = v job_queue = queue_class(options) log.info(u"Created a new job queue: {!r}".format(job_queue)) store["job_queue"] = job_queue for name, (func, args, kwargs) in self._jobs.items(): job_queue.register_job(name, func, *args, **kwargs)
def get_firebase_access_token(config_file_name): """ This returns the access token for a given JSON config file name :param config_file_name: The json file with the Service account credentials :type config_file_name: str :return: Firebase credentials :rtype: google.oauth2.service_account.Credentials """ fbt = "firebase_token" app_store = get_app_local_store() if fbt not in app_store or not isinstance(app_store[fbt], dict): # initialize the firebase_token in the app_store as dict app_store[fbt] = {} if not isinstance(app_store[fbt].get(config_file_name), service_account.Credentials) or \ app_store[fbt].get(config_file_name).expired: # If the type of the config is not of class Credentials or if the token # has expired we get new scoped access token credentials credentials = service_account.Credentials.from_service_account_file(config_file_name, scopes=SCOPES) log.debug("Fetching a new access_token for {!r} from firebase...".format(config_file_name)) # We do not use a lock here: The worst that could happen is that two threads # fetch new auth tokens concurrently. In this case, one of them wins and # is written to the dictionary. app_store[fbt][config_file_name] = credentials readable_time = credentials.expiry.isoformat() if credentials.expiry else 'Never' log.debug(u"Setting the expiration for {!r} of the new access_token " u"to {!s}.".format(config_file_name, readable_time)) return app_store[fbt][config_file_name]
def get_registry(): """ Return the ``EngineRegistry`` object associated with the current application. If there is no such object yet, create one and write it to the app-local store. This respects the ``PI_ENGINE_REGISTRY_CLASS`` config option. :return: an ``EngineRegistry`` object """ # This function will be called concurrently by multiple threads. # This is no problem when we already have an engine registry object. # However, if there is no registry object yet, two threads may concurrently # decide to create a new one. But as ``setdefault`` is atomic, only the # first one will be the written to ``app_store['config']``. The latter # one will not be referenced and will be garbage-collected at some point. app_store = get_app_local_store() try: return app_store["engine_registry"] except KeyError: # create a new engine registry of the appropriate class registry_class_name = get_app_config_value( "PI_ENGINE_REGISTRY_CLASS", DEFAULT_REGISTRY_CLASS_NAME) if registry_class_name not in ENGINE_REGISTRY_CLASSES: log.warning(u"Unknown engine registry class: {!r}".format( registry_class_name)) registry_class_name = DEFAULT_REGISTRY_CLASS_NAME registry = ENGINE_REGISTRY_CLASSES[registry_class_name]() log.info(u"Created a new engine registry: {!r}".format(registry)) return app_store.setdefault("engine_registry", registry)
def get_registry(): """ Return the ``EngineRegistry`` object associated with the current application. If there is no such object yet, create one and write it to the app-local store. This respects the ``PI_ENGINE_REGISTRY_CLASS`` config option. :return: an ``EngineRegistry`` object """ # This function will be called concurrently by multiple threads. # This is no problem when we already have an engine registry object. # However, if there is no registry object yet, two threads may concurrently # decide to create a new one. But as ``setdefault`` is atomic, only the # first one will be the written to ``app_store['config']``. The latter # one will not be referenced and will be garbage-collected at some point. app_store = get_app_local_store() try: return app_store["engine_registry"] except KeyError: # create a new engine registry of the appropriate class registry_class_name = get_app_config_value("PI_ENGINE_REGISTRY_CLASS", DEFAULT_REGISTRY_CLASS_NAME) if registry_class_name not in ENGINE_REGISTRY_CLASSES: log.warning(u"Unknown engine registry class: {!r}".format(registry_class_name)) registry_class_name = DEFAULT_REGISTRY_CLASS_NAME registry = ENGINE_REGISTRY_CLASSES[registry_class_name]() log.info(u"Created a new engine registry: {!r}".format(registry)) return app_store.setdefault("engine_registry", registry)
def register_app(self, app): """ Create an instance of a ``BaseQueue`` subclass according to the app config's ``PI_JOB_QUEUE_CLASS`` option and store it in the ``job_queue`` config. Register all collected jobs with this application. This instance is shared between threads! This function should only be called once per process. :param app: privacyIDEA app """ with app.app_context(): store = get_app_local_store() if "job_queue" in store: raise RuntimeError("App already has a job queue: {!r}".format(store["job_queue"])) try: package_name, class_name = app.config[JOB_QUEUE_CLASS].rsplit(".", 1) queue_class = get_module_class(package_name, class_name) except (ImportError, ValueError) as exx: log.warning(u"Could not import job queue class {!r}: {!r}".format(app.config[JOB_QUEUE_CLASS], exx)) return # Extract configuration from app config: All options starting with PI_JOB_QUEUE_ options = {} for k, v in app.config.items(): if k.startswith(JOB_QUEUE_OPTION_PREFIX) and k != JOB_QUEUE_CLASS: options[k[len(JOB_QUEUE_OPTION_PREFIX):].lower()] = v job_queue = queue_class(options) log.info(u"Created a new job queue: {!r}".format(job_queue)) store["job_queue"] = job_queue for name, (func, args, kwargs) in self._jobs.items(): job_queue.register_job(name, func, *args, **kwargs)
def get_job_queue(): """ Get the job queue registered with the current app. If no job queue is configured, raise a ServerError. """ store = get_app_local_store() if "job_queue" in store: return store["job_queue"] else: raise ServerError("privacyIDEA has no job queue configured!")
def get_job_queue(): """ Get the job queue registered with the current app. If no job queue is configured, raise a ServerError. """ store = get_app_local_store() if "job_queue" in store: return store["job_queue"] else: raise ServerError("privacyIDEA has no job queue configured!")
def get_shared_config_object(): """ :return: the application-wide ``SharedConfigClass`` object, which is created on demand. """ store = get_app_local_store() if 'shared_config_object' not in store: # It might happen that two threads create SharedConfigClass() instances in parallel. # However, as setting dictionary values is atomic, one of the two objects will "win", # and the next request handled by the second thread will use the winning config object. log.debug(u"Creating new shared config object") store['shared_config_object'] = SharedConfigClass() return store['shared_config_object']
def test_03a_api_authenticate_fail(self): # This tests the failed to communicate to the firebase service self.setUp_user_realms() # get enrolled push token toks = get_tokens(tokentype="push") self.assertEqual(len(toks), 1) tokenobj = toks[0] # set PIN tokenobj.set_pin("pushpin") tokenobj.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.ServiceAccountCredentials' ) as mySA: # alternative: side_effect instead of return_value mySA.from_json_keyfile_name.return_value = myCredentials( myAccessTokenInfo("my_bearer_token")) # add responses, to simulate the failing communication (status 500) 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 self.app.test_request_context('/validate/check', method='POST', data={ "user": "******", "realm": self.realm1, "pass": "******" }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 400, res) jsonresp = res.json self.assertFalse(jsonresp.get("result").get("status")) self.assertEqual( jsonresp.get("result").get("error").get("code"), 401) self.assertEqual( jsonresp.get("result").get("error").get("message"), "ERR401: Failed to submit " "message to firebase service.") # Our ServiceAccountCredentials mock has been called once, because no access token has been fetched before self.assertEqual(len(mySA.from_json_keyfile_name.mock_calls), 1) self.assertIn(FIREBASE_FILE, get_app_local_store()["firebase_token"])
def init_hsm(): """ Initialize the HSM in the app-local store The config file pi.cfg may contain PI_HSM_MODULE and parameters like: PI_HSM_MODULE_MODULE PI_HSM_MODULE_SLOT_ID... :return: hsm object """ app_store = get_app_local_store() if "pi_hsm" not in app_store or not isinstance(app_store["pi_hsm"], dict): config = get_app_config() HSM_config = {"obj": create_hsm_object(config)} app_store["pi_hsm"] = HSM_config log.info("Initialized HSM object {0}".format(HSM_config)) return app_store["pi_hsm"]["obj"]
def init_hsm(): """ Initialize the HSM in the app-local store The config file pi.cfg may contain PI_HSM_MODULE and parameters like: PI_HSM_MODULE_MODULE PI_HSM_MODULE_SLOT_ID... :return: hsm object """ app_store = get_app_local_store() if "pi_hsm" not in app_store or not isinstance(app_store["pi_hsm"], dict): config = get_app_config() HSM_config = {"obj": create_hsm_object(config)} app_store["pi_hsm"] = HSM_config log.info("Initialized HSM object {0}".format(HSM_config)) return app_store["pi_hsm"]["obj"]
def get_firebase_access_token(config_file_name): """ This returns the access token for a given JSON config file name :param config_file_name: :return: """ fbt = "firebase_token" now = time.time() app_store = get_app_local_store() if fbt not in app_store or not isinstance(app_store[fbt], dict): # initialize the firebase_token in the app_store as dict app_store[fbt] = {} if not isinstance(app_store[fbt].get(config_file_name), AccessToken) or \ now > app_store[fbt].get(config_file_name).expires_at: # If the type of the config is not class AccessToken or # if the token has expired credentials = ServiceAccountCredentials.from_json_keyfile_name( config_file_name, SCOPES) log.debug( "Fetching a new access_token for {!r} from firebase...".format( config_file_name)) access_token_info = credentials.get_access_token() # Now we set the expiration date for the new access_token with a margin of 10 seconds At = AccessToken(access_token_info.access_token, access_token_info.expires_in) # We do not use a lock here: The worst that could happen is that two threads # fetch new auth tokens concurrently. In this case, one of them wins and is written to the dictionary. app_store[fbt][config_file_name] = At readable_time = datetime.datetime.fromtimestamp( At.expires_at).isoformat() log.debug( u"Setting the expiration for {!r} of the new access_token to {!s}." .format(config_file_name, readable_time)) return app_store[fbt][config_file_name].access_token
def has_job_queue(): """ Return a boolean describing whether the current app has an app queue configured. """ return "job_queue" in get_app_local_store()
def test_04_api_authenticate_smartphone(self): # Test the /validate/check endpoints and the smartphone endpoint /ttype/push # for authentication # get enrolled push token toks = get_tokens(tokentype="push") self.assertEqual(len(toks), 1) tokenobj = toks[0] # set PIN tokenobj.set_pin("pushpin") tokenobj.add_user(User("cornelius", self.realm1)) def check_firebase_params(request): payload = json.loads(request.body) # check the signature in the payload! data = payload.get("message").get("data") sign_string = u"{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()) 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'} return (200, headers, json.dumps({})) # We mock the ServiceAccountCredentials, since we can not directly contact the Google API with mock.patch( 'privacyidea.lib.smsprovider.FirebaseProvider.ServiceAccountCredentials' ) as mySA: # alternative: side_effect instead of return_value mySA.from_json_keyfile_name.return_value = myCredentials( myAccessTokenInfo("my_bearer_token")) # add responses, to simulate the communication to firebase responses.add_callback( responses.POST, 'https://fcm.googleapis.com/v1/projects/test-123456/messages:send', callback=check_firebase_params, content_type="application/json") # Send the first authentication request to trigger the challenge with self.app.test_request_context('/validate/check', method='POST', data={ "user": "******", "realm": self.realm1, "pass": "******" }): 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) # Our ServiceAccountCredentials mock has not been called because we use a cached token self.assertEqual(len(mySA.from_json_keyfile_name.mock_calls), 0) self.assertIn(FIREBASE_FILE, get_app_local_store()["firebase_token"]) # The challenge is sent to the smartphone via the Firebase service, so we do not know # the challenge from the /validate/check API. # So lets read the challenge from the database! challengeobject_list = get_challenges(serial=tokenobj.token.serial, transaction_id=transaction_id) challenge = challengeobject_list[0].challenge # Incomplete request fails with HTTP400 with self.app.test_request_context('/ttype/push', method='POST', data={ "serial": tokenobj.token.serial, "nonce": challenge }): res = self.app.full_dispatch_request() self.assertEquals(res.status_code, 400) # This is what the smartphone answers. # create the signature: sign_data = "{0!s}|{1!s}".format(challenge, tokenobj.token.serial) signature = b32encode_and_unicode( self.smartphone_private_key.sign(sign_data.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256())) # Try an invalid signature first wrong_sign_data = "{}|{}".format(challenge, tokenobj.token.serial[1:]) wrong_signature = b32encode_and_unicode( self.smartphone_private_key.sign(wrong_sign_data.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256())) # Signed the wrong data with self.app.test_request_context('/ttype/push', method='POST', data={ "serial": tokenobj.token.serial, "nonce": challenge, "signature": wrong_signature }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertTrue(res.json['result']['status']) self.assertFalse(res.json['result']['value']) # Correct signature, wrong challenge wrong_challenge = b32encode_and_unicode(geturandom()) wrong_sign_data = "{}|{}".format(wrong_challenge, tokenobj.token.serial) wrong_signature = b32encode_and_unicode( self.smartphone_private_key.sign(wrong_sign_data.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256())) with self.app.test_request_context('/ttype/push', method='POST', data={ "serial": tokenobj.token.serial, "nonce": wrong_challenge, "signature": wrong_signature }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertTrue(res.json['result']['status']) self.assertFalse(res.json['result']['value']) # Correct signature, empty nonce with self.app.test_request_context('/ttype/push', method='POST', data={ "serial": tokenobj.token.serial, "nonce": "", "signature": signature }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertTrue(res.json['result']['status']) self.assertFalse(res.json['result']['value']) # Correct signature, wrong private key wrong_key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) wrong_sign_data = "{}|{}".format(challenge, tokenobj.token.serial) wrong_signature = b32encode_and_unicode( wrong_key.sign(wrong_sign_data.encode("utf-8"), padding.PKCS1v15(), hashes.SHA256())) with self.app.test_request_context('/ttype/push', method='POST', data={ "serial": tokenobj.token.serial, "nonce": challenge, "signature": wrong_signature }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertTrue(res.json['result']['status']) self.assertFalse(res.json['result']['value']) # Result value is still false with self.app.test_request_context('/validate/check', method='POST', data={ "user": "******", "realm": self.realm1, "pass": "", "state": transaction_id }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertFalse(res.json['result']['value']) # Now the correct request with self.app.test_request_context('/ttype/push', method='POST', data={ "serial": tokenobj.token.serial, "nonce": challenge, "signature": signature }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) self.assertTrue(res.json['result']['status']) self.assertTrue(res.json['result']['value']) with self.app.test_request_context('/validate/check', method='POST', data={ "user": "******", "realm": self.realm1, "pass": "", "state": transaction_id }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) jsonresp = res.json # Result-Value is True self.assertTrue(jsonresp.get("result").get("value"))
def test_03b_api_authenticate_client(self): # Test the /validate/check endpoints without the smartphone endpoint /ttype/push self.setUp_user_realms() # get enrolled push token toks = get_tokens(tokentype="push") self.assertEqual(len(toks), 1) tokenobj = toks[0] # set PIN tokenobj.set_pin("pushpin") tokenobj.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.ServiceAccountCredentials' ) as mySA: # alternative: side_effect instead of return_value mySA.from_json_keyfile_name.return_value = myCredentials( myAccessTokenInfo("my_bearer_token")) # add responses, to simulate the communication to firebase 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', data={ "user": "******", "realm": self.realm1, "pass": "******" }): 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) # Our ServiceAccountCredentials mock has not been called because we use a cached token self.assertEqual(len(mySA.from_json_keyfile_name.mock_calls), 0) self.assertIn(FIREBASE_FILE, get_app_local_store()["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', data={ "user": "******", "realm": self.realm1, "pass": "", "transaction_id": transaction_id }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) jsonresp = res.json # Result-Value is false, the user has not answered the challenge, yet self.assertFalse(jsonresp.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', data={'transaction_id': transaction_id}): res = self.app.full_dispatch_request() self.assertEqual(res.status_code, 200) 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) # As the challenge has been answered, the /validate/polltransaction endpoint returns true with self.app.test_request_context( '/validate/polltransaction', method='GET', data={'transaction_id': transaction_id}): res = self.app.full_dispatch_request() self.assertEqual(res.status_code, 200) self.assertTrue(res.json["result"]["status"]) self.assertTrue(res.json["result"]["value"]) with self.app.test_request_context('/validate/check', method='POST', data={ "user": "******", "realm": self.realm1, "pass": "", "state": transaction_id }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) jsonresp = res.json # Result-Value is True, since the challenge is marked resolved in the DB self.assertTrue(jsonresp.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', data={'transaction_id': transaction_id}): res = self.app.full_dispatch_request() self.assertEqual(res.status_code, 200) self.assertTrue(res.json["result"]["status"]) self.assertFalse(res.json["result"]["value"]) self.assertEqual(get_challenges(serial=tokenobj.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: mock_time.time.return_value = time.time() + 4000 with mock.patch( 'privacyidea.lib.smsprovider.FirebaseProvider.ServiceAccountCredentials' ) as mySA: # alternative: side_effect instead of return_value mySA.from_json_keyfile_name.return_value = myCredentials( myAccessTokenInfo("my_new_bearer_token")) # add responses, to simulate the communication to firebase 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)) # Send the first authentication request to trigger the challenge with self.app.test_request_context('/validate/check', method='POST', data={ "user": "******", "realm": self.realm1, "pass": "******" }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) jsonresp = 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) delete_policy("push1") # Our ServiceAccountCredentials mock has been called once because we fetched a new token self.assertEqual(len(mySA.from_json_keyfile_name.mock_calls), 1) 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.ServiceAccountCredentials' ) as mySA: # alternative: side_effect instead of return_value mySA.from_json_keyfile_name.return_value = myCredentials( myAccessTokenInfo("my_bearer_token")) # add responses, to simulate the communication to firebase 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)) # Send the first authentication request to trigger the challenge with self.app.test_request_context('/validate/check', method='POST', data={ "user": "******", "realm": self.realm1, "pass": "******" }): res = self.app.full_dispatch_request() self.assertTrue(res.status_code == 200, res) jsonresp = 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) delete_policy("push1")
def has_job_queue(): """ Return a boolean describing whether the current app has an app queue configured. """ return "job_queue" in get_app_local_store()