def test_invalid_locales(config): """ An invalid locale raises an error during app configuration. """ fake_config = SDConfig() fake_config.SUPPORTED_LOCALES = [FALLBACK_LOCALE, "yy_ZZ"] fake_config.TRANSLATION_DIRS = Path(config.TEMP_DIR) with pytest.raises(UnknownLocaleError): journalist_app_module.create_app(fake_config) with pytest.raises(UnknownLocaleError): source_app.create_app(fake_config)
def _start_journalist_server( port: int, config_to_use: SecureDropConfig, journalist_app_setup_callback: Optional[Callable[[SecureDropConfig], None]], ) -> None: # This function will be called in a separate Process that runs the journalist app # Modify the sdconfig module in the app's memory so that it mirrors the supplied config # Do this BEFORE importing any other module of the application so the modified config is # what eventually gets imported by the app's code import sdconfig sdconfig.config = config_to_use # type: ignore # Then start the journalist app from journalist_app import create_app # Some tests require a specific state to be set (such as having a submission) if journalist_app_setup_callback: journalist_app_setup_callback(config_to_use) journalist_app = create_app(config_to_use) # type: ignore journalist_app.run(port=port, debug=True, use_reloader=False, threaded=True)
def test_filters(self): sources = [ 'tests/i18n/code.py', ] kwargs = { 'translations_dir': config.TEMP_DIR, 'mapping': 'tests/i18n/babel.cfg', 'source': sources, 'extract_update': True, 'compile': True, 'verbose': logging.DEBUG, 'version': version.__version__, } args = argparse.Namespace(**kwargs) manage.setup_verbosity(args) manage.translate_messages(args) manage.sh(""" pybabel init -i {d}/messages.pot -d {d} -l en_US pybabel init -i {d}/messages.pot -d {d} -l fr_FR """.format(d=config.TEMP_DIR)) fake_config = self.get_fake_config() fake_config.SUPPORTED_LOCALES = ['en_US', 'fr_FR'] fake_config.TRANSLATION_DIRS = config.TEMP_DIR for app in (journalist_app.create_app(fake_config), source_app.create_app(fake_config)): assert i18n.LOCALES == fake_config.SUPPORTED_LOCALES self.verify_filesizeformat(app) self.verify_rel_datetime_format(app)
def test_html_attributes(journalist_app, config): """Check that HTML lang and dir attributes respect locale.""" # Then delete it because using it won't test what we want del journalist_app config.SUPPORTED_LOCALES = ["ar", "en_US"] app = journalist_app_module.create_app(config).test_client() resp = app.get("/?l=ar", follow_redirects=True) html = resp.data.decode("utf-8") assert '<html lang="ar" dir="rtl">' in html resp = app.get("/?l=en_US", follow_redirects=True) html = resp.data.decode("utf-8") assert '<html lang="en-US" dir="ltr">' in html app = source_app.create_app(config).test_client() resp = app.get("/?l=ar", follow_redirects=True) html = resp.data.decode("utf-8") assert '<html lang="ar" dir="rtl">' in html resp = app.get("/?l=en_US", follow_redirects=True) html = resp.data.decode("utf-8") assert '<html lang="en-US" dir="ltr">' in html # check '/generate' too because '/' uses a different template resp = app.post("/generate?l=ar", data={"tor2web_check": 'href="fake.onion"'}, follow_redirects=True) html = resp.data.decode("utf-8") assert '<html lang="ar" dir="rtl">' in html resp = app.post("/generate?l=en_US", data={"tor2web_check": 'href="fake.onion"'}, follow_redirects=True) html = resp.data.decode("utf-8") assert '<html lang="en-US" dir="ltr">' in html
def test_no_usable_fallback_locale(journalist_app, config): """ The apps fail if neither the default nor the fallback locale is usable. """ fake_config = SDConfig() fake_config.DEFAULT_LOCALE = NEVER_LOCALE fake_config.SUPPORTED_LOCALES = [NEVER_LOCALE] fake_config.TRANSLATION_DIRS = Path(config.TEMP_DIR) i18n.USABLE_LOCALES = set() with pytest.raises(ValueError, match="in the set of usable locales"): journalist_app_module.create_app(fake_config) with pytest.raises(ValueError, match="in the set of usable locales"): source_app.create_app(fake_config)
def test_i18n(journalist_app, config): # Then delete it because using it won't test what we want del journalist_app sources = [ os.path.join(TESTS_DIR, "i18n/code.py"), os.path.join(TESTS_DIR, "i18n/template.html"), ] i18n_tool.I18NTool().main([ "--verbose", "translate-messages", "--mapping", os.path.join(TESTS_DIR, "i18n/babel.cfg"), "--translations-dir", config.TEMP_DIR, "--sources", ",".join(sources), "--extract-update", ]) pot = os.path.join(config.TEMP_DIR, "messages.pot") pybabel("init", "-i", pot, "-d", config.TEMP_DIR, "-l", "en_US") for (l, s) in ( ("fr_FR", "code bonjour"), ("zh_Hans", "code chinese"), ("ar", "code arabic"), ("nb_NO", "code norwegian"), ("es_ES", "code spanish"), ): pybabel("init", "-i", pot, "-d", config.TEMP_DIR, "-l", l) po = os.path.join(config.TEMP_DIR, l, "LC_MESSAGES/messages.po") sed("-i", "-e", '/code hello i18n/,+1s/msgstr ""/msgstr "{}"/'.format(s), po) i18n_tool.I18NTool().main([ "--verbose", "translate-messages", "--translations-dir", config.TEMP_DIR, "--compile", ]) fake_config = SDConfig() fake_config.SUPPORTED_LOCALES = [ "ar", "en_US", "fr_FR", "nb_NO", "zh_Hans" ] fake_config.TRANSLATION_DIRS = Path(config.TEMP_DIR) # Use our config (and not an app fixture) because the i18n module # grabs values at init time and we can't inject them later. for app in (journalist_app_module.create_app(fake_config), source_app.create_app(fake_config)): with app.app_context(): db.create_all() assert list(i18n.LOCALES.keys()) == fake_config.SUPPORTED_LOCALES verify_i18n(app)
def test_verify_default_locale_en_us_if_not_defined_in_config(self): class Config: def __getattr__(self, name): if name == 'DEFAULT_LOCALE': raise AttributeError() return getattr(config, name) not_translated = 'code hello i18n' with source_app.create_app(Config()).test_client() as c: c.get('/') assert not_translated == gettext(not_translated)
def test_i18n(journalist_app, config): # Then delete it because using it won't test what we want del journalist_app sources = [ os.path.join(TESTS_DIR, 'i18n/code.py'), os.path.join(TESTS_DIR, 'i18n/template.html'), ] i18n_tool.I18NTool().main([ '--verbose', 'translate-messages', '--mapping', os.path.join(TESTS_DIR, 'i18n/babel.cfg'), '--translations-dir', config.TEMP_DIR, '--sources', ",".join(sources), '--extract-update', ]) pot = os.path.join(config.TEMP_DIR, 'messages.pot') pybabel('init', '-i', pot, '-d', config.TEMP_DIR, '-l', 'en_US') for (l, s) in (('fr_FR', 'code bonjour'), ('zh_Hans_CN', 'code chinese'), ('ar', 'code arabic'), ('nb_NO', 'code norwegian'), ('es_ES', 'code spanish')): pybabel('init', '-i', pot, '-d', config.TEMP_DIR, '-l', l) po = os.path.join(config.TEMP_DIR, l, 'LC_MESSAGES/messages.po') sed('-i', '-e', '/code hello i18n/,+1s/msgstr ""/msgstr "{}"/'.format(s), po) i18n_tool.I18NTool().main([ '--verbose', 'translate-messages', '--translations-dir', config.TEMP_DIR, '--compile', ]) fake_config = SDConfig() fake_config.SUPPORTED_LOCALES = [ 'en_US', 'fr_FR', 'zh_Hans_CN', 'ar', 'nb_NO' ] fake_config.TRANSLATION_DIRS = config.TEMP_DIR # Use our config (and not an app fixture) because the i18n module # grabs values at init time and we can't inject them later. for app in (journalist_app_module.create_app(fake_config), source_app.create_app(fake_config)): with app.app_context(): db.create_all() assert i18n.LOCALES == fake_config.SUPPORTED_LOCALES verify_i18n(app)
def test_i18n(self): sources = [ 'tests/i18n/code.py', 'tests/i18n/template.html', ] kwargs = { 'translations_dir': config.TEMP_DIR, 'mapping': 'tests/i18n/babel.cfg', 'source': sources, 'extract_update': True, 'compile': True, 'verbose': logging.DEBUG, 'version': version.__version__, } args = argparse.Namespace(**kwargs) manage.setup_verbosity(args) manage.translate_messages(args) manage.sh(""" pybabel init -i {d}/messages.pot -d {d} -l en_US pybabel init -i {d}/messages.pot -d {d} -l fr_FR sed -i -e '/code hello i18n/,+1s/msgstr ""/msgstr "code bonjour"/' \ {d}/fr_FR/LC_MESSAGES/messages.po pybabel init -i {d}/messages.pot -d {d} -l zh_Hans_CN sed -i -e '/code hello i18n/,+1s/msgstr ""/msgstr "code chinese"/' \ {d}/zh_Hans_CN/LC_MESSAGES/messages.po pybabel init -i {d}/messages.pot -d {d} -l ar sed -i -e '/code hello i18n/,+1s/msgstr ""/msgstr "code arabic"/' \ {d}/ar/LC_MESSAGES/messages.po pybabel init -i {d}/messages.pot -d {d} -l nb_NO sed -i -e '/code hello i18n/,+1s/msgstr ""/msgstr "code norwegian"/' \ {d}/nb_NO/LC_MESSAGES/messages.po pybabel init -i {d}/messages.pot -d {d} -l es_ES sed -i -e '/code hello i18n/,+1s/msgstr ""/msgstr "code spanish"/' \ {d}/es_ES/LC_MESSAGES/messages.po """.format(d=config.TEMP_DIR)) manage.translate_messages(args) fake_config = self.get_fake_config() fake_config.SUPPORTED_LOCALES = [ 'en_US', 'fr_FR', 'zh_Hans_CN', 'ar', 'nb_NO' ] fake_config.TRANSLATION_DIRS = config.TEMP_DIR for app in (journalist_app.create_app(fake_config), source_app.create_app(fake_config)): assert i18n.LOCALES == fake_config.SUPPORTED_LOCALES self.verify_i18n(app)
def test_i18n(self): sources = [ 'tests/i18n/code.py', 'tests/i18n/template.html', ] kwargs = { 'translations_dir': config.TEMP_DIR, 'mapping': 'tests/i18n/babel.cfg', 'source': sources, 'extract_update': True, 'compile': True, 'verbose': logging.DEBUG, 'version': version.__version__, } args = argparse.Namespace(**kwargs) manage.setup_verbosity(args) manage.translate_messages(args) manage.sh(""" pybabel init -i {d}/messages.pot -d {d} -l en_US pybabel init -i {d}/messages.pot -d {d} -l fr_FR sed -i -e '/code hello i18n/,+1s/msgstr ""/msgstr "code bonjour"/' \ {d}/fr_FR/LC_MESSAGES/messages.po pybabel init -i {d}/messages.pot -d {d} -l zh_Hans_CN sed -i -e '/code hello i18n/,+1s/msgstr ""/msgstr "code chinese"/' \ {d}/zh_Hans_CN/LC_MESSAGES/messages.po pybabel init -i {d}/messages.pot -d {d} -l ar sed -i -e '/code hello i18n/,+1s/msgstr ""/msgstr "code arabic"/' \ {d}/ar/LC_MESSAGES/messages.po pybabel init -i {d}/messages.pot -d {d} -l nb_NO sed -i -e '/code hello i18n/,+1s/msgstr ""/msgstr "code norwegian"/' \ {d}/nb_NO/LC_MESSAGES/messages.po pybabel init -i {d}/messages.pot -d {d} -l es_ES sed -i -e '/code hello i18n/,+1s/msgstr ""/msgstr "code spanish"/' \ {d}/es_ES/LC_MESSAGES/messages.po """.format(d=config.TEMP_DIR)) manage.translate_messages(args) fake_config = self.get_fake_config() fake_config.SUPPORTED_LOCALES = [ 'en_US', 'fr_FR', 'zh_Hans_CN', 'ar', 'nb_NO'] fake_config.TRANSLATION_DIRS = config.TEMP_DIR for app in (journalist_app.create_app(fake_config), source_app.create_app(fake_config)): assert i18n.LOCALES == fake_config.SUPPORTED_LOCALES self.verify_i18n(app)
def _start_source_server(port: int, config_to_use: SecureDropConfig) -> None: # This function will be called in a separate Process that runs the source app # Modify the sdconfig module in the app's memory so that it mirrors the supplied config # Do this BEFORE importing any other module of the application so the modified config is # what eventually gets imported by the app's code import sdconfig sdconfig.config = config_to_use # type: ignore # Then start the source app from source_app import create_app source_app = create_app(config_to_use) # type: ignore source_app.run(port=port, debug=True, use_reloader=False, threaded=True)
def test_valid_but_unusable_locales(config, caplog): """ The apps start with one or more unusable, but still valid, locales, but log an error for OSSEC to pick up. """ fake_config = SDConfig() fake_config.SUPPORTED_LOCALES = [FALLBACK_LOCALE, "wae_CH"] fake_config.TRANSLATION_DIRS = Path(config.TEMP_DIR) for app in (journalist_app_module.create_app(fake_config), source_app.create_app(fake_config)): with app.app_context(): assert "wae" in caplog.text assert "not in the set of usable locales" in caplog.text
def test_unusable_default_but_usable_fallback_locale(config, caplog): """ The apps start even if the default locale is unusable, as along as the fallback locale is usable, but log an error for OSSEC to pick up. """ fake_config = SDConfig() fake_config.DEFAULT_LOCALE = NEVER_LOCALE fake_config.SUPPORTED_LOCALES = [NEVER_LOCALE, FALLBACK_LOCALE] fake_config.TRANSLATION_DIRS = Path(config.TEMP_DIR) for app in (journalist_app_module.create_app(fake_config), source_app.create_app(fake_config)): with app.app_context(): assert NEVER_LOCALE in caplog.text assert "not in the set of usable locales" in caplog.text
def test_supported_locales(config): fake_config = SDConfig() # Check that an invalid locale raises an error during app # configuration. fake_config.SUPPORTED_LOCALES = ['en_US', 'yy_ZZ'] fake_config.TRANSLATION_DIRS = Path(config.TEMP_DIR) with pytest.raises(UnknownLocaleError): journalist_app_module.create_app(fake_config) with pytest.raises(UnknownLocaleError): source_app.create_app(fake_config) # Check that a valid but unsupported locale raises an error during # app configuration. fake_config.SUPPORTED_LOCALES = ['en_US', 'wae_CH'] fake_config.TRANSLATION_DIRS = Path(config.TEMP_DIR) with pytest.raises(ValueError, match="not in the set of translated locales"): journalist_app_module.create_app(fake_config) with pytest.raises(ValueError, match="not in the set of translated locales"): source_app.create_app(fake_config)
def test_html_en_lang_correct(self): fake_config = self.get_fake_config() app = journalist_app.create_app(fake_config).test_client() resp = app.get('/', follow_redirects=True) html = resp.data.decode('utf-8') assert re.compile('<html .*lang="en".*>').search(html), html app = source_app.create_app(fake_config).test_client() resp = app.get('/', follow_redirects=True) html = resp.data.decode('utf-8') assert re.compile('<html .*lang="en".*>').search(html), html # check '/generate' too because '/' uses a different template resp = app.get('/generate', follow_redirects=True) html = resp.data.decode('utf-8') assert re.compile('<html .*lang="en".*>').search(html), html
def start_source_server(): # We call Random.atfork() here because we fork the source and # journalist server from the main Python process we use to drive # our browser with multiprocessing.Process() below. These child # processes inherit the same RNG state as the parent process, which # is a problem because they would produce identical output if we # didn't re-seed them after forking. Random.atfork() config.SESSION_EXPIRATION_MINUTES = self.session_expiration source_app = create_app(config) source_app.run(port=source_port, debug=True, use_reloader=False, threaded=True)
def test_html_en_lang_correct(journalist_app, config): # Then delete it because using it won't test what we want del journalist_app app = journalist_app_module.create_app(config).test_client() resp = app.get('/', follow_redirects=True) html = resp.data.decode('utf-8') assert re.compile('<html .*lang="en".*>').search(html), html app = source_app.create_app(config).test_client() resp = app.get('/', follow_redirects=True) html = resp.data.decode('utf-8') assert re.compile('<html .*lang="en".*>').search(html), html # check '/generate' too because '/' uses a different template resp = app.get('/generate', follow_redirects=True) html = resp.data.decode('utf-8') assert re.compile('<html .*lang="en".*>').search(html), html
def test_html_fr_lang_correct(self): """Check that when the locale is fr_FR the lang property is correct""" fake_config = self.get_fake_config() fake_config.SUPPORTED_LOCALES = ['fr_FR', 'en_US'] app = journalist_app.create_app(fake_config).test_client() resp = app.get('/?l=fr_FR', follow_redirects=True) html = resp.data.decode('utf-8') assert re.compile('<html .*lang="fr".*>').search(html), html app = source_app.create_app(fake_config).test_client() resp = app.get('/?l=fr_FR', follow_redirects=True) html = resp.data.decode('utf-8') assert re.compile('<html .*lang="fr".*>').search(html), html # check '/generate' too because '/' uses a different template resp = app.get('/generate?l=fr_FR', follow_redirects=True) html = resp.data.decode('utf-8') assert re.compile('<html .*lang="fr".*>').search(html), html
def start_source_server(): # We call Random.atfork() here because we fork the source and # journalist server from the main Python process we use to drive # our browser with multiprocessing.Process() below. These child # processes inherit the same RNG state as the parent process, which # is a problem because they would produce identical output if we # didn't re-seed them after forking. Random.atfork() config.SESSION_EXPIRATION_MINUTES = self.session_expiration source_app = create_app(config) source_app.run( port=source_port, debug=True, use_reloader=False, threaded=True)
def test_html_en_lang_correct(journalist_app, config): # Then delete it because using it won't test what we want del journalist_app app = journalist_app_module.create_app(config).test_client() resp = app.get("/", follow_redirects=True) html = resp.data.decode("utf-8") assert re.compile('<html lang="en-US".*>').search(html), html app = source_app.create_app(config).test_client() resp = app.get("/", follow_redirects=True) html = resp.data.decode("utf-8") assert re.compile('<html lang="en-US".*>').search(html), html # check '/generate' too because '/' uses a different template resp = app.post("/generate", data={"tor2web_check": 'href="fake.onion"'}, follow_redirects=True) html = resp.data.decode("utf-8") assert re.compile('<html lang="en-US".*>').search(html), html
def test_html_fr_lang_correct(journalist_app, config): """Check that when the locale is fr_FR the lang property is correct""" # Then delete it because using it won't test what we want del journalist_app config.SUPPORTED_LOCALES = ['fr_FR', 'en_US'] app = journalist_app_module.create_app(config).test_client() resp = app.get('/?l=fr_FR', follow_redirects=True) html = resp.data.decode('utf-8') assert re.compile('<html .*lang="fr".*>').search(html), html app = source_app.create_app(config).test_client() resp = app.get('/?l=fr_FR', follow_redirects=True) html = resp.data.decode('utf-8') assert re.compile('<html .*lang="fr".*>').search(html), html # check '/generate' too because '/' uses a different template resp = app.get('/generate?l=fr_FR', follow_redirects=True) html = resp.data.decode('utf-8') assert re.compile('<html .*lang="fr".*>').search(html), html
def test_html_fr_lang_correct(journalist_app, config): """Check that when the locale is fr_FR the lang property is correct""" # Then delete it because using it won't test what we want del journalist_app config.SUPPORTED_LOCALES = ["fr_FR", "en_US"] app = journalist_app_module.create_app(config).test_client() resp = app.get("/?l=fr_FR", follow_redirects=True) html = resp.data.decode("utf-8") assert re.compile('<html lang="fr-FR".*>').search(html), html app = source_app.create_app(config).test_client() resp = app.get("/?l=fr_FR", follow_redirects=True) html = resp.data.decode("utf-8") assert re.compile('<html lang="fr-FR".*>').search(html), html # check '/generate' too because '/' uses a different template resp = app.post("/generate?l=fr_FR", data={"tor2web_check": 'href="fake.onion"'}, follow_redirects=True) html = resp.data.decode("utf-8") assert re.compile('<html lang="fr-FR".*>').search(html), html
# -*- coding: utf-8 -*- import config from source_app import create_app app = create_app(config) if __name__ == "__main__": # pragma: no cover debug = getattr(config, 'env', 'prod') != 'prod' app.run(debug=debug, host='0.0.0.0', port=8080)
def sd_servers(self): logging.info("Starting SecureDrop servers (session expiration = %s)", self.session_expiration) # Patch the two-factor verification to avoid intermittent errors logging.info("Mocking models.Journalist.verify_token") with mock.patch("models.Journalist.verify_token", return_value=True): logging.info("Mocking source_app.main.get_entropy_estimate") with mock.patch("source_app.main.get_entropy_estimate", return_value=8192): try: signal.signal(signal.SIGUSR1, lambda _, s: traceback.print_stack(s)) source_port = self._unused_port() journalist_port = self._unused_port() self.source_location = "http://127.0.0.1:%d" % source_port self.journalist_location = "http://127.0.0.1:%d" % journalist_port self.source_app = source_app.create_app(config) self.journalist_app = journalist_app.create_app(config) self.journalist_app.config["WTF_CSRF_ENABLED"] = True self.__context = self.journalist_app.app_context() self.__context.push() env.create_directories() db.create_all() self.gpg = env.init_gpg() # Add our test user try: valid_password = "******" user = Journalist(username="******", password=valid_password, is_admin=True) user.otp_secret = "JHCOGO7VCER3EJ4L" db.session.add(user) db.session.commit() except IntegrityError: logging.error("Test user already added") db.session.rollback() # This user is required for our tests cases to login self.admin_user = { "name": "journalist", "password": ("correct horse battery staple" " profanity oil chewy"), "secret": "JHCOGO7VCER3EJ4L", } self.admin_user["totp"] = pyotp.TOTP( self.admin_user["secret"]) def start_journalist_server(app): app.run(port=journalist_port, debug=True, use_reloader=False, threaded=True) self.source_process = Process( target=lambda: self.start_source_server(source_port)) self.journalist_process = Process( target=lambda: start_journalist_server(self. journalist_app)) self.source_process.start() self.journalist_process.start() for tick in range(30): try: requests.get(self.source_location, timeout=1) requests.get(self.journalist_location, timeout=1) except Exception: time.sleep(0.25) else: break yield finally: try: self.source_process.terminate() except Exception as e: logging.error("Error stopping source app: %s", e) try: self.journalist_process.terminate() except Exception as e: logging.error("Error stopping source app: %s", e) env.teardown() self.__context.pop()